目录html
本文首发于个人我的博客,Bean装配,从Spring到Spring Boot ,欢迎访问!java
本文旨在厘清从Spring 到Spring Boot过程当中,Bean装配的过程。mysql
自从用上Spring Boot,真的是一直用一直爽,已经彻底没法直视以前Spring的代码了。约定大于配置的设计理念,使得其不须要太多的配置就能开箱即用。可是因为其便捷性,也就意味着掩盖了许多细节的部分,使得直接学习Spring Boot的开发者只会用,而不清楚内部的实现流程。最近恰好有空,从新回顾了一下Spring的相关内容,而且梳理了有关于Bean装配的一些用法,描述从过去的Spring开发,到如今的Spring开发 Boot在Bean装配上的变化和进步。程序员
在学习初期,我想每一个人都会去看一些博客,例如“Spring Spring MVC Mybatis整合”。一步一步整合出一个ssm项目。那个时候的我是没有什么概念的,彻底就是,跟着步骤走,新建配置文件,把内容贴进来,而后run,一个基本的脚手架项目就出来了。回顾一下,基本是如下几步:web
1. 创建maven web项目,并在pom.xml中添加依赖。
2. 配置web.xml,引入spring-.xml配置文件。
3. 配置若干个spring-.xml文件,例如自动扫包、静态资源映射、默认视图解析器、数据库链接池等等。
4. 写业务逻辑代码(dao、services、controller)spring
后期可能须要用到文件上传了,再去xml中配置相关的节点。在开发中,基本也是遵循一个模式——三层架构,面向接口编程。类若是是Controller的就加一个@Controller;是Services的就加一个@Services注解,而后就能够愉快的写业务逻辑了。sql
Spring是什么?那个时候的理解,像这样子配置一下,再加上几个注解,就是Spring。或者说,Spring就是这么用的。数据库
随着学习的深刻,对Spring就有更深的理解了。在Spring当中,一切皆为Bean。能够说Bean是组成一个Spring应用的基本单位。Spring(这里是狭义的概念,指Spring Core)中最核心部分就是对Bean的管理。express
<?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>
让咱们再次看一下Spring MVC的配置文件,除了一些参数外,还有两个bean节点,注入InternalResourceViewResolver来处理视图,注入CommonsMultipartResolver来处理文件上传。这个时候,若是须要集成Mybatis一块儿工做,相似的,注入相关的Bean就能够了。Mybatis最核心的Bean就是SqlSessionFactory,经过建立Session来进行数据库的操做。在不使用Spring时,能够经过加载XML,读入数据库信息,进行建立。编程
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
显然,上面的代码是不符合Spring的思想的。为了达到松耦合,高内聚,尽量不直接去new一个实例,而是经过DI的方式,来注入bean,由Spring IoC容器进行管理。Mybatis官方给到一个MyBatis-Spring包,只需添加下面的Bean就能够组织Mybatis进行工做了(建立sqlSessionFactory,打开Session等工做),关于Mybatis的内容,这里不展开了。
<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>
那么,如今咱们就知道了,经过XML配置Spring,集成各类框架的实质,就是Bean装配的。每个框架都是由N多个Bean构成的,若是须要使用它,就必须根据框架的要求装配相应的Bean。装配成功的Bean由Spring IoC容器统一管理,就可以正常进行工做。
具体Bean的装配方式,发展到如今也已经有不少种了,从过去的XML到Java Config,再到如今Spring Boot的Auto Configuration,是一种不断简化,不断清晰的过程。
Bean装配通常分为三步:注册、扫描、注入。
XML配置Bean早已成为明日黄花了,目前更常见的是使用Java Config和注解来进行Bean的装配。固然,偶尔也能看到它的身影,例如用于ssm框架的集成的spring-*.xml。
Java Config的优点以下:
所以下面主要讲都是基于Java的配置方法。基本流程以下:
// 注册 @Configuration public class BeanConfiguration { @Bean public AtomicInteger count() { return new AtomicInteger(); } } //或者 @Componment public class Foo{} // 扫描 @ComponentScan(basePackages={}) @Configuration public class BeanConfiguration {} // 注入 @Autowired private AtomicInteger c;
下面详细展开。
Java Config注册Bean,主要分为两类,注册非源码的Bean和注册源码的Bean。
非源码的Bean,指的是咱们没法去编辑的代码,主要是引入外部框架或依赖,或者使用Spring的一些Bean。这些Bean的配置通常采用Java文件的形式进行声明。
新建一个使用@Configuration
修饰的配置类,而后使用@Bean
修饰须要建立Bean的方法,具体的可指定value和name值(二者等同)。示例以下:
@Configuration public class BeanConfiguration { @Scope("prototype") @Bean(value = "uploadThreadPool") public ExecutorService downloadThreadPool() { return Executors.newFixedThreadPool(10); } }
其中须要注意的是:
@Scope("prototype")
。对于这个例子而言,默认建立的线程池是单例的,在应用的任何一个地方注入后使用,用到的都是同一个线程池(全局共享);加上@Scope("prototype")
后,在每个Controller中分别注入,意味着,每个Controller都拥有各自的线程池,各自的请求会分别提交到各自的线程池中。@Primary
,标出首选的Bean。源码的Bean,指的是咱们本身写的代码,通常不会以@Bean的形式装配,而是使用另一系列具备语义的注解。(@Component、@Controller、@Service、@Repository)添加这些注解后,该类就成为Spring管理的组件类了,列出的后三个注解用的概率最高,基本已经成为样板注解了,Controller类添加@Controller,Service层实现添加@Service。
下面展现一个例子,经过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); // 若是直接传入MutiPartFile,文件会没法存入,由于对象传递后spring会将tmp文件缓存清楚 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)); } } }
接着上面的线程池讲,这里咱们实现了一个task,用来处理异步上传任务。在传统JUC中,咱们通常会这么写代码:
private ExecutorService uploadThreadPool = Executors.newFixedThreadPool(10); uploadThreadPool.submit(new UploadTask(fileCopyList, fileNameList));
在Spring中,我以为就应该把代码写的更Spring化一些,所以添加@Component使之成为Spring Bean,而且线程非单例,添加@Scope注解。重构后的代码以下:
@Resource(name = "uploadThreadPool") private ExecutorService uploadThreadPool; @PostMapping("/upload") public RestResult upload(HttpServletRequest request) { uploadThreadPool.submit((Runnable) SpringUtils.getBean("uploadThread", fileCopyList, fileNameList)); }
Bean的注入在下节会仔细讲。其实这样写还有零一个缘由,非Spring管理的Bean通常是没法直接注入Spring Bean的。若是咱们须要在UploadTask中实现一些业务逻辑,可能须要注入一些Services,最好的作法就是讲UploadTask自己也注册成Spring Bean,那么在类中就可以使用@Autowired进行自动注入了。
额外须要注意的是:因为线程安全的一些缘由,线程类是没法直接使用@Autowired注入Bean的。通常会采用SpringUtils.getBean()
手动注入。
在配置类上添加@ComponentScan
注解。该注解默认会扫描该类所在的包下全部的配置类,特殊的包能够配置basePackages
属性。Spring扫描到全部Bean,待注入就可使用了。
对于一些不直接使用的Bean,注册到Spring IoC容器后,咱们是不须要手动去注入的。例如前面提到Mybatis的三个Bean。咱们只须要根据文档进行使用,建立Mapper接口,并使用@Mapper修饰,在调用具体的查询方法时,Mybatis内部会进行Bean的注入,Open一个Session进行数据库的操做。
对于咱们须要使用到的Bean,就须要注入到变量中进行使用。经常使用Bean注入的方式有两种。
1.注解注入(@Autowired和@Resource)。
@Autowired
是Bean注入最经常使用的注解,默认是经过byType的方式注入的。也就是说若是包含多个相同类型的Bean,是没法直接经过@Autowired注入的。这个时候须要经过@Qualifier限定注入的Bean。或者使用@Resource。@Resource
是经过byName的方式注入,直接在注解上标明Bean的name便可。
public class Foo{ // 正常字段注入 @Autowired private AtomicInteger c; // 正常构造器注入 private final AtomicInteger c; @Autowired public Foo(AtomicInteger c){this.c = c;} // 歧义加上@Qualifier @Autowired @Qualifier("count") private AtomicInteger c; // 歧义直接使用@Resource(与前一种等同) @Resource("count") private AtomicInteger c; }
2.调用Application Context的getBean方法。
推荐使用注解注入,可是在默写特殊状况下,须要使用getBean()方法来注入Bean。例如以前讲到的多线程环境。具体实现可见附录,实现ApplicationContextAware,能够封装成一个工具类。
因为Java Config的优点,框架集成工做不少选择不用XML配置了。例如以前在xml中配置Spring MVC的ViewResolver,在Java中就能够这样去注入:
@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; } }
有兴趣的,能够本身去使用Java Config集成一下,其实须要配置的东西都是一致的,只是在Java中,有的是要注册对应的Bean,有的须要实现对应的接口罢了。由XML到Java,仅仅只是配置语言的改变,真正解放程序员,提升生产力是Spring Boot。基于约定大于配置的思路,经过Auto Configuration,大规模减小了一些缺省Bean的配置工做。
接下来咱们看一下,基于Spring Boot的SSM集成须要配置的内容。
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
用过Spring Boot的都知道,上面的是Spring Boot的配置文件application.yml
。只须要在pom.xml添加对应依赖,在yml里配置必要的信息。(框架再智能也不可能知道咱们的数据库信息吧😄)以前看到的CommonsMultipartResolver、SqlSessionFactoryBean等不再须要手动去装配了。
Spring Boot和传统SSM的不一样之处在于:
mybatis
和mybatis-spring
变成了mybatis-spring-boot-starter
这里也不对Spring Boot Starter进行介绍了,只要知道这就是一个新的dependency,包含自动配置和其余须要的依赖就好了。在过去Spring中引入的依赖中,若是Boot实现了对应的starter,应该优先使用Starter。
下面咱们就跟一下源码,了解一下Spring Boot究竟是如何实现Auto Configuration的。Take it easy,咱们不会逐行去分析源码,只是梳理下大概的流程。
顺着思路,首先看到的是@SpringBootApplication注解中的内容,显然这是一个复合注解,主要包含@SpringBootConfiguration、@EnableAutoConfiguration和@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 {}
再看@EnableAutoConfiguration,发现内部import一个AutoConfigurationImportSelector
类,这个类基本上就是处理Auto Configuration工做的核心类了。
// 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; }
看到上面的这个方法,getCandidateConfigurations()
的做用就是获取须要自动配置的依赖信息,核心功能由SpringFactoriesLoader的loadFactoryNames()
方法来完成。
// 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()); } }
这里简单说一下流程,有兴趣的能够看下这个类的源码。SpringFactoriesLoader会扫描classpath下的全部jar包,并加载META-INF/spring.factories下的内容。咱们以mybatis-spring-boot-starter为例,里面有个依赖mybatis-spring-boot-autoconfigure,能够看到存在META-INF/spring.factories。
内容以下:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
接下来,会获取到key为org.springframework.boot.autoconfigure.EnableAutoConfiguration
的值。也就是Mybatis具体的自动配置类。能够看到在包org.mybatis.spring.boot.autoconfigure
下,有MybatisAutoConfiguration
和MybatisProperties
,前者是自动配置类,后者是用于配置的一些参数,对应在yml中的mybatis节点下的键值对。下面贴一些源码看看:
// MybatisAutoConfiguration.java @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); // 省略 return factory.getObject(); } }
这里就看到了熟悉的SqlSessionFactory,以前咱们须要手动注入,如今经过@ConditionalOnMissingBean
,当Spring容器中不存在这个Bean时,就会为咱们自动装配。
下面的流程图就是简单描述了整个自动配置的流程。
其实就是复合注解啦,除了约定大于配置的自动装配外,Spring Boot经过一个大的复合注解@SpringBootApplication,把@ComponentScan和@SpringBootConfiguration也包在里面,使得启动类直接就能够做为配置类使用,而且减小了Bean扫描这一步。
本文不算是一篇完整的Spring IoC教程,只是梳理了一下从Spring 到Spring Boot一路走来,在Bean装配上的用法和改变。能够看到这是一个不断简化,不断进步的过程,可是核心依然不变,所以即便当下转到Spring Boot 进行开发,在遇到一些复杂的业务上,任然须要用到Spring IoC相关的技术点,例如控制Bean的做用域,条件化Bean等等。
@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); } }
在非Spring Bean中获取Spring Bean,须要改造SpringUtils.java,去掉@Component,而且不须要实现接口了,手动注入Spring Context。
public class SpringUtils { private static ApplicationContext applicationContext; public static void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (SpringUtil.applicationContext == null) { SpringUtil.applicationContext = applicationContext; } } // 余下代码如上 }
public class SkeletonApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(SkeletonApplication.class, args); SpringUtils.setApplicationContext(context); } }