Since using Spring Boot, I have been using Spring Boot all the time. I can't look directly at Spring's code. The design concept that convention is greater than configuration enables it to be used out of the box without too much configuration. But because of its convenience, it means covering up many details, making developers who directly learn Spring Boot only use it, but not know the internal implementation process. Recently, I just have time to review Spring, and comb some usage of Bean Assembly, describe the changes and progress of Spring Development Boot in Bean Assembly from Spring Development in the past to Spring Development Boot in the present.
From SSM Integration to Bean Assembly
At the beginning of the study, I think everyone will visit some blogs, such as "Spring MVC Mybatis Integration". Step by step, an ssm project is integrated. At that time, I did not have any concepts. I just followed the steps to create a new configuration file, paste in the content, and then run, a basic scaffolding project came out. In retrospect, there are basically the following steps:
- Create a maven web project and add dependencies to pom.xml.
- Configure web.xml and introduce spring-*.xml configuration file.
- Configure several spring-*.xml files, such as automatic package sweeping, static resource mapping, default view parser, database connection pool, and so on.
- Write business logic code (dao, services, controller)
Later you may need to upload files and configure related nodes in xml. In the process of development, it basically follows a pattern - three-tier architecture, which is oriented to interface programming. If the class is Controller, add an @Controller; if it is Services, add an @Services annotation, and then you can write business logic happily.
What is Spring? At that time, the understanding, like this configuration, plus a few annotations, is Spring. That's how Spring works.
With the deepening of learning, Spring has a deeper understanding. In Spring, everything is Bean. Beans can be said to be the basic unit of a Spring application. The core part of Spring (in this narrow sense, Spring Core) 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>
Let's take another look at the configuration file of Spring MVC. In addition to some parameters, there are two bean nodes that inject Internal ResourceViewResolver to process views and Commons MultipartResolver to process 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 SqlSession Factory, which creates Session to operate the database. When Spring is not used, it can be created by loading XML and Reading database information.
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Obviously, the above code is not in line with Spring's thinking. In order to achieve loose coupling and high cohesion, we try not to go directly to a new instance, but to inject beans through DI, which is managed by Spring IoC container. Mybatis is officially given a MyBatis-Spring package. Just add the following beans to organize Mybatis to work (create SQL Session Factory, open Session, etc.). The content of Mybatis 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>
So now we know that the essence of integrating various frameworks by configuring Spring through XML is Bean assembly. Each framework is composed of N beans. If you need to use it, you must assemble the corresponding beans according to the requirements of the framework. The beans assembled successfully are managed by the Spring IoC container and can work properly.
Bean Assembly
There are many ways of assembling concrete beans. From XML to Java Config in the past to AutoConfiguration in Spring Boot now, it is a process of continuous simplification and clarity.
Bean assembly is generally divided into three steps: registration, scanning and injection.
From XML to Java Config
XML configuration beans have long been a thing of the past, and Java Config and annotations are more commonly used to assemble beans. Of course, occasionally it can be seen, such as spring-*.xml for integration of ssm frameworks.
Java Config has the following advantages:
- Java is type-safe, and the compiler will give direct feedback if problems arise during assembly.
- XML configuration files will grow larger and larger with the increase of configuration, and it is not easy to maintain and manage.
- Java Config is easier to refactor and search beans.
So the following is mainly about Java-based configuration methods. The basic process is as follows:
// register @Configuration public class BeanConfiguration { @Bean public AtomicInteger count() { return new AtomicInteger(); } } //perhaps @Componment public class Foo{} // scanning @ComponentScan(basePackages={}) @Configuration public class BeanConfiguration {} // injection @Autowired private AtomicInteger c;
Let's expand in detail below.
Java Config registered beans, mainly divided into two categories, registered non-source beans and registered source beans.
Register non-source beans
Non-source beans refer to code that we can't edit, mainly introducing external frameworks or dependencies, or using Spring's beans. The configuration of these beans is generally declared in the form of Java files.
Create a new configuration class modified with @Configuration, and then use @Bean to modify the method that needs to create beans, specifying the value and name values (both equal). Examples are as follows:
@Configuration public class BeanConfiguration { @Scope("prototype") @Bean(value = "uploadThreadPool") public ExecutorService downloadThreadPool() { return Executors.newFixedThreadPool(10); } }
It should be noted that:
- Bean s are created as singletons by default, and only one instance is created throughout the application. If necessary, each injection creates a new instance with the annotation @Scope("prototype"). For this example, the thread pool created by default is singleton. When injected anywhere in the application, the same thread pool (global sharing) is used; when @Scope("prototype") is added, it is injected into each Controller separately, which means that each Controller has its own thread pool. Each request is submitted to its own thread pool.
- When multiple beans of the same type are injected, name or value values are manually specified to distinguish them. Or by @Primary, mark the preferred Bean.
Register Source Bean
Source Beans, which refer to our own code, are generally not assembled in the form of @Bean, but use a series of other semantic annotations. After adding these annotations (@Component,@Controller,@Service,@Repository), this class becomes a Spring-managed component class. The last three annotations listed have the highest probability of being used. They have basically become template annotations. Controller class adds @Controller and Service layer implements the addition of @Service.
Here's an example of declaring your own encapsulated classes 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 in MutiPartFile directly, the file will not be saved because spring will cache the tmp file clearly after the object is passed in. public UploadThread(List<ByteArrayInputStream> files, List<String> fileNameList) { this.files = files; this.fileNameList = fileNameList; } @Override public void run() { for (int i = 0; i < files.size(); ++i) { String fileName = fileNameList.get(i); String filePath = FileUtils.generatePath(prop.getImageSavePath(),fileName); FileUtils.save(new File(filePath), files.get(i)); } } }
Then the thread pool above says that we have implemented a task to handle asynchronous upload tasks. In traditional JUC, we usually write code like this:
private ExecutorService uploadThreadPool = Executors.newFixedThreadPool(10); uploadThreadPool.submit(new UploadTask(fileCopyList, fileNameList));
In Spring, I think I should write more Spring code, so add @Component to make it Spring Bean, and thread is not a singleton, add @Scope annotation. The reconstructed code is as follows:
@Resource(name = "uploadThreadPool") private ExecutorService uploadThreadPool; @PostMapping("/upload") public RestResult upload(HttpServletRequest request) { uploadThreadPool.submit((Runnable) SpringUtils.getBean("uploadThread", fileCopyList, fileNameList)); }
Bean injection will be discussed in detail in the next section. In fact, there is another reason for this. Beans that are not managed by Spring generally cannot be injected directly into Spring Beans. If we need to implement some business logic in UploadTask, we may need to inject some Services. The best way to do this is to register UploadTask itself as Spring Bean. Then we can use @Autowired to inject automatically in the class.
Additionally, it should be noted that threading classes are not able to inject beans directly using @Autowired due to some thread security reasons. SpringUtils. get Bean () is usually injected manually.
Automatic scanning
Add the @ComponentScan annotation to the configuration class. By default, the annotation scans all configuration classes under the package in which the class resides, and special packages can configure the basePackages property. Spring scans all beans and is ready for injection.
Bean injection
For some beans that are not directly used, we do not need to inject them manually after registering with the Spring IoC container. For example, the three Bean s of Mybatis mentioned earlier. We only need to create Mapper interface according to the document, and use @Mapper decoration. When calling specific query methods, Mybatis will inject beans internally, and Open a Session will operate the database.
For beans that we need to use, we need to inject them into variables for use. There are two common ways to inject beans.
- Annotation injection (@Autowired and @Resource).
@ Autowire is the most commonly used annotation for Bean injection, which is injected by default through byType. That is, if you have multiple beans of the same type, you cannot inject them directly through @Autowire. At this point, you need to qualify the injected beans by @Qualifier. Or use @Resource. @ Resource is injected by byName, and the name of the Bean can be marked directly 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;} // Ambiguity plus @Qualifier @Autowired @Qualifier("count") private AtomicInteger c; // Ambiguity uses @Resource directly (the same as the previous one) @Resource("count") private AtomicInteger c; }
- Call the getBean method of Application Context.
Annotation injection is recommended, but in special cases of steganography, get Bean () method is needed to inject beans. For example, the multithreaded environment mentioned earlier. Specific implementation can be seen in the appendix, the implementation of ApplicationContextAware, can be encapsulated into a tool class.
Java Version of SSM Integration
Because of the advantages of Java Config, many frameworks have been integrated without XML configuration. For example, the ViewResolver of Spring MVC was previously configured in XML to inject in Java as follows:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Bean public ViewResolver viewResolver(){ InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/view/"); resolver.setSuffix(".jsp"); return resolver; } }
Interested, you can use Java Config integration, in fact, the need to configure things are consistent, but in Java, some are to register the corresponding beans, some need to implement the corresponding interface. From XML to Java, it's just a change in configuration language. Spring Boot really liberates programmers and improves productivity. Based on the idea that convention is greater than configuration, the configuration of default beans is greatly reduced through Auto Configuration.
Spring Boot Magic
Auto Configuration
Next, let's look at what Spring Boot-based SSM integration needs to configure.
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
As anyone who has used Spring Boot knows, above is Spring Boot's configuration file application.yml. Simply add corresponding dependencies to pom.xml and configure the necessary information in yml. The Commons MultipartResolver, SqlSession FactoryBean, etc. you saw before, no longer need to assemble manually.
Spring Boot differs from traditional SSM in that:
- Additional maven dependencies vary from mybatis and mybatis-spring to mybatis-spring-boot-starter
- More @SpringBootApplication, less @ComponentScan
Spring Boot Starter is not introduced here, as long as you know that this is a new dependency, including automatic configuration and other dependencies needed. In the dependencies introduced in Spring in the past, Starter should be preferred if Boot implements the corresponding starter.
Let's follow the source code to see how Spring Boot actually implements Auto Configuration. Take it easy, we won't analyze the source code line by line, just sort out the general process.
Along the way, the first thing you see is the content of the @SpringBootApplication annotation, which is obviously a composite annotation, mainly including @SpringBootConfiguration, @Enable AutoConfiguration 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 {}
- @ SpringBootConfiguration, which is internally an @Configuration, is a Spring configuration class, as before.
- @ ComponentScan is no stranger. Integrating in the @SpringBootApplication annotation, the project is added by default when it is created, so we don't need to manually open the Bean scan.
- @ Enable Auto Configuration, which is the key to Auto Configuration. In short, with this annotation, Spring automatically scans the items that require Auto Configuration in the introduced dependencies (jar packages under classpath) and automatically configures them according to yml.
Looking at @EnableAutoConfiguration, we find that the internal import of an Auto Configuration ImportSelector class is basically the core class that handles AutoConfiguration work.
// 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; }
Seeing the above method, getCandidate Configurations () is used to obtain dependency information that needs to be automatically configured. The core function is accomplished by Spring Factories Loader's loadFactoryNames() method.
// 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(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } }
Here is a brief description of the process, interested can look at the source code of this class. Spring Factories Loader scans all jar packages under classpath and loads the contents under META-INF/spring.factories. Let's take mybatis-spring-boot-starter as an example. There is a dependence on mybatis-spring-boot-autoconfigure. You can see the existence of META-INF/spring.factories.
The contents are as follows:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
Next, you get the value whose key is org. spring framework. boot. autoconfigure. EnableAutoConfiguration. That is Mybatis specific automatic configuration class. You can see that under package org.mybatis.spring.boot.autoconfigure, there are Mybatis AutoConfiguration and Mybatis Properties, the former is an automatic configuration class, and the latter is some parameters for configuration, corresponding to key pairs under mybatis node in yml. Here are some source codes 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); // ellipsis return factory.getObject(); } }
Here you see the familiar SqlSession Factory, which we need to inject manually before, and now through @ConditionalOnMissingBean, when this Bean does not exist in the Spring container, it will automatically assemble for us.
The following flow chart simply describes the whole process of automatic configuration.
Syntactic sugar
In fact, it's compound annotations. In addition to the automatic assembly of conventions larger than configurations, Spring Boot packages @ComponentScan and @Spring Boot Configuration in a large composite annotation @Spring Boot Application, making the boot class directly available as a configuration class, and reducing the Bean Scan step.
summary
This article is not a complete Spring IoC tutorial, but a review of the usage and changes in Bean assembly along the way from Spring to Spring Boot. As you can see, this is a process of continuous simplification and progress, but the core remains unchanged, so even if you go to Spring Boot for development right now, you still need to use Spring IoC related technical points in some complex business, such as controlling the scope of beans, conditional beans and so on.
Reference
- Chapter 5 Principles of Spring Boot Automatic Configuration
- Why is Spring Boot so hot?
- Spring Actual Warfare
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() { 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); } }
Method of Getting Spring Beans in Non-Spring Beans
To get Spring Beans in non-Spring Beans, you need to modify Spring Utils. java, remove @Component, and do not need to implement interfaces, manually inject Spring Context.
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 as follows }
public class SkeletonApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(SkeletonApplication.class, args); SpringUtils.setApplicationContext(context); } }