1. 注解驱动开发
当我们还在使用Spring、SpringMVC、Mybatis三大框架来整合开发的时候,我们会写大量的xml文件来进行配置。然而在Springboot和SpringCloud兴起之后,学习Spring的注解驱动及其原理那将会是非常有必要的了,因为在Springboot和SpringCloud里面会使用到大量的注解来进行配置;当我们熟练掌握了Spring的注解驱动,那当我们在学习Springboot和SpringCloud框架的时候,那将会更加的轻松自如。
1.1 环境准备
这里使用spring-context
来演示工程demo,pom 依赖如下:
1 | <properties> |
2. 组件注册
创建一个Person
类:
1 |
|
2.1 xml方式注册
创建一个 xml 配置文件,用于注册这个 Person 对象:
1 |
|
使用ClassPathXmlApplicationContext
加载这个配置文件:
1 |
|
输出结果:
Person(name=zhangsan, age=20)
2.2 注解方式注册
使用注解的方式进行加载 bean,就需要编写一个配置类,等同于 xml 配置文件:
@Bean
:相当于xml配置文件中的<bean>
标签,告诉容器注册一个bean之前xml文件中
<bean>
标签有bean的class类型,那么现在注解方式的类型当然也就是返回值的类型之前xml文件中
<bean>
标签有bean的id,现在注解的方式默认用的是方法名来作为bean的id,也就是说当前注解的方法名是person()
,那么id 就是person
,如果方法名是person01()
,那么id 就是person01
1 |
|
如果通过@Bean
注解的value
属性显式指定 bean 在 IOC 容器的id,那么就会以这个指定的id 为准注入容器中:
1 |
|
使用AnnotationConfigApplicationContext
加载这个配置类:
1 |
|
输出结果:
Person(name=king, age=22)
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
appConfig
person
从输出结果可以看到:配置类也作为了 bean 注入容器中,当使用@Bean 注解显示指定id 的时候,注册到容器中就是这个指定的id。
2.3 组件自动扫描组件
在xml文件配置的方式,我们可以这样来进行配置:
1 | <!-- 包扫描、只要标注了@Controller、@Service、@Repository,@Component --> |
以前是在xml配置文件里面写包扫描,现在我们可以在配置类里面写包扫描:
1 |
|
@ComponentScan
注解会扫描当前包及其子包的所有带@Component
、@Controller
、@Service
、@Repository
注解的组件。
1 |
|
输出结果:
appConfig2
appConfig
book
bookController
bookDao
bookService
person
在 @ComponentScan
这个注解上,也是可以指定要排除哪些包或者是只包含哪些包来进行管理:里面传是一个Filter[]数组:
1 | // @ComponentScan 源码 |
xml 版本:
1 | <!--包扫描,只要注解了@Component,@Controller等会被扫描--> |
注解版:
1 |
|
其中Filter的type的类型有:
FilterType.ANNOTATION 按照注解
FilterType.ASSIGNABLE_TYPE 按照类型 FilterType.REGEX 按照正则
FilterType.ASPECTJ 按照ASPECJ表达式规则
FilterType.CUSTOM 使用自定义规则
excludeFilters = Filter[] 指定在扫描的时候按照什么规则来排除脑哪些组件
includeFilters = Filter[] 指定在扫描的时候,只需要包含哪些组件
其中自定义规则类型过滤器需要实现TypeFilter接口:
1 |
|
FilterType.CUSTOM
过滤器过滤:
1 |
|
输出结果:
—>org.woodwhale.king.code01.dao.BookDao
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
appConfig2
bookDao
FilterType.ANNOTATION
注解过滤:
1 |
|
输出结果:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
appConfig2
bookService
我们还可以用 @ComponentScans
来定义多个扫描规则:里面是@ComponentScan
规则的数组(但是这样写的话,就必须要 java8 及以上的支持):
1 |
|
2.4 bean 的作用域
spring 默认将所有的bean 以单例的形式注入,在容器初始化之前就会创建这个实例,直到容器关闭,会一直存在这个唯一的对象。
注意:当作用域为单例的时候,IOC容器在启动的时候,就会将容器中所有作用域为单例的bean的实例给创建出来;以后的每次获取,就直接从IOC容器中来获取,相当于是从map.get()的一个过程;也就是 spring 在任何人没有获取 bean 的时候就缓存一份实例。
配置类:
1 |
|
创建一个 bean 对象:
1 |
|
多次从容器获取 bean :
1 | public class TestCode03 { |
输出结果:
org.woodwhale.king.code03.Student@b62fe6d
org.woodwhale.king.code03.Student@b62fe6d
true
我们可以用@Scope
这个注解来指定作用域的范围:这个就相当于在xml文件中配置的<bean>
标签里面指定prototype
属性:
1 | /** |
从源码的注释上,我们可以知道scopeName可以取下面这些值:
ConfigurableBeanFactory#SCOPE_PROTOTYPE
单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。以后每次获取就是直接从容器(map.get())中拿。
ConfigurableBeanFactory#SCOPE_SINGLETON
多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。每次获取的时候才会调用方法创建对象。
org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
同一次请求创建一个实例
org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
同一个session创建一个实例
配置原型模式(ConfigurableBeanFactory#SCOPE_PROTOTYPE):
1 |
|
测试输出结果:
drop student into Spring IOC …
drop student into Spring IOC …
org.woodwhale.king.code03.Student@78047b92
org.woodwhale.king.code03.Student@8909f18
false
我们可以发现,我们用getBean()
方法获取几次,就创建几次bean的实例;也就是说当bean是作用域为多例的时候,IOC容器启动的时候,就不会去创建bean的实例的,而是当我们调用getBean()
获取的时候去创建bean的实例;而且每次调用的时候,都会创建bean的实例;
2.5 懒加载
@Lazy
注解可以在容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化:
1 |
|
2.6 @Conditional 按照条件注册bean
@Conditional
:按照一定的条件进行判断,满足条件给容器中注册bean
举例:要求容器根据操作系统注入不同的bean,如果系统是 windows,给容器中注册(“bill”),如果是linux系统,给容器中注册(“linus”)。
实现Condition
接口,并重写匹配条件逻辑:
windows 条件:
1 | public class WindowsCondition implements Condition { |
linux 条件:
1 | public class LinuxCondition implements Condition { |
@Conditional
按照一定条件进行判断,满足条件容器中注册bean,若放在类中,整个配置类中的bean满足条件才会被加载到容器中:
1 |
|
测试一下注入容器的 id 是谁:
运行测试会发现,spring 只注入了 id 为 linus 的 bean 对象。
1 | public class TestCode04 { |
在 windows 系统运行,可以看到只输出了 id 为 bill 的 bean,可以在 JVM 运行参数中手动设置系统环境参数:-Dos.name=Linux
,再次运行,则输出了 id 为 linus 的 bean
2.7 @Import 导入组件
2.7.1 导入 bean 的类对象
@Import(bean的类对象)
可以快速导入一个 bean ,而不需要使用@Bean
注解一个方法的形式:只要将要注册bean 的类对象传进去,容器就能自动注册这个组:
1 |
|
测试是否导入成功:
1 | public class TestCode05 { |
输出结果:
appConfig5
org.woodwhale.king.code05.Color
从输出结果可以看出:@Import(bean的类对象)
注册的 id 默认是全类名。
2.7.2 导入 ImportSelector 的实现类类对象
实现ImportSelector
接口,重写selectImports()
方法,返回要注册的 bean 对象的全类名:
1 | public class MyImportSelector implements ImportSelector { |
导入 实现类
1 |
|
2.7.3 手动注册 bean
实现ImportBeanDefinitionRegistrar
接口,重写注册 bean 的注册方法,将所有要注册的 bean 手动注册 进容器:
1 | /** |
将这个实现类导入到容器中:
1 |
|
输出结果:
appConfig5
org.woodwhale.king.code05.Color
org.woodwhale.king.code05.Blue
rainBow
注意:当所有组件都注册成功之后,才执行这个实现类的注册方法。
2.8 使用FactoryBean注册组件
首先创建一个类实现FactoryBean<T>
接口,其中T是要注册的Bean的类型:
1 | public class ColorFactoryBean implements FactoryBean<Color> { |
将工厂类注册到配置类中:
1 |
|
编写测试类:
1 |
|
输出结果:
getObject –> new Color()
getObject –> new Color()
bean class type –> class org.woodwhale.king.code05.Color
false
bean class type –> class org.woodwhale.king.code05.ColorFactoryBean
3. 总结
xml 配置文件在 spring 注解开发中已经被替换成了Java 配置类,要成为具有 xml 功能的配置类,就需要在类名上增加@Configuration
注解。
bean 组件注册的方式有以下:
- 使用
@Bean
注解,编写创建 bean 并返回这个bean 的共有方法,在这个方法上加注解,默认会将方法名作为bean 的 id。
容器默认装载的 bean 都是单例的,如果需要多例的 bean ,就需要添加@Scope
注解。
实现
Condition
接口,并自定义条件规则,使用注解@Conditional
对 bean 进行有条件的注册使用
@Import
注解导入组件,有三种方式:- 直接传入 bean 的类对象到
@Import
属性中。 - 实现
ImportSelector
接口,重写selectImports()
方法,返回将要注册的bean 的全类名字符串数组,最后将这个实现类的类对象传入@Import
属性中。 - 实现
ImportBeanDefinitionRegistrar
接口,手动注册 bean 到容器中。
- 直接传入 bean 的类对象到
使用
FactoryBean
接口的实现类,即 bean 的工厂类,配置到配置类中。
扩展博文: