Spring 注解驱动开发【组件注册篇】
1.组件注册 @Configuration&@Bean给容器中注册组件
- 自定义配置类 MyConfig
package indi.zhihuali.config;
import indi.zhihuali.pojo.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 配置类 <=> 配置文件
@Configuration //声明spring配置类
public class MyConfig {
// @Bean 后面也可以加一个参数 作为bean的id 下面的方法名则被覆盖
@Bean("person")
// 给容器中注册一个bean 返回类型为bean的类型 方法名为bean的id
public Person person02(){
return new Person("zhihuali",20);
}
}
- Test类中测试ioc注入情况
// 通过注解(自定义配置类 @Configuration) 对ioc容器进行bean注入
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
// 获取ioc容器中的bean对象并实例化
Person person = context.getBean(Person.class);
System.out.println(person);
// 遍历获取类型为Person的组件在ioc容器中的id是什么
String[] beanNamesForType = context.getBeanNamesForType(Person.class);
for (String name : beanNamesForType) {
System.out.println(name);
}
2.组件注册 @ComponentScan 自动扫描组件及指定扫描规则
- 在自定义配置类中定义@ComponentScan(value=扫描的包)
@Configuration //声明spring配置类
@ComponentScan(value = "indi.zhihuali")
public class MyConfig {
}
-
分别生成组件注解( controller层对应@Controller、mapper层对应@Repository、service层对应@Service 及 @Component)
-
通过测试类对ioc容器中的组件进行扫描测试
@Test
public void testComponent(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
// 查看ioc容器中具有哪些组件
// context.getBeanDefinitionNames() 方法返回容器中所有注册了的bean的名字
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
输出结果为:
myConfig 配置类@Configuration本身就是一个@Component
bookController
bookMapper
bookService
person 通过@Bean注册进ioc容器
- 在@ComponentScan中添加参数 excludeFilters (除掉哪些bean) 或 includeFilters(包含哪些bean)
@Configuration //声明spring配置类
// @ComponentScan value:指定要扫描的包
// excludeFilters 返回 Filter[] 是数组
// @ComponentScan.Filter
// (type = FilterType.ANNOTATION 根据注解进行过滤
// classes = {Controller.class, Service.class} 具体要过滤的是哪些类 @Controller对应Controller.class
@ComponentScan(value = "indi.zhihuali",excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
Controller.class, Service.class})
})
public class MyConfig {
}
添加了excludeFilters后的运行结果
myConfig 配置类@Configuration本身就是一个@Component
bookMapper
person 通过@Bean注册进ioc容器
- 将@ComponentScan内参数修改为includeFilter
@ComponentScan(value = "indi.zhihuali",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
Controller.class, Service.class})
}
// 想要设置为只包含什么什么的过滤器 首先要将默认的过滤器关掉
,userDefaultFilters = false
)
组件扫描器可以指定多个 且互补配置
同样可以通过@ComponentScans进行配置 且value为数组 值为@ComponentScan
@ComponentScans(value = {
@ComponentScan(value = "indi.zhihuali",includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
Controller.class, Service.class})
},useDefaultFilters = false
)
})
3.组件注册 自定义TypeFilter指定过滤规则
// @ComponentScan value:指定要扫描的包
// excludeFilters 返回 Filter[] 是数组 指定扫描时按什么规则排除哪些组件
// includeFilters 指定扫描时只需要包含哪些组件
// (type = FilterType.ANNOTATION 根据注解进行过滤
// FilterType.ASSIGNABLE_TYPE 根据指定类型进行过滤
// FilterType.ASPECTJ 根据ASPECTJ表达式
// FilterType.REGEX 根据正则表达式
// FilterType.CUSTOM 自定义过滤规则
// ,
// classes = {Controller.class, Service.class} 具体要过滤的是哪些类 @Controller对应Controller.class
- 重点是 采用自定义FilterType.CUSTOM进行过滤
- 编写自定义过滤类
public class MyTypeFilter implements TypeFilter {
/*
MetadataReader 读取到的当前正在扫描的类的信息
MetadataReaderFactory 可以获取到其他任何类信息
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 获取当前类资源(类路径)
Resource resource = metadataReader.getResource();
// 获取当前正在扫描的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 对当前类的类信息进行打印
String className = classMetadata.getClassName();
System.out.println(classMetadata);
System.out.println(annotationMetadata);
System.out.println(resource);
System.out.println("--------------------------------");
// 若类名中包含er则放行 不包含则拦截
if(className.contains("er")){
return true;
}
return false;
}
}
- 在配置类中添加自定义typefilter的信息
@ComponentScans(value = {
@ComponentScan(value = "indi.zhihuali",includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM,classes = MyTypeFilter.class)
},useDefaultFilters = false
)
})
- 编写测试方法对ioc容器中对象进行打印
@Test
public void testComponent(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
// 查看ioc容器中具有哪些组件
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
打印结果为
org.springframework.core.type.classreading.SimpleAnnotationMetadata@d282e0
org.springframework.core.type.classreading.SimpleAnnotationMetadata@d282e0
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\config\MyTypeFilter.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@438a68
org.springframework.core.type.classreading.SimpleAnnotationMetadata@438a68
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\controller\bookController.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1d15f18
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1d15f18
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\mapper\bookMapper.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1695df3
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1695df3
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\pojo\Person.class]
--------------------------------
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1ea5ab4
org.springframework.core.type.classreading.SimpleAnnotationMetadata@1ea5ab4
file [D:\java space_idea\Spring-Anno\target\classes\indi\zhihuali\service\bookService.class]
--------------------------------
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
myConfig
myTypeFilter
bookController
bookMapper
person
bookService
注:注解扫描指定包下的所有类 如果满足放行条件则进行放行 否则就拦截
4.组件注册 @Scope 设置组件作用域
@Scope可选参数值
/*
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
* value 的四种取值 :
* prototype 多实例
* singleton 单例 【默认】
* request 同一次请求 创建一次实例 【web环境】
* session 同一个session创建一个实例 【web环境】
*/
在没有显式配置Scope时 默认单例模式
@Configuration
public class MyConfig2 {
@Bean(value = "person")
public Person person(){
System.out.println("给容器添加Person..."); //看何时进行实例化Person对象
return new Person("wangjingbo",12);
}
}
@Test
public void testComponent02(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class);
}
输出:
给容器添加Person...
通过上例可证明 默认情况下 (单例bean时),在创建Ioc容器时就自动生成bean对象 即用即取
给bean配置作用域
@Configuration
public class MyConfig2 {
@Bean(value = "person")
@Scope(value = "prototype")
public Person person(){
return new Person("wangjingbo",12);
}
}
@Test
public void testComponent02(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class);
}
打印结果:空
说明多实例情况下 ioc容器启动时并不会去调用构造器创建对象并放在容器中
那么何时调用呢? 其实是在getBean时调用!
public void testComponent02(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig2.class);
System.out.println("ioc容器初始化完成");
Object person = context.getBean("person");
}
}
ioc容器初始化完成
给容器添加Person...
若获取多个对象 则调用多次
Object person = context.getBean("person");
Object person1 = context.getBean("person");
ioc容器初始化完成
给容器添加Person...
给容器添加Person...
5.组件注册 @Lazy 懒加载bean 【针对@Scope(“singleton”)】
默认情况下 单例模式下添加bean会在创建Ioc容器时提前自动生成bean对象
通过添加@Lazy 可以达到在创建ioc容器时不再提前自动生成bean对象 而用时生成的效果
@Bean(value = "person")
@Lazy // 懒加载
public Person person(){
System.out.println("给容器添加Person...");
return new Person("wangjingbo",12);
}
6.组建注册 @Conditional 按照条件注册bean
@Conditional({condition数组}):按照一定的条件进行判断 满足条件给容器中注册bean
例如:如果系统是 windows 给容器中注册 bill
如果系统是 linux 给容器中注册 linus
将注解标注在方法上 对单一bean进行condition判断
将注解标注在类上 满足当前条件 类中的任何bean注册才会生效 类中组件统一设置
@Configuration
public class MyConfig2 {
/*
* @Conditional({condition数组}):按照一定的条件进行判断 满足条件给容器中注册bean
* 例如:如果系统是 windows 给容器中注册 bill
* 如果系统是 linux 给容器中注册 linus
* */
@Conditional({
LinuxCondition.class})
@Bean("linus")
public Person person01(){
return new Person("Linus",22);
}
@Conditional({
WindowsCondition.class})
@Bean("bill")
public Person person02(){
return new Person("Bill",27);
}
}
编写:LinuxCondition.class实现Condition接口
public class LinuxCondition implements Condition {
/*
* ConditionContext 判断条件能使用的上下文环境
* AnnotatedTypeMetadata 注释信息
* */
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断是否linux系统:
// 1.能获取到ioc使用的beanfactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 2.获取类加载器
ClassLoader classLoader = context.getClassLoader();
// 3.获取当前环境信息
Environment environment = context.getEnvironment();
// 4.获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
// Condition可以判断的东西有很多:
// 可以通过下面的方法判断容器中是否包含名为person的bean
// 也可以通过一些方法注册bean等等
boolean person = registry.containsBeanDefinition("person");
// 通过environment.getProperty("os.name")方法可以查看当前操作系统信息
String property = environment.getProperty("os.name");
if(property.contains("Linux")){
return true;
}
return false;
}
}
WindowsCondition.class 实现Condition接口
public class WindowsCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if(property.contains("Windows")){
return true;
}
return false;
}
}
默认情况下操作系统为Windows10 所以会满足WindowsCondition的条件 即注入bill
进行测试
@Test
public void testComponent03(){
// 动态获取环境变量的值 操作系统值:windows 10
ConfigurableEnvironment environment = (ConfigurableEnvironment) context.getEnvironment();
String property = environment.getProperty("os.name");
System.out.println(property);
// 1、获取Person类bean对象的名字并遍历输出
String[] beanNamesForType = context.getBeanNamesForType(Person.class);
for (String name : beanNamesForType) {
System.out.println(name);
}
// 2、获取Person类bean对象并输出
Map<String, Person> persons = context.getBeansOfType(Person.class);
System.out.println(persons);
}
输出:
Windows 10
bill
{bill=Person(name=Bill, age=27)}
可以通过editConfigurations
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7V4vEeHx-1600441483178)(C:\Users\zhihua.Li\AppData\Roaming\Typora\typora-user-images\image-20200916233646993.png)]
给虚拟机设置参数 达到修改os.name的效果从而使LinuxCondition生效而注入linus bean对象 测试同上
结果:
linux
{}
7.组件注册 @Import 给容器中快速导入一个组件
给容器中添加组件的几种方法:
1.针对于自己写的类:包扫描+组件定义(@Component @Repository @Service @Controller)
2.针对于不是自己写的类 如第三方导入时:@Configuration & @Bean
3.针对于不是自己写的类 如第三方导入时 若通过Bean导入 还需要给实体类添加无参构造器 较为复杂
同样可以快速为容器中导入一个组件:@Import({})
1.@Import(要导入的组件) 容器中就会自动注册这个组件 id默认为全类名 见7.3.1
2.采用ImportSelector:返回需要导入的组件的全类名数组 见7.3.2
3.采用ImportBeanDefinitionRegistrar:手动注册bean到ioc容器中
4.使用Spring提供的FactoryBean(工厂bean)注册组件:
1.默认获取到的是工厂bean调用getObject创建的对象
即通过context.getBean("colorFatoryBean") 获取到的是Color类的对象
2.要获取工厂bean本身 需要给id前加一个& 即context.getBean("&colorFatoryBean")
7.3.1 在配置类上方添加@Import
@Configuration
@Import({
Color.class})
public class MyConfig2 {
....
}
7.3.2 自定义ImportSelector类
- 编写两个pojo实体类
public class Red{
}
public class Yellow{
}
- 编写MyImportSelector类实现ImportSelector接口
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
// 返回值 就是导入到容器中的组件全类名 会自动添加到ioc容器中 不可以为null 但可以是空数组
// AnnotationMetadata:当前标注@Import注解的类的全部注解信息
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 可以通过importingClassMetadata.getXXX() 可以通过importingClassMetadata获取多个属性
// 将 Red 和 Yellow 添加到ioc容器中(要写全类名)
return new String[]{
"indi.zhihuali.pojo.Red","indi.zhihuali.pojo.Yellow"};
}
}
- 利用@Import添加MyImportSelector类
@Configuration
@Import({
Color.class, MyImportSelector.class})
public class MyConfig2 {
}
**Tips:**全类名的快速获取:
可以右键单击要复制全类名的类->copy->copy reference后 输入双引号并在其中ctrl+v 即可快速复制全类名
测试获取ioc容器中的所有bean 结果为:
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
myConfig2
indi.zhihuali.pojo.Color
indi.zhihuali.pojo.Red
indi.zhihuali.pojo.Yellow
bill
可以看到Red和Yellow都被注册进去了
7.3.3 自定义ImportBeanDefinitionRegistrar类实现ImportBeanDefinitionRegistrar接口
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
/*
*
* AnnotationMetadata:当前类的注解信息 可以获取类的若干信息
* BeanDefinitionRegistry:BeanDefinition注册类 将所有需要添加的bean
* 通过调用
* registry.registerBeanDefinition() 手工注册到ioc中
*
* */
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
// 判断容器中是否有名为Red和Yellow的bean 若有则将rainbow也注册进ioc中
boolean red = registry.containsBeanDefinition("indi.zhihuali.pojo.Red");
boolean yellow = registry.containsBeanDefinition("indi.zhihuali.pojo.Yellow");
if(red && yellow){
// 指定bean的定义信息 (类型 Scope等)
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rainbow.class);
// 注册一个bean 指定bean名
// registerBeanDefinitions(importingClassMetadata, registry);
// importingClassMetadata bean名字
// registry 指定bean的定义信息 (类型 Scope等)
registry.registerBeanDefinition("rainbow", rootBeanDefinition);
}
}
}
测试所得
myConfig2
indi.zhihuali.pojo.Color
indi.zhihuali.pojo.Red
indi.zhihuali.pojo.Yellow
bill
rainbow
7.4 自定义FactoryBean类
- 自定义ColorFactoryBean实现FactoryBean接口
package indi.zhihuali.pojo;
import org.springframework.beans.factory.FactoryBean;
/**
* @author zhihua.li
* @date 2020/9/18 - 8:06
**/
public class ColorFactoryBean implements FactoryBean<Color> {
// 返回一个Color对象 这个对象会添加到容器中
public Color getObject() throws Exception {
// 在对象被创建时打印标志语句
System.out.println("get it");
return new Color();
}
// 返回对象的类型
public Class<?> getObjectType() {
return Color.class;
}
// 设置对象是否单例:
// true:单例 在容器中只保存一份
// false:多例 每次获取都会创建一个新的
public boolean isSingleton() {
return true;
}
}
- 在配置类中注入定义的类
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
- 测试ioc容器中bean情况
@Test
public void testComponent04() throws Exception {
// 封装了获取bean的方法
getAllBeans(context);
}
- 测试结果
myConfig2
indi.zhihuali.pojo.Color
indi.zhihuali.pojo.Red
indi.zhihuali.pojo.Yellow
bill
colorFactoryBean
rainbow
-
可以发现ioc容器中存在一个名为colorFactoryBean的bean 可定义FactoryBean就是为了注入Color类的bean
对此进行测试 获取名为ColorFactoryBean的bean的对象并通过反射获取其运行时类
Object bean = context.getBean("colorFactoryBean"); System.out.println("不明类型的bean为:"+bean.getClass());
输出结果为:
不明类型的bean为:class indi.zhihuali.pojo.Color
可见 虽然其名字为colorFactoryBean 但实际上调用getBean方法时获取到的还是Color对象
-
是否为单例?
Object bean1 = context.getBean("colorFactoryBean");
Object bean2 = context.getBean("colorFactoryBean");
System.out.println(bean1 == bean2);
true
-
如果获取到colorFactoryBean类本身对象呢?
通过给getBean参数前加一个&即可获得其本身bean
public interface BeanFactory { /* * Used to dereference a {@link FactoryBean} instance and distinguish it from * beans <i>created</i> by the FactoryBean. For example, if the bean named * {@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject} * will return the factory, not the instance returned by the factory. */ String FACTORY_BEAN_PREFIX = "&";
Object bean = context.getBean("colorFactoryBean");
System.out.println("不明类型的bean为:"+bean.getClass());
Object bean2 = context.getBean("&colorFactoryBean");
System.out.println("添加了&后获取到的bean为:"+bean2.getClass());
输出结果为:
不明类型的bean为:class indi.zhihuali.pojo.Color
添加了&后获取到的bean为:class indi.zhihuali.pojo.ColorFactoryBean