组件注册 01 @Configuration @Bean
Spring重要特性:IOC/DI (控制反转 / 依赖注入)
传统方式
使用beans.xml
- 创建bean类
package com.example.annotations.bean;
public class Person {
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- idea中创建beans.xml
new – xml configuration file – Spring config
<bean id="person" class="com.example.annotations.bean.Person">
<property name="name" value="lxl"/>
<property name="age" value="21"/>
</bean>
- 测试
@Test
void beanTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
}
注解方式 @Configuration @Bean
-
编写MainConfig类:配置类等同于beans.xml
package com.example.annotations.config; import com.example.annotations.bean.Person; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; // @Configuration : 告诉Spring这是个配置类 @Configuration public class MainConfig { //给容器注入一个bean,类型为返回值的类型,id默认使用方法的id @Bean("person") public Person getPerson(){ return new Person("qd",21); } }
-
测试
@Test void beanConfigTest(){ ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); Person person = applicationContext.getBean(Person.class); //获取对应类型组件在IOC容器中的名字 返回一个数组 String[] beanNamesForType = applicationContext.getBeanNamesForType(Person.class); // System.out.println(Arrays.toString(beanNamesForType)); for (String str:beanNamesForType){ System.out.println("toString:" + str.toString()); } }
Notes
- @Configuration:告诉Spring这是个配置类
- @Bean:给容器注入一个bean
- 标注在类上:类型为bean类的类型,id默认为类的小写
- 标注在方法上:类型为返回值的类型,id默认使用方法的id
组件注册 02 @ComponentScan 自动扫描组件 & 指定扫描规则
资源准备
package com.example.annotations.controller;
import org.springframework.stereotype.Controller;
@Controller
public class BookController {
}
package com.example.annotations.service;
import org.springframework.stereotype.Service;
@Service
public class BookService {
}
传统方式
beans.xml中:
<context:component-scan base-package="com.example.annotations.controller"></context:component-scan>
<bean id="person" class="com.example.annotations.bean.Person">
<property name="name" value="lxl"/>
<property name="age" value="21"/>
</bean>
此后 只要在该包下标注了 @Controller @Service @Repository @Component 都会被加载进IOC容器
测试:
@Test
void packageScanTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String str:beanDefinitionNames){
System.out.println(str);
}
}
有person是因为在beans.xml手动注入的
注解方式 @ComponentScan()
使用的时候注释掉 beans.xml中的 包扫描属性
<!-- 这行 注释掉 -->
<!-- <context:component-scan base-package="com.example.annotations.controller"></context:component-scan>-->
<bean id="person" class="com.example.annotations.bean.Person">
<property name="name" value="lxl"/>
<property name="age" value="21"/>
</bean>
直接扫描
-
在MainConfig上加上注解 @Configuration(value = “com.example.annotations.service”)
// @Configuration : 告诉Spring这是个配置类 @Configuration @ComponentScan(value = "com.example.annotations") // 包扫描注解:只要在该包下标注了 @Controller @Service @Repository @Component 都会被加载进IOC容器 public class MainConfig { //给容器注入一个bean,类型为返回值的类型,id默认使用方法的id @Bean("person") public Person getPerson(){ return new Person("qd",21); } }
-
测试
@Test void packageScanTest(){ //xml // ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); //配置类测试 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String str:beanDefinitionNames){ System.out.println(str); } }
-
结果:
注入进容器的id默认是类名的小写(不严格是小写,实际为驼峰命名)
不玩钱为什么会有mainConfig?MainConfig.class上标注了@Configuration注解
@Configuration 源码
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component // 有这个注解,会被加载进容器 public @interface Configuration { @AliasFor( annotation = Component.class ) String value() default ""; boolean proxyBeanMethods() default true; }
过滤规则
@includeFilters
// @Configuration : 告诉Spring这是个配置类
@Configuration
@ComponentScan(value = "com.example.annotations",
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = { Service.class,Controller.class})},
useDefaultFilters = false
)
public class MainConfig {
//给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
@Bean("person")
public Person getPerson(){
return new Person("qd",21);
}
}
测试:
@excludeFilters
???
// @Configuration : 告诉Spring这是个配置类
@Configuration
@ComponentScan(value = "com.example.annotations",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class, Service.class})},
useDefaultFilters = false
)
public class MainConfig {
//给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
@Bean("person")
public Person getPerson(){
return new Person("qd",21);
}
}
测试:可看到过滤掉了controller和Service
组件注册 03 FilterType
FilterType.ANNOTATION 注解
常用
@ComponentScan(value = "com.example.annotations",
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = { Service.class,Controller.class})},
useDefaultFilters = false
)
FilterType.ASSIGNABLE_TYPE 按照给定的类型
@ComponentScan(value = "com.example.annotations",
includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes ={MyTypeFilter.class, BookController.class})},
useDefaultFilters = false
)
FilterType.ASPECTJ 不常用 使用ASPECTJ表达式
FilterType.REGEX 使用正则表达式
FilterType.CUSTOM 自定义规则
注意:bean不能重名 不然会报错
@ComponentScan(value = "com.example.annotations",
includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,classes ={MyTypeFilter.class})},
useDefaultFilters = false
)
需要实现TypeFilter接口 并重写match方法
public class MyTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
// 获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当秋安正在扫描的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 获取当前类资源(类的路径)
Resource resource = metadataReader.getResource();
// 获取正在扫描的类名
String className = classMetadata.getClassName();
System.out.println("className" + className);
return className.contains("er")?true:false;//true表示加载进容器 false表示不加载进容器
}
}
组件注册 04 @Scope 设置组件作用域
参数详解
常用
prototype:多实例
singleton:单实例
不常用
request:同一次请求创建一个实例
session:同一个session创建一个实例
示例
单例模式(默认情况)
配置类
@Configuration
public class MainConfig2 {
// 由于容器中已经有了一个名为"person"的bean, 所以如果再注入"person"的时候会报错 需要修改下注入的bean的名称
// 不写scope的情况下 默认是singleton 相当于 @Scope("singleton")
@Bean("person2")
public Person person(){
return new Person("qd2",22);
}
}
测试
@Test
void scopeTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
Object bean = applicationContext.getBean("person2");
Object bean1 = applicationContext.getBean("person2");
System.out.println("bean :"+bean.toString());
System.out.println("bean1:"+bean1.toString());
System.out.println(bean == bean1);
}
多例模式
@Configuration
public class MainConfig2 {
// 由于容器中已经有了一个名为"person"的bean, 所以如果再注入"person"的时候会报错 需要修改下注入的bean的名称
@Bean("person2")
@Scope("prototype")
public Person person(){
return new Person("qd2",22);
}
}
等同于
<bean id="person" class="com.example.annotations.bean.Person" scope="prototype">
<property name="name" value="lxl"/>
<property name="age" value="21"/>
</bean>
测试结果:
Notes
单实例
单例模式下(singleton)
- 在容器加载的时候加载bean
- IOC容器启动会调用方法创建对象放进IOC容器中
- 以后每次获取直接从容器(map.get())中获取
多实例
多例模式下(prototype)
- IOC容器启动并不会调用方法创建对象放在容器中
- 每次获取的时候才会调用方法创建对象
组件注册 05 @Lazy 懒加载
单实例bean:默认在容器启动的时候创建对象
多实例bean:容器启动的时候不创建对象 第一次使用(获取)Bean的时候创建对象 并初始化
测试代码
@Test
void scopeTest2(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
System.out.println("容器创建完毕");
System.out.println("准备获取bean...");
Object person = applicationContext.getBean("person");
System.out.println("创建之后获取的 "+person.toString());
}
单实例 测试
默认情况
@Configuration
public class MainConfig {
//给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
@Bean("person")
public Person getPerson(){
System.out.println("准备创建person...");
return new Person("qd",21);
}
}
运行结果
加上懒加载 @Lazy
@Configuration
public class MainConfig {
//给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
@Bean("person")
@Lazy
public Person getPerson(){
System.out.println("准备创建person...");
return new Person("qd",21);
}
}
多实例 测试
@Configuration
public class MainConfig {
//给容器注入一个bean,类型为返回值的类型,id默认使用方法的id
@Bean("person")
@Scope("prototype")
public Person getPerson(){
System.out.println("准备创建person...");
return new Person("qd",21);
}
}
组件注册 06 @Conditional 按照条件注册bean
SpringBoot底层大量使用的注解,表示按照一定的条件进行判断,若满足条件则给容器中注册bean
@Conditional可以标注在类上 也可以标注在方法上
@Test
void conditionTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("容器创建完毕");
System.out.println("准备获取bean...");
Map<String, Person> beansOfType = applicationContext.getBeansOfType(Person.class);
for (String person:beansOfType.keySet()){
System.out.println(beansOfType.get(person));
}
}
MainConfig2.java
@Configuration
public class MainConfig2 {
// 由于容器中已经有了一个名为"person"的bean, 所以如果再注入"person"的时候会报错 需要修改下注入的bean的名称
// @Scope("prototype")
@Bean("person2")
public Person person(){
System.out.println("person2创建中...");
return new Person("qd2",22);
}
@Bean("bill")
public Person person1(){
System.out.println("bill创建中...");
return new Person("bill",62);
}
@Bean("linus")
public Person person2(){
System.out.println("linus创建中...");
return new Person("linus",48);
}
}
加上条件判断
LinuxCondition.java
package com.example.annotations.condition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取IOC使用的beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 获取类加载器
ClassLoader classLoader = context.getClassLoader();
// 获取bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
// 获取当前环境信息
Environment environment = context.getEnvironment();
// 动态获取环境变量的值
String property = environment.getProperty("os.name");
if (property.contains("Linux")){
System.out.println("the running environment is 'Linux'");
return true;
}
return false;
}
}
注意
- 实现
Condition
接口的时候注意导的包- 需要实现的方法:
matches
参数解释:
- ConditionContext:判断条件能使用的上下文环境
- AnnotationTypeMetadata:注释信息
BeanDefinitionRegistry registry = context.getRegistry();
所有bean定义都在这个类,也可以用其来注册/移除/查询/一个bean定义
WindowsCondition.class
package com.example.annotations.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取运行时环境
Environment environment = context.getEnvironment();
// 动态获取环境变量的值
String property = environment.getProperty("os.name");
// 匹配环境
if (property.contains("Windows")){
System.out.println("the running environment is 'Windows'");
return true;
}
return false;
}
}
@Configuration
public class MainConfig2 {
// 由于容器中已经有了一个名为"person"的bean, 所以如果再注入"person"的时候会报错 需要修改下注入的bean的名称
@Bean("person2")
public Person person(){
System.out.println("person2创建中...");
return new Person("qd2",22);
}
@Conditional(WindowsCondition.class)
@Bean("bill")
public Person person1(){
System.out.println("bill创建中...");
return new Person("bill",62);
}
@Conditional(LinuxCondition.class)
@Bean("linus")
public Person person2(){
System.out.println("linus创建中...");
return new Person("linus",48);
}
}
运行结果;
组件注册 07 @Import 给容器快速导入一个组件
给容器注册组件:
- 包扫描 + 组件标注注解(@Controller @Service @Repository @Component),局限于自己写的
- @Bean(可导入第三方包里的组件)
- @Import(快速给容器中导入组件)
- @Import([要导入容器中的组件]):容器中就会自动注册这个组件,id默认为全类名
@Import(Color.class)
@Import({Color.class,Red.class})
- 实现ImportSelector接口:返回需要导入的组件的全类名数组
- 实现ImportBeanDefinitionRegistrar接口:手动注册bean到容器中
- 使用Spring提供的FactoryBean工厂
配置类 MainConfig3.class
测试
@Test
void importTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
System.out.println("容器创建完毕");
System.out.println("准备获取bean...");
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String name:beanDefinitionNames){
System.out.println(name);
}
}
ImportSelector 测试
Color.java
package com.example.annotations.bean;
public class Color {
}
Red.java
package com.example.annotations.bean;
public class Red {
}
MyImportSelector.java
package com.example.annotations.importselector;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* 自定义逻辑返回需要导入的组件
*/
public class MyImportSelector implements ImportSelector {
// 返回值:导入到容器中的组件 (全类名)
// AnnotationMetadata:当前标注@Import注解的类的所有注解信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 不能返回 null 可以是空数组
return new String[]{"com.example.annotations.bean.Color","com.example.annotations.bean.Red"};
}
}
运行结果:
实现ImportBeanDefinitionRegistrar接口
MyImportBeanDefinitionRegistrar.class
package com.example.annotations.importselector;
import com.example.annotations.bean.RainBow;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
*
* @param importingClassMetadata 当前类的注解信息
* @param registry BeanDefinition的注册类 负责将bean添加进容器
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 指定bean定义信息(Bean的类型 Bean的作用域 ...)
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainBow.class);
// 注册:指定bean名
if (!registry.containsBeanDefinition("rainbow")){
System.out.println(" start create 'rainbow' ...");
registry.registerBeanDefinition("rainbow",rootBeanDefinition);
}else {
System.out.println("'rainbow' already exists ");
}
}
}
运行结果
使用FatoryBean
普通bean
将bean导入到容器后,容器会调用其无参构造器,默认创建一个对象注册在容器中
FactoryBean
- 是一个接口
- 整合第三方框架的时候用的多
- 默认获取的是工厂bean调用getObject创建的对象
- 要获取工厂bean本身,需要给id前加一个
&
需要实现方法
getObject()
getObjectType()
isSingleton() :默认是true 单实例
实例测试
ColorFactoryBean.java
package com.example.annotations.bean;
import org.springframework.beans.factory.FactoryBean;
/**
* 创建一个Spring定义的FactoryBean
*/
public class ColorFactoryBean implements FactoryBean<Color> {
/**
* 返回分Color对象会被添加到容器中
* @return Color
* @throws Exception
*/
@Override
public Color getObject() throws Exception {
return new Color();
}
/**
*
* @return
*/
@Override
public Class<?> getObjectType() {
return Color.class;
}
/**
* true: 单实例 在容器中保存一份
* false:多实例 每获取一次就会创建一个新bean
* @return
*/
@Override
public boolean isSingleton() {
return true;
}
}
MainConfig2.java
@Configuration
public class MainConfig2 {
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
}
测试
@Test
void factoryBeanTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("容器创建完毕");
System.out.println("准备获取bean...");
Object bean1 = applicationContext.getBean("colorFactoryBean");//Color.class
Object bean2 = applicationContext.getBean("colorFactoryBean");//Color.class
System.out.println("bean1 == bean2? => " + (bean1 == bean2));
}
测试结果
现将ColorFactoryBean.java中的isSingleton改为false,再次运行
查看获取的类型
@Test
void factoryBeanTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("容器创建完毕");
System.out.println("准备获取bean...");
Object bean1 = applicationContext.getBean("colorFactoryBean");//Color.class 获取的是工厂bean调用
Object bean2 = applicationContext.getBean("&colorFactoryBean");//ColorFactoryBean.class
System.out.println("bean1 == bean2? => " + (bean1 == bean2));
System.out.println("bean1 " + bean1.getClass());
System.out.println("bean2 " + bean2.getClass());
}
生命周期 01 @Bean指定初始化和销毁方法
bean生命周期:bean创建 – 初始化 – 销毁
容器管理bean的生命周期
可自定义初始化和销毁方法:容器在bean进行到当前生命周期的时候来调用自定义的初始化和销毁方法
package com.example.annotations.bean;
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 ...");
}
}
指定初始化和销毁方法(传统方式)
beans.xml
<bean id="bean_xml_car" class="com.example.annotations.bean.Car" init-method="init" destroy-method="destroy"/>
使用注解
scope采用默认
@Configuration
public class MainConfigOfLifeCycle {
@Bean(initMethod = "init" ,destroyMethod = "destroy")
public Car car(){
return new Car();
}
}
测试
@Test
void lifeCycleTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完毕");
}
测试结果
现在test中容器关闭
@Test
void lifeCycleTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完毕");
//关闭容器
applicationContext.close();
}
scope改为prototype
MainConfigOfLifeCycle
@Configuration
public class MainConfigOfLifeCycle {
@Bean(value = "car4LifeCycle",initMethod = "init" ,destroyMethod = "destroy")
@Scope("prototype")
public Car car(){
return new Car();
}
}
测试
@Test
void lifeCycleTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完毕");
//关闭容器
applicationContext.close();
}
测试结果
手动获取bean
test
@Test
void lifeCycleTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完毕");
//获取bean
System.out.println("准备获取bean");
Object car4LifeCycle = applicationContext.getBean("car4LifeCycle");
System.out.println("获取完毕");
//关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
运行结果
可看出,prototype下 不会调用销语句
Notes
构造(对象创建)
- 单实例:容器启动的时候创建对象
- 多实例:每次获取的时候创建对象
初始化
对象创建完成并赋值完成 调用初始化方法
销毁
- 单实例:容器关闭的时候
- 多实例:容器不会管理这个bean;销毁方法不会由容器调用
单实例
- 容器启动的时候创建对象
- 会在创建容器完毕前 获取bean并完成初始化方法
- 容器关闭的时候会调用bean的销毁方法
多实例
- 容器创建的时候并不会创建或者调用初始化
- 每次获取的时候创建对象 调用初始化
- 销毁:容器不会管理这个bean,销毁方法不会由容器来调用
生命周期 02 实现InitializingBean, DisposableBean接口
Cat.java
package com.example.annotations.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
/**
* 生命周期测试
*/
public class Cat implements InitializingBean, DisposableBean {
/**
* 构造
*/
public Cat() {
System.out.println("cat constructor ...");
}
/**
* 做初始化方法
* 初始化方法不可以有参数 但可以抛异常
*/
public void init() {
System.out.println("cat init ...");
}
/**
* 初始化设置完属性后
* 来源于 InitializingBean
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("cat afterPropertiesSet ...");
}
/**
* 销毁的时候调用
* 来源于 DisposableBean
* @throws Exception
*/
@Override
public void destroy() throws Exception {
System.out.println("cat destroy ...");
}
public void destroyMethod() {
System.out.println("cat destroyMethod ...");
}
}
MainConfigOfLifeCycle.class中添加以下方法
@Bean(value = "cat4LifeCycle",initMethod = "init",destroyMethod = "destroyMethod")
public Cat cat(){
return new Cat();
}
测试
@Test
void lifeCycleTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完毕");
//获取bean
System.out.println("准备获取bean");
Object car4LifeCycle = applicationContext.getBean("cat4LifeCycle");
System.out.println("获取完毕");
//关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
运行结果:
生命周期 03 @PostConstruct & @PreDestroy
使用JSR250
@PostConstruct:在bean创建完成并且属性赋值完成后执行此方法
@PreDestroy:在容器销毁bean之前通知进行清理工作
Dog.java
package com.example.annotations.bean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* 测试
* @PostConstruct
* @PreDestroy
*/
public class Dog {
public Dog() {
System.out.println("dog construct ...");
}
/**
* 对象创建并赋值之后调用
*/
@PostConstruct
public void init(){
System.out.println("dog @PostConstruct ...");
}
/**
* 容器移除对象前调用
*/
@PreDestroy
public void destroy(){
System.out.println("dog @PreDestroy ...");
}
}
配置新加
@Bean(value = "dog4LifeCycle")
public Dog dog(){
return new Dog();
}
测试
@Test
void lifeCycleTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完毕");
//关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
结果
现添加之前的生命周期方法
package com.example.annotations.bean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* 测试
* @PostConstruct
* @PreDestroy
*/
public class Dog {
public Dog() {
System.out.println("dog construct ...");
}
/**
* 做初始化方法
* 初始化方法不可以有参数 但可以抛异常
*/
public void init() {
System.out.println("dog init ...");
}
/**
* 对象创建并赋值之后调用
*/
@PostConstruct
public void postConstruct(){
System.out.println("dog @PostConstruct ...");
}
/**
* 容器移除对象前调用
*/
@PreDestroy
public void preDestroy(){
System.out.println("dog @PreDestroy ...");
}
/**
* 做销毁方法
* 销毁方法不可以有参数 但可以抛异常
*/
public void destroyMethod() {
System.out.println("dog destroyMethod ...");
}
}
配置类:
@Bean(value = "dog4LifeCycle",initMethod = "init",destroyMethod = "destroyMethod")
public Dog dog(){
return new Dog();
}
测试结果
扩展 JSR250
原文连接
-
简介
JSR 250(Java Specification Requests
),Java注解规范,定义了一系列基于Java EE和Java SE通用注解。它避免了不同框架或组件间重复(或冗余)的注解。JSR 250正式发布于2006年5月11日。随着申明式注解配置被越来越多地应用在Java框架(比如Spring),JSR 250可能在未来会持续增长,所以在使用诸如Spring之类的框架时,尽量使用JSR 250中定义的注解,避免和特定框架紧耦合。 -
注解列表
注解 作用 @Generated 标记该资源是自动生成的 @Resource 定义了对某个资源的引用 @Resources 容器针对多资源的注解 @PostConstruct 标记在方法上用于依赖注入的初始化动作 @PreDestroy 标记在方法上用于该对象实例从容器销毁时的前置操作 @Priority 定义了该类执行的优先级 如拦截器的优先级 @RunAs 定义了应用在Java EE容器运行时的角色 @RolesAllowed 定义了什么安全角色被允许执行方法 @PermitAll 标记在类或方法上允许所有角色执行(所有)方法 @DenyAll 标记在方法上不允许所有角色调用 @DeclareRoles 用于定义系统的安全角色 @DataSourceDefinition 定义一个数据源,并通过JNDI注册到容器 @ManagedBean 定义一个被容器管理的对象
生命周期 04 BeanPostProcessor 后置处理器
BeanPostProcessor是一个接口:bean的后置处理器
在bean初始化前后进行一些处理工作
postProcessBeforeInitialization:初始化之前调用
postProcessAfterInitialization:初始化之后调用
自定义后置处理器
package com.example.annotations.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
/**
* 测试 BeanPostProcessor(后置处理器)
* 初始化前后进行处理工作
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 初始化之前调用
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization === " + beanName +" => " + bean );
return bean;
}
/**
* 初始化之后调用
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization === " + beanName +" => " + bean );
return bean;
}
}
配置类
@Import(MyBeanPostProcessor.class) :
- 将MyBeanPostProcessor纳入到容器中;
- 这样该容器中的所有bean在初始化前后都会执行该后置处理器中的逻辑,即使未定义初始化方法这些逻辑也会执行的
@Configuration
@Import(MyBeanPostProcessor.class) //将MyBeanPostProcessor纳入到容器中;这样该容器中的所有bean在初始化前后都会执行该后置处理器中的逻辑,即使未定义初始化方法这些逻辑也会执行的
public class MainConfigOfLifeCycle {
@Bean(value = "dog4LifeCycle",initMethod = "init",destroyMethod = "destroyMethod")
public Dog dog(){
return new Dog();
}
}
测试
@Test
void lifeCycleTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完毕");
//关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
运行结果
属性赋值 01 @Value赋值
传统方式
public class Person {
private String name;
private Integer age;
private String nickName;
public Person() {
}
public Person(String name, Integer age, String nickName) {
this.name = name;
this.age = age;
this.nickName = nickName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", nickName='" + nickName + '\'' +
'}';
}
}
beans.xml
<context:property-placeholder location="classpath:person.properties" />
<bean id="person4PropertyValues" class="com.example.annotations.bean.Person">
<property name="age" value="21"/>
<property name="name" value="qd"/>
<property name="nickName" value="${person.nickName}"/>
</bean>
配置文件
person.properties
person.nickName=Aimer
测试
@Test
void xmlPropertyValuesTest(){
//传统方式
ClassPathXmlApplicationContext xmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
System.out.println("容器创建完毕");
//获取bean
System.out.println("准备获取bean");
Object person4PropertyValues = xmlApplicationContext.getBean("person4PropertyValues");
System.out.println(person4PropertyValues.toString());
System.out.println("获取完毕");
//关闭容器
xmlApplicationContext.close();
System.out.println("容器已关闭");
}
运行结果:
注解方式
- 基本数值
- SPEL表达式 : #{22-1} => 21
- ${} :取出配置文件的值 [在运行环境变量中的值]
Person.java
public class Person {
@Value("qd")
private String name;
@Value("#{22-1}")
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
配置类
@Configuration
public class MainConfig4PropertyValues {
@Bean("person4PropertyValues")
public Person person(){
return new Person();
}
}
测试
@Test
void propertyValuesTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4PropertyValues.class);
System.out.println("容器创建完毕");
//获取bean
System.out.println("准备获取bean");
Object person4PropertyValues = applicationContext.getBean("person4PropertyValues");
System.out.println(person4PropertyValues.toString());
System.out.println("获取完毕");
//关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
运行结果
注意 无论是beans.xml 还是配置类 都要保证bean的名字不重复
属性赋值 02 @PropertySource赋值
使用@PropertySource读取外部配置文件中的 k-v 保存到运行的环境变量中
加载完外部的配置文件以后 使用${}
取出配置文件中的值
配置类
package com.example.annotations.config;
import com.example.annotations.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource(value = {"classpath:/person.properties"})
public class MainConfig4PropertyValues {
@Bean("person4PropertyValues")
public Person person(){
return new Person();
}
}
Person.java
package com.example.annotations.bean;
import org.springframework.beans.factory.annotation.Value;
public class Person {
@Value("qd")
private String name;
@Value("21")
private Integer age;
@Value("${person.nickName}")
private String nickName;
public Person(String name, Integer age, String nickName) {
this.name = name;
this.age = age;
this.nickName = nickName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", nickName='" + nickName + '\'' +
'}';
}
}
配置文件
person.properties
person.nickName=Aimer
测试
@Test
void propertyValuesTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4PropertyValues.class);
System.out.println("容器创建完毕");
//获取bean
System.out.println("准备获取bean");
Object person4PropertyValues = applicationContext.getBean("person4PropertyValues");
System.out.println(person4PropertyValues.toString());
System.out.println("获取完毕");
//关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
运行结果
扩展:
@Test
void propertyValuesTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4PropertyValues.class);
System.out.println("容器创建完毕");
// //获取bean
// System.out.println("准备获取bean");
// Object person4PropertyValues = applicationContext.getBean("person4PropertyValues");
// System.out.println(person4PropertyValues.toString());
// System.out.println("获取完毕");
//运行环境测试输出
System.out.println("运行环境测试输出");
ConfigurableEnvironment environment = applicationContext.getEnvironment();
String property = environment.getProperty("person.nickName");
System.out.println("person.nickName" + " => " + property);
//关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
运行结果
- 可以使用
@ProPertySources注解
指定多个PropertySource
自动装配 01 @Autowired & @Qualifier & @Primary
自动装配:Spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值
-
常用@Autowired
- 默认优先按照类型去容器中找对应的组件
- 若ioc中有多个同类型的bean,再将属性名称作组件id去容器中查找(会报错 )
-
也可以使用
@Qualifier("[beanName]")
来指定注入bean,这种情况下可以存在相同类型的多个bean‘ -
如果没有bean,会报错 自动装配默认一定要将属性赋值好,否则会报错
可以修改这种默认设置:
@Autowired(required=false)
多数据源的时候用的多 -
@Primary:让Spring进行自动装配的时候默认使用首选的bean,也可以继续使用
@Qulifier
指定需要装配的bean的名字优先级:@Qulifier > @Primary
@Primary测试
常用@Autowired
BookDao.java
@Repository
public class BookDao {
private Integer lable = 1;
public Integer getLable() {
return lable;
}
public void setLable(Integer lable) {
this.lable = lable;
}
@Override
public String toString() {
return "BookDao{" +
"lable=" + lable +
'}';
}
}
BookService.java
@Service
public class BookService {
@Autowired
private BookDao booKDao;
@Override
public String toString() {
return "BookService{" +
"booKDao=" + booKDao +
'}';
}
}
BookController.java
@Controller
public class BookController {
@Autowired
private BookService bookService;
}
配置类
MainConfig4Autowire.java
package com.example.annotations.config;
import com.example.annotations.dao.BookDao;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
@ComponentScan(value = {"com.example.annotations.service","com.example.annotations.dao","com.example.annotations.controller"})
public class MainConfig4Autowire {
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLable(2);
return bookDao;
}
}
测试:
@Test
void autowiredTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4Autowire.class);
System.out.println("容器创建完毕");
//获取bean
System.out.println("准备获取bean");
BookService bookService = applicationContext.getBean(BookService.class);
System.out.println(bookService.toString());
System.out.println("获取完毕");
//关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
运行结果:
配置类不加 @Primary
的时候,启动测试会报错,因为有两个BookDao的bean;配置类如果不指定注入的话,运行结果如下:
@Qualifier测试
配置类:
@Configuration
@ComponentScan(value = {"com.example.annotations.service","com.example.annotations.dao","com.example.annotations.controller"})
public class MainConfig4Autowire {
// @Primary
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLable(2);
return bookDao;
}
}
BookService.java
@Service
public class BookService {
@Autowired
@Qualifier("bookDao2")
private BookDao booKDao;
@Override
public String toString() {
return "BookService{" +
"booKDao=" + booKDao +
'}';
}
}
运行结果:
自动装配 02 @Resource & @Inject
Spring还支持使用@Resource 属于JSR250
和 @Inject 属于JSR330
@Resource & @Inject
属于java规范的注解
@Autowired
属于Spring的注解
@Resource
- 可以和@Autowired一样实现自动装配功能,默认是按照组件名称装配的
- 没有能支持@Primary 或者 @Autowired(required = false) 或者@Qulifier
@Service
public class BookService {
@Resource
private BookDao bookDao;
@Override
public String toString() {
return "BookService{" +
"booKDao=" + bookDao +
'}';
}
}
运行结果
@Inject
- 需要在maven库中找到 javax.inject 包中的central依赖
- 和**@Autowired**功能相似,但没有required属性
没有实现代码
自动装配 03 方法 构造器位置的自动装配
@Autowired的位置:构造器 方法 参数 属性 都是从容器中获取参数的值
- 方法上:Spring容器创建当前对象就会调用此方法 完成赋值
- 方法使用的参数 自定义类型就会从ioc容器中获取
- 构造器上: 构造器要用的组件都是从容器中获取
- 若bean中只有一个有参构造方法,
@Autowired
注解可以省掉,默认会调用这个参数位置的组件还是会从ioc容器获取
- 若bean中只有一个有参构造方法,
- 参数上:@Bean + 方法参数:默认不写@Autowired ,也能自动装配 也是从ioc容器中获取
Boss.java
@Component
public class Boss {
private Car car;
public Car getCar() {
return car;
}
@Autowired
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Boss{" +
"car=" + car +
'}';
}
}
Car.java
/**
* 生命周期测试
* 自动装配测试
*/
@Component
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 ...");
}
}
配置类
@Configuration
@ComponentScan(value = {"com.example.annotations.service","com.example.annotations.dao","com.example.annotations.controller","com.example.annotations.bean"})
public class MainConfig4Autowire {
}
测试:
@Test
void autowiredTest(){
//配置类测试
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4Autowire.class);
System.out.println("容器创建完毕");
//获取bean
System.out.println("准备获取bean");
Boss boss = applicationContext.getBean(Boss.class);
Car car = applicationContext.getBean(Car.class);
System.out.println(boss.toString());
System.out.println(car.toString());
System.out.println("获取完毕");
//关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
@Bean + 方法参数
@Component
public class Boss {
private Car car;
public Boss(Car car) {
System.out.println("构造方法注入");
this.car = car;
}
public Car getCar() {
return car;
}
// @Autowired
public void setCar(Car car) {
System.out.println("setter注入");
this.car = car;
}
@Override
public String toString() {
return "Boss{" +
"car=" + car +
'}';
}
}
运行测试之后可看到有相应输出
自动装配 04 Aware 注入Spring底层组件 & 原理
总接口:
Aware
- 自定义组件想要使用Spring容器底层的一些组件(ApplicationContext BeanFactory 等等)
- 需要让自定义组件实现xxAware接口:在创建对象的时候会调用接口规定的方法注入相关组件 总体参照
Aware
接口 ,把Spring底层一些组件注入到自定义的bean中 - xxxAware功能:使用xxProcessor 如ApplicationContextAware ==》 ApplicationContextAwareProcessor,利用后置处理器判断实现了哪个接口,就将Bean转成相应的接口类型,再选择注入相应类型的applicationContext
/**
* 生命周期 感知接口测试
*/
@Component//注入后自动调用实现的感知接口方法
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
@Override
public void setBeanName(String name) {
System.out.println("调用了 setBeanName");
System.out.println("当前bean的名字 " + name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
System.out.println("调用了 setApplicationContext");
}
/**
* 解析占位符
* @param resolver
*/
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
System.out.println("调用了 setEmbeddedValueResolver");
String resolveString = resolver.resolveStringValue(" hello ${os.name} ; age #{22-1}");
System.out.println("解析的字符串 " + resolveString);
}
}
配置类
@Configuration
@ComponentScan(value = {"com.example.annotations.service","com.example.annotations.dao","com.example.annotations.controller","com.example.annotations.bean"})
public class MainConfig4Autowire {
}
测试
@Test
void autowiredTest(){
//配置类测试
System.out.println("准备创建容器");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4Autowire.class);
System.out.println("容器创建完毕");
//关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
测试结果:
自动装配 05 @Profile环境搭建
和condition
相似
@Profile:Spring为我们提供的可以根据当前环境动态的激活和切换一系列组件的功能 也可以根据环境切换数据源
dev
test
prov
@Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定的时候任何环境下都能注册这个组件
- 加了环境标识的bean 只有这个环境被激活的时候才能注册到容器中。
- 写在配置类上,只有是指定的环境的时候,整个配置类中的所有配置才能开始生效
- 没有标注环境标识的bean,任何环境下都是加载的。默认是
default
修改运行环境的几种方式
- 使用命令行动态参数:在虚拟机参数位置加载
-Dspring.profiles.active=[运行环境]
:-Dspring.profiles.active=test
- 手动代码设置
- 获取ApplicationContext :注意要使用无参方式获取,否则刷新之后还是没有注入的状态(具体可查看源码分析)
- 设置需要激活的环境
- 注册主配置类
- 启动刷新容器
代码测试
配置类
/**
* 用于测试数据源和运行环境
* 使用了多种方式设置参数
*/
@PropertySource("classpath:/dbConfig.properties")
@Configuration
public class MainConfig4Profile implements EmbeddedValueResolverAware {
public MainConfig4Profile() {
}
@Value("${db.user}")
private String user;
// 用于EmbeddedValueResolverAware接口测试
private String driverClass;
@Profile("prov")
@Bean("provDataSouce")
public DataSource dataSource4Prov() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/sb_anno_prov?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8");
// 加载驱动的时候会抛异常
dataSource.setDriverClass("com.mysql.jdbc.Driver");
return dataSource;
}
@Profile("dev")
@Bean("devDataSouce")
public DataSource dataSource4Dev() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("${db.user}");
dataSource.setPassword("${db.password}");
dataSource.setJdbcUrl("${db.user}");
dataSource.setDriverClass("${db.driverClass}");
return dataSource;
}
@Profile("test")
@Bean("testDataSouce")
public DataSource dataSource4Test(@Value("${db.password}") String pwd) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/sb_anno_test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8");
dataSource.setDriverClass(driverClass);
return dataSource;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.driverClass = resolver.resolveStringValue("${db.driverClass}");
System.out.println("解析后的driverClass " + driverClass);
}
}
dbConfig.properties
db.user=root
db.password=
db.driverClass=com.mysql.jdbc.Driver
pom.xml
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- 注意版本问题,本地安装的是5.几的版本,而springboot默认导入的是最新的,需要手动指定版本号-->
<version>5.1.46</version>
<scope>runtime</scope>
</dependency>
测试
@Test
void profileTest(){
//配置类测试
System.out.println("准备创建容器");
// 1. 获取ApplicationContext 注意要使用无参构造器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
System.out.println("容器创建完毕");
// 2. 设置需要激活的环境
applicationContext.getEnvironment().setActiveProfiles("test","dev");
// 3. 注册配置类
applicationContext.register(MainConfig4Profile.class);
// 4. 启动刷新容器
applicationContext.refresh();
System.out.println("容器刷新完毕");
// 获取bean
String[] beanNamesForType = applicationContext.getBeanNamesForType(DataSource.class);
for (String name : beanNamesForType) {
System.out.println("当前环境下注入的bean" + name);
}
// 关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
运行结果:
AnnotationConfigApplicationContext.java部分源码(无参构造):
public AnnotationConfigApplicationContext() {
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
AnnotationConfigApplicationContext.java部分源码(有参构造):
/**
* 有参方式调用的时候,最后的refresh相当于没有设置
*/
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
register(componentClasses);
refresh();
}
AOP 01 AOP功能测试
AOP:动态代理
指在程序运行期间动态的将某段代码切入到指定位置进行运行的编程方式
步骤
- 导入AOP模块
- 定义业务逻辑类:在业务逻辑运行的时候打印日志(方法前后 异常 …)
- 定义日志切面类:切面里的方法需动态感知目标方法运行到哪里然后执行
- 给切面类的目标方法标注何时何地运行(通知注解)
- 将切面类和业务逻辑类(目标方法所在类)都加到容器中
- 必须告诉Spring哪个是切面类(给切面类加上
@Aspect
注解) - 给配置类中的@EnableAspectJAutoProxy开启基于注解的AOP模式
Spring中很多的@EnableXXX表示用来开启某一功能
通知注解
作用 | 注解 | 备注 |
---|---|---|
前置通知 | @Before | |
后置通知 | @After | 无论正常结束还是异常结束都会调用 |
返回通知 | @AfterReturning | 正常返回后运行 |
异常通知 | @AfterThrowing | |
环绕通知 | @Around | 动态代理 手动推进目标方法运行joinPoint.proceed() 相当于一个最底层的通知 |
代码测试
业务逻辑类
MathCaculator.java
/**
* @Classname MathCaculator
* @Description AOP测试_业务逻辑类
*/
public class MathCaculator {
public int div(int i,int j){
System.out.println("MathCaculator 运行 div() ...");
return i/j;
}
}
切面类
/**
* @Classname LogAspects
* @Description 切面类
* @Date 2021/8/5 10:34
* @Created by LXLDEMO
*/
@Aspect
public class LogAspects {
/**
* 抽取公共的切入点表达式
* 指向目标方法
* 1. 本类引用
* 2. 其他的切面引用
*/
@Pointcut("execution(public int com.example.annotations.bean.MathCaculator.div(int ,int ))")
public void pointCut(){
}
@Before("pointCut()")
public void logStart(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
System.out.println("运行@Before");
System.out.println("方法名字 " + joinPoint.getSignature().getName());
System.out.println("方法参数 " + Arrays.toString(args));
}
/**
* 注解中参数的这种写法属于其他切面的调用方式
* 全包名
* @param joinPoint
*/
@After("com.example.annotations.aop.LogAspects.pointCut()")
public void LogEnd(JoinPoint joinPoint){
System.out.println("运行@After " + joinPoint.getSignature().getName());
System.out.println(joinPoint.getSignature().getName() + "运行结束");
}
/**
* 当有多个参数的时候 JoinPoint必须作为第一个参数
* @param joinPoint
* @param result
*/
@AfterReturning(value = "pointCut()",returning = "result")
public void logReturning(JoinPoint joinPoint,Object result){
System.out.println("运行@AfterReturning");
System.out.println(joinPoint.getSignature().getName()+"正常返回" + result);
}
/**
* 当有多个参数的时候 JoinPoint必须作为第一个参数
* @param joinPoint
* @param exception
*/
@AfterThrowing(value = "pointCut()",throwing = "exception")
public void logException(JoinPoint joinPoint,Exception exception){
System.out.println("运行@AfterThrowing");
System.out.println("" + joinPoint.getSignature().getName() +" === 异常信息:"+exception);
}
}
配置类
/**
* @Classname MainConfig4AOP
* @Description 配置类
* @Date 2021/8/5 10:31
* @Created by LXLDEMO
*/
@EnableAspectJAutoProxy
@Configuration
public class MainConfig4AOP {
@Bean
public MathCaculator caculator(){
System.out.println("准备注入 MathCaculator ...");
return new MathCaculator();
}
@Bean
public LogAspects logAspects(){
System.out.println("准备注入 LogAspects ...");
return new LogAspects();
}
}
测试
@Test
void Aoptest(){
//配置类测试
System.out.println("准备创建容器");
// 注意要使用无参构造器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig4AOP.class);
System.out.println("容器创建完毕");
// 获取bean
MathCaculator mathCaculator = applicationContext.getBean(MathCaculator.class);
mathCaculator.div(1,1);
// 关闭容器
applicationContext.close();
System.out.println("容器已关闭");
}
测试结果
将参数换成(1,0)
to be continued …