1、背景
IoC(Inversion of Control)管制反转,IoC是一种通过形容来生成或获取对象的技术。每一个须要治理的对象称为Spring Bean,而Spring治理这些Bean的容器称为Spring IoC容器,也就是咱们所说的Spring应用程序上下文ApplicationContext。
IOC容器负责实例化、定位、配置应用程序中的对象及建设这些对象间的依赖。交由Spring容器对立进行治理,从而实现松耦合。
- 容器启动,创立和初始化IoC 容器
- 扫描包下所有class,通过反射解析class类的信息,包含注解信息
- 基于反射实例的对象,对其进行封装
- 将实例的对象放入汇合中保留
- 能够通过getBean获取汇合中对象
2、@SpringBootApplication
@SpringBootApplication开启了Spring的组件扫描和SpringBoot主动配置性能。实际上它是一个复合注解,蕴含3个重要的注解: @SpringBootConfiguration、@ComponentScan、@EnableAutoConfiguration。
SpringBoot晚期的版本中,须要在入口类同时增加这3个注解,但从SpringBoot1.2.0开始,只有在入口类增加@SpringBootApplication注解即可。咱们如果想了解它的含意,能够从通过剖析这3个注解开始。
2.1、@SpringBootConfiguration
@SpringBootConfiguration 是对 @Configuration 的简略封装,二者性能上没有太大差别。后者咱们比拟相熟,所以 @SpringBootConfiguration 能够了解为配置类的注解,会将以后类内申明的一个或多个以@Bean注解标记的办法的实例纳入到spring容器中。
2.2、@ComponentScan
@ComponentScan次要就是定义扫描的门路,从中找出标识了须要拆卸的类主动拆卸到spring的bean容器中。默认扫描门路是以后门路,因为启动类是在我的项目的根门路,所以启动类中的该注解,默认扫描的以后我的项目门路。
那么怎么找出须要拆卸的类呢?咱们晓得失常装载类,是要在配置类下注册Bean的。然而咱们日常应用的@Controller、@Service、@Repository、@Component、@Configuration却能够主动装载,就是因为@ComponentScan这个注解。@Component是个非凡的注解,它能够被@ComponentScan扫描装载,如果查看@Controller、@Service、@Repository、@Configuration等注解的源码就会发现,它们都继承@Component,因而同样也能够主动被扫描装载进容器中。
@ComponentScan注解的罕用属性包含:
- value/basePackages:定义扫描的门路。
- includeFilters:退出扫描门路下,没有继承@Component注解的类退出spring容器。
- excludeFilters:过滤出不必退出spring容器的类。
2.3、@EnableAutoConfiguration
我的项目开发时,常常通过maven等形式引入第三方jar包,那么也同样冀望可能将第三方jar包中的Bean也加载进以后我的项目的Bean容器中。后面说过@ComponentScan能够将定义扫描门路下的Bean装载进容器中,因而能够手动的在@ComponentScan注解的value属性中,填上所有第三方jar包的扫描门路。实践上是这样的,但应该没有人这么干吧?一个我的项目依赖几十个jar包,那要配置多少扫描门路?
因而个别有另外的两种办法来实现需求,个别咱们开发一个供调用的jar包利用(如starter),次要依靠自身的配置类来注入Bean(@Configuration+@Bean 或 @ComponentScan+@Component),因而只有保障第三方利用的配置类能加载进以后的Bean容器即可。
@Import 和 spring.factories
- 第三方利用,定义一个注解(个别是Enable结尾),通过 @Import 注解注入配置类。并且以后我的项目的配置类(倡议启动类)中加上该注解。
//配置类@Configuration@EnableConfigurationProperties({ SecurityProperty.class})@ComponentScan(basePackages = {"com.smec.mpaas.unicorn"})public class UnicornAutoConfig { }//注解@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Import({UnicornAutoConfig.class})public @interface EnableUnicorn {}
- 在第三方利用/META-INF 目录下创立 spring.factories 文件,并定义须要加载的配置类门路。以后我的项目的 @EnableAutoConfiguration
// mybatis 的 spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration=org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
咱们重点谈谈第二种,因为用到了@EnableAutoConfiguration,注解的定义如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class[] exclude() default {}; String[] excludeName() default {};}
@EnableAutoConfiguration实现的关键在于引入了AutoConfigurationImportSelector,其外围逻辑为selectImports办法,借助AutoConfigurationImportSelector,它能够帮忙SpringBoot利用将所有符合条件的@Configuration配置都加载到以后SpringBoot创立并应用的IoC容器。
xxxAutoConfiguration条件注解
当springboot扫描到@EnableAutoConfiguration注解时则会将spring-boot-autoconfigure.jar/META-INF/spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value里的所有xxxConfiguration类加载到IOC容器中。spring.factories文件里每一个xxxAutoConfiguration文件个别都会有上面的条件注解:
- @ConditionalOnClass : classpath中存在该类时起效
- @ConditionalOnMissingClass : classpath中不存在该类时起效
- @ConditionalOnBean : DI容器中存在该类型Bean时起效
- @ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
- @ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
- @ConditionalOnExpression : SpEL表达式后果为true时
- @ConditionalOnProperty : 参数设置或者值统一时起效
- @ConditionalOnResource : 指定的文件存在时起效
- @ConditionalOnJndi : 指定的JNDI存在时起效
- @ConditionalOnJava : 指定的Java版本存在时起效
- @ConditionalOnWebApplication : Web应用环境下起效
- @ConditionalOnNotWebApplication : 非Web应用环境下起效
AutoConfigurationImportSelector类的逻辑
SpringBoot中EnableAutoConfiguration实现的关键在于引入了AutoConfigurationImportSelector,其外围逻辑为selectImports办法,逻辑大抵如下:
- 从配置文件META-INF/spring.factories加载所有可能用到的主动配置类;
- 去重,并将exclude和excludeName属性携带的类排除;
- 过滤,将满足条件(@Conditional)的主动配置类返回;
3、Bean注册
IoC容器中次要就是治理Bean,那么在这之前,最重要的就是如何将自定义的Bean注册进IoC容器中。常见的有两种形式:(1)配置@Component注解,或者蕴含@Component的注解;(2)@Bean+@Configuration的组合。
3.1、@Component
@Component偏向于组件扫描和主动拆卸。前文说过了,次要是配合@ComponentScan注解,该注解会扫描指定门路下蕴含@Component注解的类,并将其注册进IoC容器。spring中有很多其余的注解也是依靠@Component实现的,如:@Controller、@Service、@Repository、@Configuration等。
注册进IoC容器后,Bean的名字取决于两种状况:
- 如果在注解中指定了value,则为value的值
- 如果在注解中没有指定value,默认是类名的第一个字母小写命名(如:类名 UserDepartMerge,bean名 userDepartMerge)
@Component只有一个注解属性:
- value:在代码中定义了别名,为bean起一个名字。
3.2、@Bean
@Bean润饰的办法,必须中配置类有@Configuration的类)中定义。在办法上打上注解@Bean,办法返回咱们要创立的对象,即示意申明该办法返回的实例是受Spring治理的Bean。
相比拟于@Component而言,@Bean的形式更加灵便。如果你要引入第三方库,可是如果你没有源代码,也就无奈在其上增加@Component,主动设置也就无从下手,但@Bean会返回一个被spring认可的Bean。
@Configurationpublic class BeanConfig { @Bean(initMethod = "initMethod",destroyMethod = "destroyMethod") public User user(){ return new User(); }}
注册进IoC容器后,Bean的名字取决于两种状况:
- 如果在注解中指定了name,则为name属性的值
- 如果没有指定name,默认是办法名(示例中即:user)
@Bean的注解属性解析:
- value/name:name和value两个属性是雷同的含意的,在代码中定义了别名。为bean起一个名字,如果默认没有写该属性,那么就应用办法的名称为该bean的名称。
- autowire:拆卸形式 有三个选项Autowire.NO (默认设置)/Autowire.BY_NAME/Autowire.BY_TYPE。指定bean的拆卸形式,依据名称和依据类型拆卸,个别不设置,采纳默认即可。
- initMethod:bean的初始化办法,间接指定办法名称(bean所对应类中,某个实例办法的名称)即可,不必带括号。
- destroyMethod:bean的销毁办法,在调用IoC容器的close()办法时,会执行到该属性指定的办法。不过,只是单实例的bean才会调用该办法,如果是多实例的状况下,不会调用该办法。
4、Bean注入
bean的齐全初始化过程比较复杂,简略来说:先通过构造方法生成一个实例,其次再初始化实例中属性的值。当bean曾经被加载进IoC容器后,在有些开发过程中如果须要用到一些bean的实例,就要对其注入。
spring倒退中,风行过三种注入形式,当有多个bean注入时,如果存在依赖关系,“谁先加载,谁后加载”有时就很重要。这和上面三种注入形式也有关系,一个经典的案例就是spring的循环依赖问题,这个前面会有独自一篇文章介绍,下文先打好根底。
4.1、基于字段注入
这是晚期最常见的注入形式,借助 @Autowired 或 @Resource 来注入,注解能够加在字段上。
byName 和 byType
spring在获取bean时,有两种依赖路径:byName 和 byType。之前在介绍bean的注册时,始终强调bean被加载进IoC容器后命名,byName就是依据bean的名字去IoC容器中寻找bean注入。而byType是依据被注册bean的类型来寻找的,类型即bean对应的类或接口。
@Resource 注解
@Resource注解的门路是 javax.annotation;
,它是属于J2EE
的注解。
@Resource默认是依照byName
形式注入的。
@Resource有两个重要属性:name,type:
- 如果只指定了
name
属性,则依照byName
形式注入; - 如果只指定了
type
属性,则依照byType
形式注入; - 如果两个属性都没指定,则默认依照
byName
形式注入; - 如果两个属性都指定了,则要求依照
byName
和byType
两种形式都匹配,才会注入。
示例代码:
@Componentpublic class User { @Resource(name = "type",type = Type.class) private Type type;}
@Autowirec 注解
@Autowired注解的门路是org.springframework.beans.factory.annotation;
,它是属于Spring
的注解。
@Autowired并不能指定注入形式是byName还是byType,默认只能依照byType
形式注入。如果想通过byName
形式注入,得配合@Qualifier
注解一起应用:
- 默认依照
byType
形式注入; - 配合
@Qualifier
注解应用,能够实现byName
形式注入; - 如果依照
byType
匹配到多个bean,则会主动将属性名
视为bean的名称,通过byName
形式匹配注入。
示例代码中,有个Action的接口,两个实现类Human、Dog。因为Action类型能匹配两个bean,依据属性名dog会匹配到dog:
//Action@Componentpublic interface Action { String run();}//Human@Componentpublic class Human implements Action{ @Override public String run() { return "Human run!"; }}//Dog@Componentpublic class Dog implements Action{ @Override public String run() { return "Dog run!"; }}//Controller@RestControllerpublic class DemoController { @Autowired private Action dog; @GetMapping("/demo") public String demo() { return dog.run(); }}
4.2、基于setter注入
基于setter的注入,就是增加属性的setter办法。setter办法上的@Autowired注解能够加,也能够不加,不会影响性能实现,加了可能只是有助于辨别其余一般的setter办法。
@Componentpublic class User { private Type type; //@Autowired public void setType(Type type) { this.type = type; }}
上文代码中,User 和 Type 两个类都加上了@Component的注解,即都曾经被注入IoC容器中。User在创立实例的时候只执行本身的构造方法即可,但在初始化type属性数据时会调用set办法,会注入Type类的实例。所以会先执行User构造方法,再执行Type构造方法。
相较于字段注入
的形式来说,改善了局部问题,但不显著。而且如果须要注入的bean多,settter办法就要写一堆,代码构造很蹩脚。所以个别更举荐结构器注入
。
4.3、结构器注入
下列代码是不是很相熟?Angular中依赖注入组件,就是这种写法。在User创立实例时,就依赖于Type的实例。因而先执行Type的构造方法,再执行User的构造方法。
@Componentpublic class User { private final Type type; public User(Type type) { this.type = type; }}
这个结构器注入的形式啊,可能保障注入的组件不可变(final),并且确保须要的依赖不为空。此外,结构器注入的依赖总是可能在返回客户端(组件)代码的时候保障齐全初始化的状态。
4.4、比拟
基于字段注入
的形式,会使代码看起来更整洁,然而对于空指针等问题无奈及时防止。基于结构器注入
的形式,貌似是前者短板的最好代替。我不倡议思考setter注入形式。
咱们当在字段上应用@Autowired时,IDE个别都会给咱们一个正告 Field injection is not recommended
,不举荐应用字段注入。这个提醒是spring framerwork 4.0当前开始呈现的,spring 4.0开始就不举荐应用属性注入,改为举荐结构器注入
和setter注入
。
@Autowired因为默认是byType
注入,常常会因为注入的类型是接口,在初始化属性数据时,却发现IoC容器中并没有加载实现类。而后就呈现了空指针的谬误,包含还有可能暗藏的循环依赖谬误。因而在很多的开发标准中,都举荐用结构器注入,代替@Autowired
。
但为啥对于在字段上应用@Resource,IDE不正告呢?集体认为毕竟@Resource的属性很多,能够定制化实现很多性能,这些灵便的性能,结构器注入形式代替不了。不像@Autowired那么鸡肋。
5、Aware属性注入
Spring中有很多继承于aware
中的接口,上面列出了一部分:
- ApplicationContextAware
- ApplicationEventPublisherAware
- BeanClassLoaderAware
- BeanFactoryAware
- BeanNameAware
- EnvironmentAware
- ImportAware
- MessageSourceAware
- NotificationPublisherAware
- ResourceLoaderAware
- ServletConfigAware
- ServletContextAware
aware
,英文翻译是“晓得的,意识到的”。能够了解成,如果实现了这些 xxxAware
的接口,就能感知到被润饰的xxx
属性。如:实现BeanNameAware接口,能获取到以后bean的名称。实现ApplicationContextAware接口,能获取到ApplicationContex。
这些接口中都有且只有一个去掉接口名中的Aware后缀的设置办法,例如BeanNameAware
接口只有一个void setBeanName(String var1)
的办法。
所以能够了解成,这些Aware接口的作用,是让开发者实现相应的 属性注入
。如下例中,通过单例模式实现一个获取全局ApplicationContext的办法,能够手动获取IoC容器和容器中的bean。
@Componentpublic class ApplicationContextAwareImpl implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext curApplicationContext) { if (applicationContext == null) { applicationContext = curApplicationContext; } } public static ApplicationContext getApplicationContext() { return applicationContext; }}