spring注解学习之一:IOC

编程入门 行业动态 更新时间:2024-10-24 18:21:16

spring<a href=https://www.elefans.com/category/jswz/34/1768912.html style=注解学习之一:IOC"/>

spring注解学习之一:IOC

文章目录

    • 组件注册1-Helloworld(使用xml配置文件)
    • 组件注册2-Helloworld(使用注解)
    • 组件注册3-component-scan包扫描
      • includeFilters excludeFilters useDefaultFilters
      • 过滤规则
    • 组件注册4-Scope和Lazy
    • 组件注册5-Conditional
    • 组件注册6-Import
    • 生命周期:初始化和销毁方法
      • 指定初始化和销毁方法
      • 实现`InitializingBean` `DisposableBean:`
      • 使用`@PostConstruct` `@PreDestroy`注解:
      • 使用BeanPostProcessor接口:Bean的后置处理器
      • BeanPostProcessor的原理&Spring底层应用
    • 属性赋值
    • 自动装配@Autowired、@Qualifier、@Primary
    • 方法、构造器位置的自动装配

组件注册1-Helloworld(使用xml配置文件)

  • 新建工程,添加Maven支持
  • 在Maven配置文件pom.xml中添加:
    <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.13.RELEASE</version></dependency></dependencies>
  • 写一个Person类,属性有name,age,在类中添加无参构造方法,有参构造方法,getter和setter,toString方法
  • 在代码根目录下添加bean.xml文件,内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=""xmlns:xsi=""xsi:schemaLocation=" .xsd"><bean id="person" class="win.bojack.bean.Person"><property name="age" value="33" /><property name="name" value="zhangsan" /></bean>
</beans>
  • 获取在bean.xml文件中定义的bean:写一个测试类,在main方法中添加代码:
// 老的方式(xml配置文件):
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
Object person = ac.getBean("person");
System.out.println(person)

组件注册2-Helloworld(使用注解)

  • 写一个配置类MainConfig:
@Configuration # 这个注解的含义就是告诉spring这是一个配置类.
public class MainConfig {@Bean("person11")  # 这个注解表示这个方法生成一个bean,默认bean的名字就是方法名,当然也可以指定bean namepublic Person person() {return new Person("lisi", 44);}
}
  • 写一个测试类MainTest:
// 注解方式:
ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
Person person = ac.getBean(Person.class);
System.out.println(person);

组件注册3-component-scan包扫描

component-scan的目的主要是扫描指定包同级和下级包中所有标注了@Componet ,@Service ,@Repository,@Controller的类.将它们注册为bean

  • 在win.bojack包下添加几个子包:controller,dao,service,分别在这几个包下增加相应的类,并在类注解上注名: @Controller, @Repository, @Service, 例:
package win.bojack.dao;
@Repository
public class BookDao {
}
  • xml方式:在bean.xml文件中增加:
<context:component-scan base-package="win.bojack" /> 
  • 注解方式:在配置类的类注解上添加:
@ComponentScan("win.bojack") # 指定要扫描的包
  • 测试:
ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
String[] beanNames = ac.getBeanDefinitionNames();
for (String beanName : beanNames) {System.out.println(beanName);
}

打印时会将MainConfig也打印出来,因为MainConfig是一个@Configuration ,它是一个@Component. @Component会被@Component-scan自动扫描

includeFilters excludeFilters useDefaultFilters

@Component-scan会扫描所有标注了@Componet ,@Service ,@Repository,@Controller的类,太过粗暴.可以使用@includeFilters来指定扫描哪些类,@excludeFilters来指定排除哪些类.需要注意的是,用includeFilters之前,要用useDefaultFilters=false将默认的过滤器禁用掉才会生效:

@ComponentScan(value = "win.bojack",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
})
@ComponentScan(value = "win.bojack",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Repository.class})
})

@ComponentScan是一个@Repeatable的,因此可以写多个@ComponentScan.也可以用@ComponentScans,里面包含多个@ComponentScan数组:

@ComponentScans(value = {@ComponentScan(value = "win.bojack",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Repository.class})}),@ComponentScan(value = "win.xxxxx",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Repository.class})})
})

过滤规则

  • FilterType.ANNOTATION (按照注解类型来过滤)
  • FilterType.ASSIGNABLE_TYPE (按照类型(类名)来过滤)
  • FilterType.ASPECTJ (ASPECTJ表达式)
  • FilterType.REGEX (使用正则表达式来过滤)
  • FilterType.CUSTOM (使用自定义类来实现过滤,必须实现TypeFilter接口)

自定义TypeFilter类:

public class MyTypeFilter implements TypeFilter {/**** @param metadataReader 读取到的当前正在扫描的类的信息* @param metadataReaderFactory 可以获取到其他相关类的信息(一般是超类或接口)* @return* @throws IOException*/public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {// 获得当前类注解的信息AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();// 获得当前正在扫描的类的信息ClassMetadata classMetadata = metadataReader.getClassMetadata();String className = classMetadata.getClassName();System.out.println("className ---> " + className);// 获得当前类的资源Resource resource = metadataReader.getResource();//return className.contains("er");}
}

然后在配置类中添加如下注解:

@Configuration
@ComponentScan(value = "win.bojack", useDefaultFilters = false, includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)
})

即可实现只注册名称中er的bean

组件注册4-Scope和Lazy

bean的scope有四种:

  • prototype: 多实例.在使用bean对象前, 是不会生成bean对象的.每次获取时才会调用方法创建对象,而且每次获取新的对象.
  • singleton: 单实例.(默认情况)只生成一个bean对象,多次获取bean获得的是同一对象. 容器初始化时都会调用生成对象的方法, 以后每次调用都是直接从窗口中拿(map.get())
  • request 同一次请求创建一个实例(web环境下,不常见)
  • session 同一个session创建一个实例(web环境下,不常用)
Object person = ac.getBean("person");
Object person1 = ac.getBean("person");
System.out.println(person == person1);
// 结果为true
  • lazy :只针对单实例. 因为多实例本来就是要使用对象时才会加载, 单实例默认情况下是容器初始化时就加载了对象

组件注册5-Conditional

  • springboot底层大量使用此注解,按照一定的条件进行判断. 满足条件给容器注册bean
  • @Conditional注解可以用在类上,也可以用在方法上
  • 需要自定义类来实现,实现Condition接口

编写两个Condition类:

public class WindowsCondition implements Condition {/**** @param context 判断条件能使用的上下文环境* @param metadata 注释信息* @return*/public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// 1获取IOC使用的beanFactoryConfigurableListableBeanFactory beanFactory = context.getBeanFactory();// 2获取类加载器ClassLoader classLoader = context.getClassLoader();// 3获取bean定义的注册类BeanDefinitionRegistry registry = context.getRegistry();// 4获取当前环境信息Environment environment = context.getEnvironment();String property = environment.getProperty("os.name");return property.contains("Windows");}
}
public class LinuxCondition implements Condition {public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Environment environment = context.getEnvironment();String property = environment.getProperty("os.name");return property.contains("Linux");}
}

编写两个方法,分别返回两个bean:

    @Bean("bill")@Conditional(WindowsCondition.class)public Person person01() {return new Person("bill gates" , 66);}@Conditional(LinuxCondition.class)@Bean("linus")public Person person02() {return new Person("linus", 48);}

测试方法:

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig2.class);private void printBeanNames() {String[] beanDefinitionNames = ac.getBeanDefinitionNames();for (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);}
}
@Test
public void test3() {System.out.println("ioc容器创建完成...");printBeanNames();Map<String, Person> beansOfType = ac.getBeansOfType(Person.class);System.out.println(beansOfType);ConfigurableEnvironment environment = ac.getEnvironment();String property = environment.getProperty("os.name");System.out.println(property);
}
bill
{bill=Person{name='bill gates', age=66}}
Windows 10

只打印了: bill.
如果在run中指定参数 -Dos.name=Linux,则只打印linus.

还可以在Condition实现类中添加其他条件,比如检查一个类是否注册,和对类进行注册:

if (registry.containsBeanDefinition("person11")) {registry.registerBeanDefinition(// TODO);
}

@Conditional注解除了在方法上使用,还可以用在类上, 类中配置的所有bean注册都会生效

组件注册6-Import

给容器注册bean有几种方法:

  • 包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)
  • @Bean[导入第3方包里面的组件]
  • @Import[快速导入组件]
    • 在类名上使用,导入单个组件 @Import(Color.class) ,容器会注册这个组件,组件名默认是类的全名
    • @ImportSelector 生成一个类,实现ImportSelector接口, 返回一个数组,包含所有需要导入的类的路径(springboot中用得多)
    • @ImportBeanDefinitionRegistrar 生成一个类,实现MyImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法,可以实现一些Bean导入逻辑.
  • @ImportSelector:
// 自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {// 返回值就是导入到容器中的组件类路径// AnnotationMetadata : 当前标注@Import注解的类的所有注解信息public String[] selectImports(AnnotationMetadata importingClassMetadata) {// 注意这里不要返回null,至少返回一个空数组return new String[]{"win.bojack.bean.Blue", "win.bojack.bean.Red", "win.bojack.bean.Yellow"};}
}

在配置类上添加:

@Import({Color.class, MyImportSelector.class})
  • @ImportBeanDefinitionRegistrar:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {/**** @param importingClassMetadata : 当前类的注解信息* @param registry BeanDefinition注册类:*                 把所有需要添加到容器中的bean:调用*                 BeanDefinitionRegistry.registerBeanDefinition手工注册进来*/public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {boolean reg1 = registry.containsBeanDefinition("win.bojack.bean.Red");boolean reg2 = registry.containsBeanDefinition("win.bojack.bean.Blue");if (reg1 && reg2) {// 指定Bean定义信息,{Bean的类型,scope...}RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rainbow.class);// 指定Bean名注册一个Beanregistry.registerBeanDefinition("rainBow", rootBeanDefinition);}}
}
  • 使用Spring提供的BeanFactory(工厂Bean)
    • 默认获取到的是工厂Bean本身调用getObject产生的对象
    • 要获取工厂Bean本身,需要在id前面加一个&符号: &colorFactoryBean
      生成一个工厂类:
public class ColorFactoryBean implements FactoryBean<Color> {// 返回一个Color对象,这个对象会添加到容器中public Color getObject() throws Exception {return new Color();}public Class<?> getObjectType() {return Color.class;}// 是否单例public boolean isSingleton() {return true;}
}

在配置类中增加Bean:

@Bean
public ColorFactoryBean colorFactoryBean() {return new ColorFactoryBean();
}

测试类:

// 这里获取的Bean是colorFactoryBean,打印getClass时却是Color, 因为实际上调用的是ColorFactoryBean中的getObjectType方法, 返回的Bean也是ColorFactoryBean中的getObject方法
Object colorFactoryBean = ac.getBean("colorFactoryBean");System.out.println(" color 类型: " + colorFactoryBean.getClass());
// 如果想要返回colorFactoryBean,则需要:
Object bean1 = ac.getBean("&colorFactoryBean");
// 在Bean前面加&是BeanFactory这个接口的约定

生命周期:初始化和销毁方法

Bean的生命周期:bean的创建–初始化–销毁的过程
容器管理的bean的生命周期。我们可以自定义初始化和销毁方法:容器在bean进行到当前生命周期某个阶段时,自动调用指定的方法

构造(对象创建)
单实例:在容器启动的时候创建对象
多实例:在每次获取的时候创建对象

执行顺序:
BeanPostProcessor.postProcessBeforeInitialization
初始化:在对象创建完成并赋值好时,调用初始化方法…
BeanPostProcessor.postProcessAfterInitialization
销毁:容器关闭时
单实例:容器关闭的时候
多实例:容器不会管理这个Bean容器不会调用销毁方法(需要手动调用销毁方法)

指定初始化和销毁方法:在xml配置文件时,可以指定init-methoddestroy-method,或通过@Bean指定init-methoddestroy-method
通过让Bean实现过InitializingBean接口来实现定义初始化逻辑,和DisposableBean接口来实现销毁逻辑
通过@PostConstructor在Bean创建完成并且属性赋值完成时来执行方法
和@PreDestroy在容器销毁Bean之前来执行清理工作

指定初始化和销毁方法

@Configuration
@ComponentScan("win.bojack.bean")
public class MainConfigOfLifeCycle {// @Scope("prototype")@Bean(initMethod = "init", destroyMethod = "destroy")public Car car() {return new Car();}
}
public class Car {public Car() {System.out.println("car constructor...");}public void init() {System.out.println("car init...");}public void destroy() {System.out.println("car destroy...");}
}
    public void test01() {// 1,创建IOC容器AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);System.out.println("容器创建完成");ac.close();}
打印结果:
car constructor...
car init...
容器创建完成
car destroy...

实现InitializingBean DisposableBean:

@Component
public class Cat implements InitializingBean, DisposableBean {public Cat() {System.out.println("cat constructor...");}public void destroy() throws Exception {System.out.println("cat destroy...");}public void afterPropertiesSet() throws Exception {System.out.println("cat propertiesSet ...");}
}

使用@PostConstruct @PreDestroy注解:

@Component
public class Dog {public Dog() {System.out.println("dog Constructor ... ");}@PostConstructpublic void init() {System.out.println("dog PostConstruct ... ");}@PreDestroypublic void destroy() {System.out.println("dog PreDestroy ... ");}
}

结果:

cat constructor...
cat propertiesSet ...
dog Constructor ... 
dog PostConstruct ... 
car constructor...
car init...
容器创建完成
car destroy...
dog PreDestroy ... 
cat destroy...

使用BeanPostProcessor接口:Bean的后置处理器

在Bean初始化前后进行一些处理工作,通过实现BeanPostProcessor的两个方法来实现一些功能:

  • postProcessBeforeInitialization:在任何初始化工作之前,比如afterPropertiesSet方法之前,完成某些任务
  • postProcessAfterInitialization:在初始化之后工作
/*** 后置处理器:在容器初始化前后进行处理工作。对所有组件都生效。* 将后置处理器加入到容器中*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("postProcessBeforeInitialization +++ " + beanName + "==>" + bean);// 在这里可以对bean进行一些额外的处理,然后再返回return bean;}public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("postProcessAfterInitialization ... " + beanName + "==>" + bean);return bean;}
}

结果:

postProcessBeforeInitialization +++ org.springframework.context.event.internalEventListenerProcessor==>org.springframework.context.event.EventListenerMethodProcessor@36f0f1be
postProcessAfterInitialization ... org.springframework.context.event.internalEventListenerProcessor==>org.springframework.context.event.EventListenerMethodProcessor@36f0f1be
postProcessBeforeInitialization +++ org.springframework.context.event.internalEventListenerFactory==>org.springframework.context.event.DefaultEventListenerFactory@6ee12bac
postProcessAfterInitialization ... org.springframework.context.event.internalEventListenerFactory==>org.springframework.context.event.DefaultEventListenerFactory@6ee12bac
postProcessBeforeInitialization +++ mainConfigOfLifeCycle==>win.bojack.config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$39885bd3@64c87930
postProcessAfterInitialization ... mainConfigOfLifeCycle==>win.bojack.config.MainConfigOfLifeCycle$$EnhancerBySpringCGLIB$$39885bd3@64c87930
cat constructor...
postProcessBeforeInitialization +++ cat==>win.bojack.bean.Cat@525f1e4e
cat propertiesSet ...
postProcessAfterInitialization ... cat==>win.bojack.bean.Cat@525f1e4e
dog Constructor ... 
postProcessBeforeInitialization +++ dog==>win.bojack.bean.Dog@5ea434c8
dog PostConstruct ... 
postProcessAfterInitialization ... dog==>win.bojack.bean.Dog@5ea434c8
car constructor...
postProcessBeforeInitialization +++ car==>win.bojack.bean.Car@1d548a08
car init...
postProcessAfterInitialization ... car==>win.bojack.bean.Car@1d548a08
容器创建完成
car destroy...
dog PreDestroy ... 
cat destroy...

可以看出,所有的组件都经过了MyBeanPostProcessor的两个方法的处理

BeanPostProcessor的原理&Spring底层应用

AnnotationConfigApplicationContext的初始化开始,一层层地调用,一直到AbstractAutowireCapableBeanFactory.initializeBean方法,这个方法执行applyBeanPostProcessorsBeforeInitialization,然后invokeInitMethods,再执行applyBeanPostProcessorsAfterInitialization。这两个方法找到所有的BeanPostProcessor,执行每个beanPostProcesssorpostProcessBeforeInitializationpostProcessAfterInitialization方法并获得结果,如果有一个方法的结果为null,直接返回结果,不会执行后面的BeanPostProcessor

populateBean(beanName, mbd, instanceWrapper); // 给bean进行属性赋值
initializeBean() {applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);invokeInitMethods(beanName, wrappedBean, mbd); // 执行自定义初始化applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

BeanPostProcessor接口众多的实现类中,有一个ApplicationContextAwareProcessor,它的作用就是给组件注入IOC容器。如果一个组件想要IOC容器,就可以实现ApplicationContextAware并实现方法:

private ApplicationContext ac;
public void setApplicationContext(ApplicationContext ac) throws BeansException {this.ac = ac;
}

然后这个组件就可以很愉快的调用容器本身的一些功能了,很神奇,很蒙B有木有??这正是ApplicationContextAwareProcessor实现的,它有一个postProcessBeforeInitialization方法,这个方法判断组件有没有实现ApplicationContextAware接口,如果有实现的话就调用invokeAwareInterfaces方法,将组件转成对应的ApplicationContextAware,再将applicationContext设置进来。

BeanPostProcessor还有一些重要的实现类,比如BeanValidationPostProcessor,在做数据检验时很有用。它可以在初始化前后调用postProcessBeforeInitializationpostProcessAfterInitialization再调用doValidate来实现数据校验功能。

InitDestroyAnnotationBeanPostProcessor,它处理PostConstructPostDestroy注解的,实现原理也是通过调用调用postProcessBeforeInitializationpostProcessAfterInitialization方法来实现的。

AutowiredAnnotationBeanPostProcessor:处理@Autowired标记的组件,原理同上。

总结Spring底层,通过调用BeanPostProcessor,实现bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async,都是通过xxxxBeanPostProcessor来实现的。

属性赋值

  • 使用@Value赋值:
    • 1、直接写基本数值,如String、Integer、Boolean类型的值
    • 2、可以写SPEL:#{}
    • 3、可以通过${}取出配置文件(.properties)中的值(在运行环境变量中的值),这种情况需要在xml文件配置<context:property-placeholder location="classpath:person.properties“/>,或者在配置类中添加注解@PropertySource(value = {"classpath:/xxxx.properties"}, encoding = "GBK") #注意指定编码
// 配置类:
@Configuration
@PropertySource(value = {"classpath:/xxxx.properties"}, encoding = "GBK")
public class MainConfigOfPropertiesValue {
@Value("zhangsan")
private String name;
@Value("#{20-4}")
private Integer age;
@Value("${name.nickName}")
private String nickName;
# xxxx.properties
name.nickName=小张

还可以用这种方式获得properties文件中配置的属性值:

String property = ac.getEnvironment().getProperty("person.nickName");
System.out.println(property);

还可以使用@PropertySources来指定多个@PropertySource,因为@PropertySource是个@Repeatable

自动装配@Autowired、@Qualifier、@Primary

Spring 利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值
1、@Autowired,自动注入
默认优先按照类型去容器中找对应的组件,如果找到就进行赋值。applicationContext.getBean(BookDao.class);
如果找到多个相同类型的组件,再将属性作为组件的id去容器中查找applicationContext.getBean("bookDao");
@Qualifier("bookDao3")使用@Qualifier指定需要装配的组件的id,而不是使用属性名
自动装配一定要将属性赋值好,没有就会报错。也可以使用@Autowired(required= false)来实现非必须的依赖,也就是找得到组件就自动装配,没有找到就不装配。
可以使用@Primary设置首选的组件

2、Spring还支持@Resource(JSR250规范)和@Inject(JSR330规范)
@Resource也能实现自动装配功能,默认是按照组件名称来装配的。没有Primary功能也没有Autowired(required=xx)的功能
@Inject需要导入javax.inject依赖,功能和@Autowired类似,@Inject没有任何属性,因此不太好用

@Autowired是Spring定义的,@Resource@Inject都是Java规范

@Autowired是利用AutowiredAnnotationBeanPostProcessor来实现自动装配的

方法、构造器位置的自动装配

3、 @Autowired可以标注在方法上,也可以标注在构造方法,或者构造方法的参数上。如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略。参数位置的组件还是可以从容器中自动获取

  • 1、【标注在方法位置】 @Bean+方法参数 :参数从容器中获取,默认不写@Autowired效果是一样的;都能自动装配
  • 2、【标注在构造器上】,如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件可以自动装配
  • 3、放在参数位置,

4、自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,…),就可以自定义组件实现xxxAware:在创建对象的时候,会调用 接口规定的方法来注入相关的组件。有一个叫Aware的总接口,它是利用一种像回调机制的方式来实现自动调用。
自定义组件实现xxxAware:在创建对象的时候,会调用接口规定的方法注入相关的组件;Aware,把Spring底层的一些组件注入到自定义的Bean中;
每一个xxxAware都有对应的xxxBeanPostProcessor:
比如ApplicationContextAware对应着ApplicationContextAwareProcessor,后者就是一个BeanPostProcessor

更多推荐

spring注解学习之一:IOC

本文发布于:2024-02-27 14:31:37,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1706908.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:注解   spring   IOC

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!