Dependency Injection in Spring Boot

Dependency Injection in Spring can be done through constructors, setters or fields.

Constructor-Based Dependency Injection

@Configuration
public class AppConfig {

    @Bean
    public Item item1() {
        return new ItemImpl1();
    }

    @Bean
    public Store store() {
        return new Store(item1());
    }
}

The @Configuration annotation indicates that the class is a source of bean definitions. We can also add it to multiple configuration classes.

We use the @Bean annotation on a method to define a bean. If we don’t specify a custom name, then the bean name will default to the method name.

For a bean with the default singleton scope, Spring first checks if a cached instance of the bean already exists, and only creates a new one if it doesn’t. If we’re using the prototype scope, the container returns a new bean instance for each method call.

Setter-Based Dependency Injection

@Bean
public Store store() {
    Store store = new Store();
    store.setItem(item1());
    return store;
}

Field-Based Dependency Injection

public class Store {
    @Autowired
    private Item item; 
}

While constructing the Store object, if there’s no constructor or setter method to inject the Item bean, the container will use reflection to inject Item into Store.

Autowiring Dependencies

Wiring allows the Spring container to automatically resolve dependencies between collaborating beans by inspecting the beans that have been defined.

There are four modes of autowiring a bean using an XML configuration:

no: the default value – this means no autowiring is used for the bean and we have to explicitly name the dependencies.
byName: autowiring is done based on the name of the property, therefore Spring will look for a bean with the same name as the property that needs to be set.
byType: similar to the byName autowiring, only based on the type of the property. This means Spring will look for a bean with the same type of the property to set. If there’s more than one bean of that type, the framework throws an exception.
constructor: autowiring is done based on constructor arguments, meaning Spring will look for beans with the same type as the constructor arguments.

For example, let’s autowire the item1 bean defined above by type into the store bean:

@Bean(autowire = Autowire.BY_TYPE)
public class Store {
    
    private Item item;

    public setItem(Item item){
        this.item = item;    
    }
}

We can also inject beans using the @Autowired annotation for autowiring by type:

public class Store {
    
    @Autowired
    private Item item;
}

If there’s more than one bean of the same type, we can use the @Qualifier annotation to reference a bean by name:

public class Store {
    
    @Autowired
    @Qualifier("item1")
    private Item item;
}

References
https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring

Create Indexes in Spring Data MongoDB

Using @Indexed
This annotation marks the field as indexed in MongoDB:

@QueryEntity
@Document
public class User {
    @Indexed
    private String name;
    
    ... 
}

but as of Spring Data MongoDB 3.0, automatic index creation is turned off by default.

We can, however, change that behavior by explicitly overriding autoIndexCreation() method in our MongoConfig:

public class MongoConfig extends AbstractMongoClientConfiguration {

    // rest of the config goes here

    @Override
    protected boolean autoIndexCreation() {
        return true;
    }
}

We can create other types of index using these attributes :

@Indexed
@TextIndexed
@HashIndexed
@GeoSpatialIndexed
@WildcardIndexed
@GeoIndexed

Create an Index Programmatically

mongoOps.indexOps(User.class).
  ensureIndex(new Index().on("name", Direction.ASC));

Compound Indexes

MongoDB supports compound indexes, where a single index structure holds references to multiple fields.

@QueryEntity
@Document
@CompoundIndexes({
    @CompoundIndex(name = "email_age", def = "{'email.id' : 1, 'age': 1}")
})
public class User {
    //
}

References
https://www.baeldung.com/spring-data-mongodb-index-annotations-converter
https://careydevelopment.us/blog/spring-boot-and-mongotemplate-how-to-handle-a-full-text-search
https://spring.io/blog/2014/07/17/text-search-your-documents-with-spring-data-mongodb
https://mongodb.github.io/mongo-java-driver/3.4/driver/tutorials/indexes/
https://www.mongodb.com/docs/manual/indexes/

Initialize MongoDB Repositories using ApplicationContextAware in Spring Boot

@Component
public class ApplicationContextProvider implements ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        DB.mongoTemplate=applicationContext.getBean(MongoTemplate.class);
        DB.monitoringItemRepository = applicationContext.getBean(MonitoringItemRepository.class);
    }
}
public class DB {
    public static MongoTemplate mongoTemplate=null;
    public static MonitoringItemRepository monitoringItemRepository = null;
}

References
https://zetcode.com/springboot/applicationcontext/

Configure Spring Boot to Connect to MongoDB with Credential

@Configuration
public class MongoConfig extends AbstractMongoClientConfiguration {
    /*
     * Use the standard Mongo driver API to create a com.mongodb.client.MongoClient instance.
     */
    public MongoClient mongoClient() {
        //String connectionString = "mongodb://username:password@localhost:27017/?authSource=admin&authMechanism=SCRAM-SHA-1";
        MongoCredential credential = MongoCredential.createScramSha256Credential("username", "admin", "password".toCharArray());
        MongoClient mongoClient = MongoClients.create(
                MongoClientSettings.builder()
                        .applyToClusterSettings(builder ->
                                builder.hosts(Arrays.asList(new ServerAddress("localhost", 27017))))
                        .credential(credential)
                        .build());
        return mongoClient;
    }

    @Override
    protected String getDatabaseName() {
        return "databaseName";
    }

    @Override
    public Collection getMappingBasePackages() {
        return Collections.singleton("net.pupli");
    }
}

References
https://www.baeldung.com/spring-data-mongodb-tutorial
https://www.mongodb.com/docs/drivers/java/sync/current/fundamentals/auth/
https://www.mongodb.com/docs/manual/reference/connection-string/

Returning Image/Media Data with Spring

@RequestMapping(value = "/image-manual-response", method = RequestMethod.GET)
public void getImageAsByteArray(HttpServletResponse response) throws IOException {
    InputStream in = servletContext.getResourceAsStream("/WEB-INF/images/image-example.jpg");
    response.setContentType(MediaType.IMAGE_JPEG_VALUE);
    IOUtils.copy(in, response.getOutputStream());
}

or

@GetMapping(
  value = "/get-image-with-media-type",
  produces = MediaType.IMAGE_JPEG_VALUE
)
public @ResponseBody byte[] getImageWithMediaType() throws IOException {
    InputStream in = getClass()
      .getResourceAsStream("/com/baeldung/produceimage/image.jpg");
    return IOUtils.toByteArray(in);
}

References
https://www.baeldung.com/spring-mvc-image-media-data
https://www.baeldung.com/spring-controller-return-image-file

Pagination and Sorting using Spring Data JPA

Creating Repository

public interface ProductRepository extends PagingAndSortingRepository<Product, Integer> {
 
    List<Product> findAllByPrice(double price, Pageable pageable);
}

Pagination

Pageable firstPageWithTwoElements = PageRequest.of(0, 2);
 
Pageable secondPageWithFiveElements = PageRequest.of(1, 5);
Page<Product> allProducts = productRepository.findAll(firstPageWithTwoElements);
 
List<Product> allTenDollarProducts = 
  productRepository.findAllByPrice(10, secondPageWithFiveElements);

Pagination and Sorting

Pageable sortedByName = 
  PageRequest.of(0, 3, Sort.by("name"));
 
Pageable sortedByPriceDesc = 
  PageRequest.of(0, 3, Sort.by("price").descending());
 
Pageable sortedByPriceDescNameAsc = 
  PageRequest.of(0, 5, Sort.by("price").descending().and(Sort.by("name")));

References
https://www.baeldung.com/spring-data-jpa-pagination-sorting
https://www.baeldung.com/queries-in-spring-data-mongodb

Best approach for deploying Angular app in Spring Boot

Avoid @EnableWebMvc

WebConfig

package net.pupli.web_server.config;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.resource.EncodedResourceResolver;
import org.springframework.web.servlet.resource.GzipResourceResolver;
import org.springframework.web.servlet.resource.PathResourceResolver;

import java.io.IOException;
import java.util.List;

@Configuration
@EnableAutoConfiguration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        // enable cache for static files
/*        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/")
                .setCachePeriod(3600 * 24 * 7);*/

        // serve pre gzip files in static folder
        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/")
                .setCachePeriod(3600 * 24 * 7)
                .resourceChain(false)
                .addResolver(new EncodedResourceResolver());

        registry.addResourceHandler("/index.html")
                .addResourceLocations("classpath:/static/")
                .setCachePeriod(0);
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/notFound").setViewName("forward:/index.html");
    }

    @Bean
    public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> containerCustomizer() {
        return container -> {
            container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,
                    "/notFound"));
        };
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter();
        converters.add(gsonHttpMessageConverter);
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }
}

 

References
https://stackoverflow.com/questions/44692781/configure-spring-boot-to-redirect-404-to-a-single-page-app
https://stackoverflow.com/questions/39331929/spring-catch-all-route-for-index-html/42998817