共计 8756 个字符,预计需要花费 22 分钟才能阅读完成。
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.factories
org.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。
@Configuration
public 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
两种形式都匹配,才会注入。
示例代码:
@Component
public 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
@Component
public interface Action {String run();
}
//Human
@Component
public class Human implements Action{
@Override
public String run() {return "Human run!";}
}
//Dog
@Component
public class Dog implements Action{
@Override
public String run() {return "Dog run!";}
}
//Controller
@RestController
public class DemoController {
@Autowired
private Action dog;
@GetMapping("/demo")
public String demo() {return dog.run();
}
}
4.2、基于 setter 注入
基于 setter 的注入,就是增加属性的 setter 办法。setter 办法上的 @Autowired 注解能够加,也能够不加,不会影响性能实现,加了可能只是有助于辨别其余一般的 setter 办法。
@Component
public 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 的构造方法。
@Component
public 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。
@Component
public 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;}
}