Since using Spring Boot, I’ve really been using it all the time, and I can’t look at the previous Spring code at all. The idea of convention over configuration makes it easy to use out of the box without much configuration. However, because of its convenience, which means that many details are covered, developers who learn Spring Boot directly will only use it without knowing the internal implementation process. Recently, I had a chance to review the relevant content of Spring, and combed some usage of Bean assembly, describing the changes and progress in Bean assembly from the past Spring development to the current Spring development Boot.

From SSM integration to Bean assembly

At the beginning of learning, I think everyone will read some blogs, such as “Spring Spring MVC Mybatis integration”. Put together an SSM project step by step. I had no idea at the time, just follow the steps, create a configuration file, paste it in, and run, a basic scaffolding project. To review, there are basically the following steps:

  1. Set up a Maven Web project and add dependencies in POM.xml.
  2. Configure web.xml by importing the Spring -*.xml configuration file.
  3. Configure several Spring -*.xml files, such as automatic package sweep, static resource mapping, default view parser, database connection pool, and so on.
  4. Write business logic code (DAO, Services, Controller)

Later, you may need to upload files, and then configure related nodes in XML. In the development, basically follow a pattern — three-tier architecture, interface oriented programming. If the class is Controller, I’ll put at sign Controller; If it is “Services”, add an @services annotation and you can have fun writing business logic.

What is Spring? The understanding at the time, configured like this, with a few comments, was Spring. Or, that’s how Spring works.

As you learn more, you will have a better understanding of Spring. In Spring, everything is a Bean. Beans are the basic unit of a Spring application. A Core part of Spring is the management of beans.

<?xml version="1.0" encoding="UTF-8"? >
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="Http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:annotation-config/>

    <context:component-scan base-package="com.zjut.ssm.controller">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="20971500"/>
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="resolveLazily" value="true"/>
    </bean>
</beans>
Copy the code

Let’s have a look at Spring MVC again configuration file, in addition to some of the parameters, and the two beans nodes, injection InternalResourceViewResolver to deal with the view, injection CommonsMultipartResolver to handle file uploads. At this point, if you need to integrate Mybatis to work together, similarly, you can inject related beans. The core Bean of Mybatis is SqlSessionFactory, which creates a Session to operate the database. When you’re not using Spring, you can load XML, read in database information, and create.

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Copy the code

Obviously, the above code is not in line with Spring’s thinking. To achieve loose coupling and high cohesion, it is possible to inject beans through DI rather than directly new an instance, which is managed by the Spring IoC container. Mybatis is officially a Mybatis -spring package, just add the following Bean can organize Mybatis to work (create sqlSessionFactory, open Session, etc.), about Mybatis content is not expanded here.

<beans>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value=""/>
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value=""/>
    </bean>
</beans>
Copy the code

So, now we know that the essence of ** configuring Spring through XML and integrating various frameworks is Bean assembly. ** Each framework is made up of N beans, and if you need to use it, you must assemble the corresponding beans according to the requirements of the framework. Successfully assembled beans are managed uniformly by the Spring IoC container and work as expected.

The assembly of the Bean

Up to now, there have been many kinds of specific Bean assembly methods, from XML in the past to Java Config, and then to the current Spring Boot Auto Configuration, which is a process of continuous simplification and clarity.

Bean assembly typically consists of three steps: registration, scanning, and injection.

From XML to Java Config

XML configuration beans have long been a thing of the past, and it is now more common to assemble beans using Java Config and annotations. Of course, you can see it occasionally, such as in the integration of Spring -*.xml for the SSM framework.

Java Config has the following advantages:

  1. Java is type-safe, and if there is a problem during assembly, the compiler will respond directly.
  2. XML configuration files grow larger as the configuration increases and are difficult to maintain and manage.
  3. Java Config makes it easier to refactor and search beans.

Therefore, the following is mainly java-based configuration methods. The basic process is as follows:

/ / register
@Configuration
public class BeanConfiguration {
    @Bean
    public AtomicInteger count(a) {
        return newAtomicInteger(); }}/ / or
@Componment
public class Foo{}

/ / scan
@ComponentScan(basePackages={})
@Configuration
public class BeanConfiguration {}

/ / injection
@Autowired
private AtomicInteger c;
Copy the code

Let’s expand on this.

Java Config registers beans, which fall into two main categories, registering non-source beans and registering source beans.

Register non-source beans

Non-source beans are code that we can’t edit, mainly importing external frameworks or dependencies, or using some of Spring’s beans. The configuration of these beans is typically declared in Java files.

Create a Configuration class that uses the @Configuration modifier, and then use the @Bean modifier to modify the method that needs to create the Bean, specifying the value and name values (both equal). The following is an example:

@Configuration
public class BeanConfiguration {
    @Scope("prototype")
    @Bean(value = "uploadThreadPool")
    public ExecutorService downloadThreadPool(a) {
        return Executors.newFixedThreadPool(10); }}Copy the code

Among them are the following:

  1. Beans are created as singletons by default, and only one instance is created throughout the application. If necessary, a new instance is created for each injection and annotations can be added@Scope("prototype"). For this example, the default thread pool created is a singleton, used after injection anywhere in the application, using the same thread pool (globally shared); add@Scope("prototype")After that, it is injected separately in each Controller, which means that each Controller has its own thread pool, and each request is submitted separately to its own thread pool.
  2. When injecting multiple beans of the same type, manually specify a name or value value to distinguish them. Or through@PrimaryTo indicate the preferred Bean.

Register the source Bean

Source beans, meaning our own code, are typically not assembled as @beans, but instead use another set of semantic annotations. (@Component, @Controller, @Service, @repository) Adding these annotations makes this class a spring-managed Component class. The last three annotations are the most frequently used, and are almost template annotations. The Controller class adds @Controller, and the Service layer implementation adds @Service.

Here is an example of declaring your own wrapped class through Spring.

@Scope("prototype")
@Component(value = "uploadThread")
public class UploadTask implements Runnable {
    private List<ByteArrayInputStream> files;
    private List<String> fileNameList;
    private PropertiesConfig prop = SpringUtil.getBean(PropertiesConfig.class);

    // If you pass MutiPartFile directly, the file will not be saved, because spring will cache the TMP file after the object is passed
    public UploadThread(List<ByteArrayInputStream> files, List<String> fileNameList) {
        this.files = files;
        this.fileNameList = fileNameList;
    }

    @Override
    public void run(a) {
        for (int i = 0; i < files.size(); ++i) {
            String fileName = fileNameList.get(i);
            String filePath = FileUtils.generatePath(prop.getImageSavePath(),fileName);
            FileUtils.save(newFile(filePath), files.get(i)); }}}Copy the code

Continuing with the thread pool above, we have implemented a task to handle asynchronous upload tasks. In traditional JUC, we would write code like this:

private ExecutorService uploadThreadPool = Executors.newFixedThreadPool(10);
uploadThreadPool.submit(new UploadTask(fileCopyList, fileNameList));
Copy the code

In Spring, I thought I’d make the code more spring-like, so add @Component to make it a Spring Bean, and the thread is not a singleton, and add the @Scope annotation. The refactored code looks like this:

@Resource(name = "uploadThreadPool")
private ExecutorService uploadThreadPool;

@PostMapping("/upload")
public RestResult upload(HttpServletRequest request) {
	  uploadThreadPool.submit((Runnable) SpringUtils.getBean("uploadThread", fileCopyList, fileNameList));
}
Copy the code

Injection of beans is covered in more detail in the next section. There is one more reason to write this: Non-Spring-managed beans generally cannot be injected directly into Spring beans. If we need to implement some business logic in UploadTask and maybe inject some Services, it is best to register UploadTask itself as a Spring Bean and use @AutoWired for automatic injection in the class.

One additional note: ** For thread-safety reasons, thread classes cannot inject beans directly using @AutoWired. ** Manual injection is usually done using springutils.getBean ().

Automatic scanning

Add the @ComponentScan annotation to the configuration class. By default, this annotation scans for all configuration classes in the package that the class belongs to. Special packages can be configured with the basePackages attribute. Spring scans all the beans and is ready for injection.

The Bean’s injection

For some beans that are not used directly, we do not need to inject them manually after registering with the Spring IoC container. For example, the three beans of Mybatis mentioned earlier. We only need to use according to the document, create Mapper interface, and use @mapper modification. When calling specific query methods, Mybatis will conduct Bean injection internally and Open a Session for database operation.

For the beans we need to use, we need to inject them into variables for use. There are two common ways to inject beans.

  1. Annotation injection (@autowired and @Resource).

@autoWired is the most common annotation for Bean injection, which is injected via byType by default. That is, if you have multiple beans of the same type, you cannot inject them directly via @AutoWired. In this case, the injected Bean needs to be qualified through @Qualifier. Or use @resource. @Resource is injected by byName, and the Bean name is specified on the annotation.

public class Foo{
  // Normal field injection
  @Autowired
	private AtomicInteger c;
  
  // Normal constructor injection
	private final AtomicInteger c;
  @Autowired
  public Foo(AtomicInteger c){this.c = c; }// add @qualifier to the ambiguity
  @Autowired
  @Qualifier("count")
	private AtomicInteger c;  
  
  // The ambiguity uses @resource directly (same as the previous one)
  @Resource("count")
	private AtomicInteger c;  
}

Copy the code
  1. Call the getBean method of the Application Context.

Annotation injection is recommended, but the getBean() method is used to inject the Bean in the special case of write silently. Take, for example, the multithreaded environment we talked about earlier. The implementation can be seen in appendix, implementing ApplicationContextAware, which can be packaged into a tool class.

Java version of SSM integration

Due to the advantages of Java Config, many framework integration efforts have opted out of XML configuration. For example, the ViewResolver used to configure Spring MVC in XML can be injected in Java like this:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
    @Bean
    public ViewResolver viewResolver(a){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/view/");
        resolver.setSuffix(".jsp");
        returnresolver; }}Copy the code

If you are interested, you can use Java Config to integrate. In fact, the things you need to configure are the same, but in Java, some need to register the corresponding Bean, and some need to implement the corresponding interface. From XML to Java, just a configuration language change, the real liberation of programmers, improve productivity is Spring Boot. Based on the idea that convention is more than Configuration, Auto Configuration reduces the Configuration of some default beans on a large scale.

Spring Boot Magic

Auto Configuration

Let’s take a look at what needs to be configured for Spring Boot-based SSM integration.

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: fur@6289
mybatis:
  type-aliases-package: com.fur.mybatis_web.mapper
  mapper-locations: classpath:mapping/*.xml
Copy the code

As anyone who has used Spring Boot knows, this is the Spring Boot configuration file application.yml. Just add the dependency in POM.xml and configure the necessary information in YML. CommonsMultipartResolver, SqlSessionFactoryBean, and so on do not need to be assembled manually.

The differences between Spring Boot and traditional SSM are:

  1. Add maven dependencies differently, bymybatisandmybatis-springTurned out to bemybatis-spring-boot-starter
  2. More @SpringBootApplication, less @ComponentScan

The Spring Boot Starter will also not be covered here, as long as it is a new dependency that includes auto-configuration and other required dependencies. In past dependencies introduced in Spring, the starter should be preferred if Boot implements the corresponding starter.

Let’s follow the source code to see how Spring Boot implements Auto Configuration. Take it Easy, we will not analyze the source line by line, just comb through the general process.

The @SpringBootApplication annotation is a composite annotation that includes @SpringBootConfiguration, @EnableAutoConfiguration, and @ComponentScan.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}
Copy the code
  • @SpringBootConfiguration, inside is an @Configuration, and as before, the class that the annotation decorates is a Spring Configuration class.
  • @ComponentScan, no stranger either. Integrated in the @SpringBootApplication annotation and added by default when creating the project, we don’t need to manually enable Bean scanning.
  • @enableAutoConfiguration. This is the key to AutoConfiguration. In short, with this annotation, Spring automatically scans for imported dependencies (jar packages in classpath) that require Auto Configuration and does the automatic Configuration according to YML.

Look @ EnableAutoConfiguration, found inside the import a AutoConfigurationImportSelector class, this class is basically dealing with Auto Configuration work at the core of the class.

//	AutoConfigurationImportSelector.java
/** * Return the auto-configuration class names that should be considered. By default * this method will load candidates  using {@link SpringFactoriesLoader} with
	 * {@link #getSpringFactoriesLoaderFactoryClass()}.
	 * @param metadata the source metadata
	 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
	 * attributes}
	 * @return a list of candidate configurations
	 */
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}
Copy the code

See on the method above, getCandidateConfigurations () function is the need to automatically configure relies on information, the core function of SpringFactoriesLoader loadFactoryNames () method to complete.

// SpringFactoriesLoader.java
public final class SpringFactoriesLoader {
  public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	/**
	 * Load the fully qualified class names of factory implementations of the
	 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
	 * class loader.
	 * @param factoryClass the interface or abstract class representing the factory
	 * @param classLoader the ClassLoader to use for loading resources; can be
	 * {@code null} to use the default
	 * @throws IllegalArgumentException if an error occurs while loading factory names
	 * @see #loadFactories
	 */
	public static List<String> loadFactoryNames(Class
        factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		returnloadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); }}Copy the code

Here is a brief description of the process, interested can see the source code of this class. The SpringFactoriesLoader scans all jars in the classpath and loads the contents of meta-INF/Spring.Factories. The “meta-INF”/” spring-boots-starter “factories exist with mybatis-spring-boot-autoconfigure.

As follows:

# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.spring.boot.autoconfigure.MybatisAutoConfigu rationCopy the code

Next, will get the key for org. Springframework. Boot. Autoconfigure. EnableAutoConfiguration value. This is Mybatis specific automatic configuration class. You can see in the package org. Mybatis. Spring. The boot. Under the autoconfigure MybatisAutoConfiguration and MybatisProperties, the former is automatically configured class, which is used to configure some parameters, Key value pairs corresponding to the mybatis node in YML. Here are some source code to see:

// MybatisAutoConfiguration.java
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration implements InitializingBean {
	@Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
   	/ / to omit
    returnfactory.getObject(); }}Copy the code

Here we see the familiar SqlSessionFactory, which was injected manually, via @conditionalonmissingBean, which will be automatically assembled for us when it doesn’t already exist in the Spring container.

The following flow chart simply describes the entire automatic configuration process.

Syntactic sugar

SpringBoot packages @ComponentScan and @SpringBootConfiguration with a large @SpringBootApplication composite annotation. Enables the startup class to be used directly as a configuration class and eliminates the Bean scan step.

conclusion

This article is by no means a complete Spring IoC tutorial, but rather an overview of usage and changes in Bean assembly along the way from Spring to Spring Boot. It can be seen that this is a process of continuous simplification and progress, but the core remains the same. Therefore, even if we switch to Spring Boot for development, we still need to use the relevant technical points of Spring IoC in some complex businesses, such as the scope of control beans, conditional beans and so on.

reference

  • Chapter 5 Spring Boot automatic configuration principle
  • Why is Spring Boot so popular?
  • Spring In Action

The appendix

SpringUtils.java

@Component
public class SpringUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) { SpringUtil.applicationContext = applicationContext; }}public static ApplicationContext getApplicationContext(a) {
        return applicationContext;
    }

    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

    public static <T> T getBean(String name, Object... args) {
        return(T) getApplicationContext().getBean(name, args); }}Copy the code

Method to get a Spring Bean in a non-Spring Bean

To get Spring beans from non-Spring beans, springutils.java needs to be modified to remove the @Component and manually inject the Spring Context without implementing the interface.

public class SpringUtils {
    private static ApplicationContext applicationContext;
    public static void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) { SpringUtil.applicationContext = applicationContext; }}// The rest of the code is shown above
}
Copy the code
public class SkeletonApplication {
    public static void main(String[] args) { ApplicationContext context = SpringApplication.run(SkeletonApplication.class, args); SpringUtils.setApplicationContext(context); }}Copy the code