介绍
代码仓库地址:https://gitee.com/CandyWall/spring-source-study
跟着黑马满一航老师的spring高级49讲做的学习笔记,本笔记跟视频内容的项目名称和代码略有不同,我将49讲的代码每一讲的代码都拆成了独立的springboot项目,并且项目名称尽量做到了见名知意,都是基于我自己的考量,代码都已经过运行验证过的,仅供参考。
视频教程地址:https://www.bilibili.com/video/BV1P44y1N7QG
注:
1. 每一讲对应一个二级标题,每一个三级标题是使用子项目名称命名的,和我代码仓库的项目是一一对应的;
2. 代码里面用到了lombok插件来简化了Bean中的get()、set()方法,以及日志的记录的时候用了lombok的@Slf4j注解。
笔记中如有不正确的地方,欢迎在评论区指正,非常感谢!!!
每个子项目对应的视频链接以及一些重要内容的笔记
第一讲 BeanFactory
与ApplicationContext
的区别与联系
spring_01_beanfactory_applicationcontext_differences_connections
p1 000-Spring高级49讲-导学
p2 001-第一讲-BeanFactory与ApplicationContext_1
测试代码:
@SpringBootApplication@Slf4jpublic class A01Application { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args); // class org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext System.out.println(context.getClass()); }}
到底什么是BeanFactory
它是
ApplicationContext
的父接口鼠标选中
ConfigurableApplicationContext
,按Ctrl + Shift + U
或者Ctrl + Alt + U
打开类图,可以看到ApplicationContext
的有个父接口是BeanFactory
它才是
Spring
的核心容器,主要的ApplicationContext
实现都 [组合]了他的功能打印
context.getClass()
,可以看到SpringBoot的启动程序返回的ConfigurableApplicationContext
的具体的实现类是AnnotationConfigServletWebServerApplicationContext
ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContextSystem.out.println(context.getClass());
按图索骥,
AnnotationConfigServletWebServerApplicationContext
又间接继承了GenericApplicationContext
,在这个类里面可以找到beanFactory
作为成员变量出现。
查看springboot
默认的ConfigurableApplicationContext
类中的BeanFactory
的实际类型
ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContextConfigurableListableBeanFactory beanFactory = context.getBeanFactory();// 查看实际类型// class org.springframework.beans.factory.support.DefaultListableBeanFactorySystem.out.println(beanFactory.getClass());
从打印结果可以了解到实际类型为DefaultListableBeanFactory
,所以这里以BeanFactory
的一个实现类DefaultListableBeanFactory
作为出发点,进行分析。
它的类图如下:
这里我们暂且不细看DefaultListableBeanFactory
,先看DefaultListableBeanFactory
的父类DefaultSingletonBeanFactory
,先选中它,然后按F12
,可以跳转到对应的源码,可以看到有个私有的成员变量singletonObjects
这里通过反射的方法来获取该成员变量,进行分析
先补充一下反射获取某个类的成员变量的步骤:
获取成员变量,步骤如下:
获取Class对象
获取构造方法
通过构造方法,创建对象
获取指定的成员变量(私有成员变量,通过setAccessible(boolean flag)方法暴力访问)
通过方法,给指定对象的指定成员变量赋值或者获取值
public void set(Object obj, Object value)
在指定对象obj中,将此 Field 对象表示的成员变量设置为指定的新值
public Object get(Object obj)
返回指定对象obj中,此 Field 对象表示的成员变量的值
代码如下:
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");// 设置私有变量可以被访问singletonObjects.setAccessible(true);ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);// 查看实际类型// class org.springframework.beans.factory.support.DefaultListableBeanFactorySystem.out.println(beanFactory.getClass());map.entrySet().stream().filter(entry -> entry.getKey().startsWith("component")).forEach(System.out::println);
这里singletonObjects.get(beanFactory)
为什么要传一个ConfigurableListableBeanFactory
的变量进去呢?打印了这个beanFactory
的实际类型为DefaultListableBeanFactory
,查看其类图,可以了解到该类也实现了DefaultSingletonBeanRegistry
接口,所以这里反射获取某个类的成员变量的get()
方法中可以作为参数传进来。
MessageSource
在
resources
目录下创建四个文件messages.propertes
、messages_en.properties
、messages_ja.properties
、messages_zh.properties
,然后分别在四个文件里面定义同名的key
,比如在message_en.properties
中定义hi=hello
,在messages_ja.propertes
中定义hi=こんにちは
,在messages_zh
中定义hi=你好
,这样在代码中就可以根据这个**key hi
和不同的语言类型**获取不同的value
了。System.out.println(context.getMessage("hi", null, Locale.CHINA));System.out.println(context.getMessage("hi", null, Locale.ENGLISH));System.out.println(context.getMessage("hi", null, Locale.JAPANESE));
运行结果如下:
例2:获取spring
相关jar
包中的spring.factories
配置文件
resources = context.getResources("classpath*:META-INF/spring.factories");for (Resource resource : resources) { System.out.println(resource);}
EnvironmentCapable
获取系统环境变量中的java_home
和项目的application.yml
中的server.port
属性
System.out.println(context.getEnvironment().getProperty("java_home"));System.out.println(context.getEnvironment().getProperty("server.port"));
p7 006-第一讲-小结
第二讲 BeanFactory
和 ApplicationContext
类的重要实现类
spring_02_01_beanfactory_impl
p8 007-第二讲-BeanFactory实现
p9 008-第二讲-BeanFactory实现
p10 009-第二讲-BeanFactory实现-后处理器排序
DefaultListableBeanFactory
接着第一讲中的内容,执行以下代码,可以了解到
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContextConfigurableListableBeanFactory beanFactory = context.getBeanFactory();// 查看实际类型// class org.springframework.beans.factory.support.DefaultListableBeanFactorySystem.out.println(beanFactory.getClass());
ConfigurableApplicationContext
类内部组合的BeanFactory
实际类型为DefaultListableBeanFactory
,spring
底层创建实体类就是依赖于这个类,所以它是BeanFactory
接口最重要的一个实现类,下面使用这个类,模拟一下spring
使用DefaultListableBeanFactory
类创建其他实体类对象的过程。
测试代码如下:
package top.jacktgq.spring_02_beanfactory_impl;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.BeanPostProcessor;import org.springframework.beans.factory.support.AbstractBeanDefinition;import org.springframework.beans.factory.support.BeanDefinitionBuilder;import org.springframework.beans.factory.support.DefaultListableBeanFactory;import org.springframework.context.annotation.AnnotationConfigUtils;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;import java.util.ArrayList;import java.util.stream.Collectors;/** * @Author CandyWall * @Date 2022/3/24--21:20 * @Description */public class TestBeanFactory { public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); // bean 的定义(即bean的一些描述信息,包含class:bean是哪个类,scope:单例还是多例,初始化、销毁方法等) AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition(); beanFactory.registerBeanDefinition("config", beanDefinition); // 给 BeanFactory添加一些常用的后处理器,让它具备解析@Configuration、@Bean等注解的能力 AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory); // 从bean工厂中取出BeanFactory的后处理器,并且执行这些后处理器 // BeanFactory 后处理器主要功能,补充了一些 bean 的定义 beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> { System.out.println(beanFactoryPostProcessor); beanFactoryPostProcessor.postProcessBeanFactory(beanFactory); }); // 打印BeanFactory中Bean for (String name : beanFactory.getBeanDefinitionNames()) { System.out.println(name); } // 从BeanFactory中取出Bean1,然后再从Bean1中取出它依赖的Bean2 // 可以看到结果为null,所以@Autowired注解并没有被解析 // Bean1 bean1 = beanFactory.getBean(Bean1.class); // System.out.println(bean1.getBean2()); // 要想@Autowired、@Resource等注解被解析,还要添加Bean的后处理器,可以针对Bean的生命周期的各个阶段提供扩展 // 从bean工厂中取出Bean的后处理器,并且执行这些后处理器 // BeanFactory 后处理器主要功能,补充了一些 bean 的定义 // beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor); // beanFactory.addBeanPostProcessors(beanFactory.getBeansOfType(BeanPostProcessor.class).values()); // 改变Bean后处理器加入BeanFactory的顺序 // 写法1: // ArrayList list = new ArrayList(beanFactory.getBeansOfType(BeanPostProcessor.class).values()); // Collections.reverse(list); // beanFactory.addBeanPostProcessors(list); // 写法2: beanFactory.addBeanPostProcessors(beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream().sorted(beanFactory.getDependencyComparator()).collect(Collectors.toCollection(ArrayList::new))); // 准备好所有单例,get()前就把对象初始化好 beanFactory.preInstantiateSingletons(); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>"); Bean1 bean1 = beanFactory.getBean(Bean1.class); System.out.println(bean1.getBean2()); /** * 学到了什么: * a. beanFactory 不会做的事 * 1. 不会主动调用BeanFactory的后处理器 * 2. 不会主动添加Bean的后处理器 * 3. 不会主动初始化单例 * 4. 不会解析BeanFactory,还不会解析 ${}, #{} * * b. Bean后处理器会有排序的逻辑 */ System.out.println(bean1.getInter()); } @Configuration static class Config { @Bean public Bean1 bean1() { return new Bean1(); } @Bean public Bean2 bean2() { return new Bean2(); } @Bean public Bean3 bean3() { return new Bean3(); } @Bean public Bean4 bean4() { return new Bean4(); } } @Slf4j static class Bean1 { @Autowired private Bean2 bean2; public Bean2 getBean2() { return bean2; } @Autowired @Resource(name = "bean4") private Inter bean3; public Inter getInter() { return bean3; } public Bean1() { log.debug("构造 Bean1()"); } } @Slf4j static class Bean2 { public Bean2() { log.debug("构造 Bean2()"); } } interface Inter { } @Slf4j static class Bean3 implements Inter { public Bean3() { log.debug("构造 Bean3()"); } } @Slf4j static class Bean4 implements Inter { public Bean4() { log.debug("构造 Bean4()"); } }}
总结:
beanFactory 不会做的事
不会主动调用BeanFactory的后处理器
不会主动添加Bean的后处理器
不会主动初始化单例
不会解析BeanFactory,还不会解析 ${}, #{}
Bean后处理器会有排序的逻辑
先定义一个接口Inter,再定义两个Bean,名称分别为Bean3和Bean4,都继承Inter,接着在Config中通过@Bean注解将Bean3和Bean4都加进Bean工厂中,然后在Bean1中定义一个Inter对象,通过@Autowired注解将实现类注入进来。
@Configurationstatic class Config { @Bean public Bean1 bean1() { return new Bean1(); } @Bean public Bean2 bean2() { return new Bean2(); } @Bean public Bean3 bean3() { return new Bean3(); } @Bean public Bean4 bean4() { return new Bean4(); }}@Slf4jstatic class Bean1 { @Autowired private Bean2 bean2; public Bean2 getBean2() { return bean2; } @Autowired @Resource(name = "bean4") private Inter bean3; public Inter getInter() { return bean3; } public Bean1() { log.debug("构造 Bean1()"); }}@Slf4jstatic class Bean2 { public Bean2() { log.debug("构造 Bean2()"); }}interface Inter {}@Slf4jstatic class Bean3 implements Inter { public Bean3() { log.debug("构造 Bean3()"); }}@Slf4jstatic class Bean4 implements Inter { public Bean4() { log.debug("构造 Bean4()"); }}
如果把以
Inter
接口声明的变量名定义为inter
,@Autowired
注解首先会**根据名称(byName)进行匹配,没有匹配上,于是又会根据类型(byType)**进行匹配,发现Bean3和Bean4都实现了Inter接口,会报无法自动装配的错误。所以为了避免这种错误,以
Inter
接口声明的变量名只能为bean3
或者bean4
,这里把以Inter
接口声明的变量名定义为bean3
,然后就不报错了,@Autowired
会通过byName
的方式进行匹配。在main方法中去获取Inter,然后打印,可以看到注入的是Bean3
如果此时在
private Inter bean3;
上面再加上@Resource(name = "bean4")
注解,然后再打印结果,结果还是bean3
,为什么呢?我们先看一下加入BeanFactory
的Bean后处理器的顺序,解析@Autowired
注解的后处理器internalAutowiredAnnotationProcessor
的顺序排在解析@Resource
注解的后处理器internalCommonAnnotationProcessor
的前面,所以internalAutowiredAnnotationProcessor
会被BeanFactory
先启用,故@Autowired
注解先被解析了。如果想要让
@Resource
注解先被解析呢,这就需要让后处理器internalCommonAnnotationProcessor
比internalAutowiredAnnotationProcessor
先加入BeanFactory
,代码如下:// 改变Bean后处理器加入BeanFactory的顺序 beanFactory.addBeanPostProcessors(beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream().sorted(beanFactory.getDependencyComparator()).collect(Collectors.toCollection(ArrayList::new)));
这样一来注入的结果就是
Bean4
,@Resource(name = "bean4")
注解被先解析了通过
AnnotationConfigUtils
给beanFactory
添加一些后处理的时候会默认设置比较器,可以对BeanPostProcessor
进行排序,排序的依据是BeanPostProcessor
内部的order
属性,其中internalAutowiredAnnotationProcessor
的order属性的值为Ordered.LOWEST_PRECEDENCE - 2
,internalCommonAnnotationProcessor
的order
属性的值为Ordered.LOWEST_PRECEDENCE - 3
。从打印结果来看,
internalAutowiredAnnotationProcessor:2147483645
,internalCommonAnnotationProcessor:2147483644
,internalCommonAnnotationProcessor
的order
值更小,所以排序的时候会排在前面BeanFactory
本身功能只是将定义好的BeanDefinition
加进来,而BeanFactory
的后处理器BeanFactoryPostProcessor
补充了一些Bean
的定义,可以解析@Configuration
、@Bean
等注解,将这些被注解修饰的Bean
也加进BeanFactory
。@Configuration
和@Bean
注解的解析过程的源码可以看AnnotationConfigUtils
和ConfigurationClassPostProcessor
要想
@Autowired
、@Resource
等注解被解析,还要添加Bean
的后处理器BeanPostProcessor
,可以针对Bean
的生命周期的各个阶段提供扩展。BeanFactory中的对象都是懒加载的,如果不去调用get()方法获取的话,就不会初始化,如果想要让对象在get()之前就创建好,需要调用
beanFactory.preInstantiateSingletons()
方法。教程弹幕中有人问:为啥
@Bean
和@Configration
注解不需要建立联系就能使用?- 建立联系了啊,上面也获取了
BeanFactory
的后置处理器,然后foreach
循环就是建立BeanFactory
的后置处理器和BeanFactory的联系。 - 另外
@Configuration
加不加,Config
类中的@Bea
n注解都会被解析,@Configuration
是用于spring
类扫描的时候用的,加了这个注解的类被扫描到了就会被放进Bean
工厂
- 建立联系了啊,上面也获取了
spring_02_02_applicationcontext_impl
ClassPathXmlApplicationContext
:FileSystemXmlApplicationContext
:AnnotationConfigApplicationContext
:AnnotationConfigServletWebServerApplication
:
相关测试代码如下:
@Slf4jpublic class TestApplicationContext { @Test // ⬇️1.最为经典的容器,基于classpath 下 xml 格式的配置文件来创建 public void testClassPathXmlApplicationContext() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring_bean.xml"); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); } System.out.println(context.getBean(Bean2.class).getBean1()); } @Test // ⬇️2.基于磁盘路径下 xml 格式的配置文件来创建 public void testFileSystemXmlApplicationContext() { // 可以用绝对路径或者相对路径 // FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("D:\\ideacode\\spring-source-study\\spring_02_02_applicationcontext_impl\\src\\main\\resources\\spring_bean.xml"); FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("src\\main\\resources\\spring_bean.xml"); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); } System.out.println(context.getBean(Bean2.class).getBean1()); } @Test // ⬇️模拟一下ClassPathXmlApplicationContext和FileSystemXmlApplicationContext底层的一些操作 public void testMockClassPathAndFileSystemXmlApplicationContext() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); System.out.println("读取之前"); for (String name : beanFactory.getBeanDefinitionNames()) { System.out.println(name); } System.out.println("读取之后"); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); // reader.loadBeanDefinitions("spring_bean.xml"); // reader.loadBeanDefinitions(new ClassPathResource("spring_bean.xml")); reader.loadBeanDefinitions(new FileSystemResource("src\\main\\resources\\spring_bean.xml")); for (String name : beanFactory.getBeanDefinitionNames()) { System.out.println(name); } } @Test // ⬇️3.较为经典的容器,基于java配置类来创建 public void testAnnotationConfigApplicationContext() { // 会自动加上5个后处理器 // 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 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); } System.out.println(context.getBean(Bean2.class).getBean1()); } @Test // ⬇️4.较为经典的容器,基于java配置类来创建,并且还可以用于web环境 // 模拟了 springboot web项目内嵌Tomcat的工作原理 public void testAnnotationConfigServletWebServerApplicationContext() throws IOException { AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext(WebConfig.class); // 防止程序终止 System.in.read(); }}@Configurationclass WebConfig { @Bean // 1. WebServer工厂 public ServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(); } @Bean // 2. web项目必备的DispatcherServlet public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); } @Bean // 3. 将DispatcherServlet注册到WebServer上 public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) { return new DispatcherServletRegistrationBean(dispatcherServlet, "/"); } @Bean("/hello") public Controller controller1() { return (request, response) -> { response.getWriter().println("hello"); return null; }; }}// 单元测试的过程中如果要解析一些Spring注解,比如@Configuration的时候不要把相关类定义到写单元测试类的内部类,会读取不到@Configurationclass Config { @Bean public Bean1 bean1() { return new Bean1(); } @Bean public Bean2 bean2(Bean1 bean1) { Bean2 bean2 = new Bean2(); bean2.setBean1(bean1); return bean2; }}class Bean1 {}class Bean2 { private Bean1 bean1; public Bean1 getBean1() { return bean1; } public void setBean1(Bean1 bean1) { this.bean1 = bean1; }}
spring_bean.xml
如下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 把5个后处理器加进来 等价于:AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory); --> <context:annotation-config /> <bean id="bean1" class="top.jacktgq.Bean1" /> <bean id="bean2" class="top.jacktgq.Bean2"> <property name="bean1" ref="bean1"/> </bean></beans>
第三讲 Bean
的生命周期和模板方法设计模式
spring_03_bean_lifecycle
p14 013-第三讲-bean生命周期
springboot
项目启动类
@SpringBootApplicationpublic class BeanLifeCycleApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(BeanLifeCycleApplication.class, args); context.close(); }}
定义一个LifeCycleBean
,加上@Component
注解,再编写一些方法,给这些方法加上Bean
的生命周期过程中的注解
@Component@Slf4jpublic class LifeCycleBean { public LifeCycleBean() { log.debug("构造"); } @Autowired public void autowire(@Value("${JAVA_HOME}") String name) { log.debug("依赖注入:{}", name); } @PostConstruct public void init() { log.debug("初始化"); } @PreDestroy public void destroy() { log.debug("销毁"); }}
编写自定义Bean
的后处理器,需要实现InstantiationAwareBeanPostProcessor
和DestructionAwareBeanPostProcessor
接口,并加上@Component
注解,对lifeCycleBean
的生命周期过程进行扩展。
@Slf4j@Componentpublic class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor { @Override // 实例化前(即调用构造方法前)执行的方法 public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { if (beanName.equals("lifeCycleBean")) log.debug("<<<<<<<<<<< 实例化前执行,如@PreDestroy"); // 返回null保持原有对象不变,返回不为null,会替换掉原有对象 return null; } @Override // 实例化后执行的方法 public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { if (beanName.equals("lifeCycleBean")) { log.debug("<<<<<<<<<<< 实例化后执行,这里如果返回 false 会跳过依赖注入阶段"); // return false; } return true; } @Override // 依赖注入阶段执行的方法 public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException { if (beanName.equals("lifeCycleBean")) log.debug("<<<<<<<<<<< 依赖注入阶段执行,如@Autowired、@Value、@Resource"); return pvs; } @Override // 销毁前执行的方法 public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { if(beanName.equals("lifeCycleBean")) log.debug("<<<<<<<<<<<销毁之前执行"); } @Override // 初始化之前执行的方法 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if(beanName.equals("lifeCycleBean")) log.debug("<<<<<<<<<<< 初始化之前执行,这里返回的对象会替换掉原本的bean,如 @PostConstruct、@ConfigurationProperties"); return bean; } @Override // 初始化之后执行的方法 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if(beanName.equals("lifeCycleBean")) log.debug("<<<<<<<<<<< 初始化之后执行,这里返回的对象会替换掉原本的bean,如 代理增强"); return bean; }}
运行结果如下:
经过测试和运行结果的比对:
@Autowired
注解对应的后处理器是AutowiredAnnotationBeanPostProcessor
;@Value
注解需要配合@Autowired
注解一起使用,所以也用到了AutowiredAnnotationBeanPostProcessor
后处理器,然后@Value
注解还需要再用到ContextAnnotationAutowireCandidateResolver
解析器,否则会报错;@Resource
、@PostConstruct
、@PreDestroy
注解对应的后处理器是CommonAnnotationBeanPostProcessor
;@ConfigurationProperties
注解对应的后处理器是ConfigurationPropertiesBindingPostProcessor
。
p18 017-第四讲-@Autowired bean后处理器执行分析
p19 018-第四讲-@Autowired bean后处理器执行分析
本案例测试代码紧接着上面,这里对Bean1
中加了@Autowired
注解的属性注入Bean2
、方法注入Bean3
以及方法注入环境变量JAVA_HOME
的过程进行分析。
@Autowired
注解解析用到的后处理器是AutowiredAnnotationBeanPostProcessor
- 这个后处理器就是通过调用
postProcessProperties(PropertyValues pvs, Object bean, String beanName)
完成注解的解析和注入的功能 - 这个方法中又调用了一个私有的方法
findAutowiringMetadata(beanName, bean.getClass(), pvs)
,其返回值InjectionMetadata
中封装了被@Autowired
注解修饰的属性和方法 - 然后会调用
InjectionMetadata.inject(bean1, "bean1", null)
进行依赖注入 - 由于
InjectionMetadata.inject(bean1, "bean1", null)
的源码调用链过长,摘出主要调用过程进行说明: - 成员变量注入,
InjectionMetadata
注入Bean3
的过程:InjectionMetadata
会把Bean1
中加了@Autowired
注解的属性的BeanName
先拿到,这里拿到的BeanName
就是bean3
,然后再通过反射拿到这个属性,Field bean3Field = Bean1.class.getDeclaredField("bean3");
- 将这个属性封装成一个
DependencyDescriptor
对象,再去调用Bean3 bean3Value = (Bean3) beanFactory.doResolveDependency(dd1, null, null, null);
拿到bean3Value
- 最后把值赋给这个属性
bean3Field.set(bean1, bean3Value);
- 方法参数注入,
InjectionMetadata
注入Bean2
的过程:InjectionMetadata
会把Bean1
中加了@Autowired
注解的方法的MethodName
先拿到,这里拿到的MethodName
就是setBean2
,然后再通过反射拿到这个方法,Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
- 将这个属性封装成一个
DependencyDescriptor
对象,再去调用Bean2 bean2Value = (Bean2) beanFactory.doResolveDependency(dd2, "bean2", null, null);
拿到bean2Value
- 最后调用方法
setBean2.invoke(bean1, bean2Value)
,给方法参数赋值。
- 方法参数注入,参数类型为String类型,且加上了@Value注解,
InjectionMetadata
注入环境变量JAVA_HOME
的过程:InjectionMetadata
会把Bean1
中加了@Autowired
注解的方法的MethodName
先拿到,这里拿到的MethodName
就是setJava_home
,然后再通过反射拿到这个方法,Method setJava_home = Bean1.class.getDeclaredMethod("setJava_home", String.class);
- 将这个属性封装成一个
DependencyDescriptor
对象,再去调用String java_home = (String) beanFactory.doResolveDependency(dd3, null, null, null);
拿到java_home
- 最后调用方法
setJava_home.invoke(bean1, java_home);
,给方法参数赋值。
全部测试代码如下:
@Slf4jpublic class TestBeanPostProcessors { @Test public void testAutowiredAnnotationBeanPostProcessor() throws Throwable { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); // 这里为了省事就不使用 beanFactory.registerBeanDefinition()方法去添加类的描述信息了 // 直接使用 beanFactory.registerSingleton可以直接将Bean的单例对象注入进去, // 后面调用beanFactory.getBean()方法的时候就不会去根据Bean的定义去创建Bean的实例了, // 也不会有懒加载和依赖注入的初始化过程了。 beanFactory.registerSingleton("bean2", new Bean2()); beanFactory.registerSingleton("bean3", new Bean3()); // 设置@Autowired注解的解析器 beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); // 设置解析 @Value 注解中的 ${} 表达式的解析器 beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders); // 1. 查找哪些属性、方法加了 @Autowired,这称之为InjectionMetadata // 创建后处理器 AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor(); // 后处理器在解析@Autowired和@Value的时候需要用到其他Bean, // 而BeanFactory提供了需要的Bean,所以需要把BeanFactory传给这个后处理器 processor.setBeanFactory(beanFactory); // 创建Bean1 Bean1 bean1 = new Bean1(); System.out.println(bean1); // 解析@Autowired和@Value注解,执行依赖注入 // PropertyValues pvs: 给注解的属性注入给定的值,这里不需要手动给定,传null即可 // processor.postProcessProperties(null, bean1, "bean1"); // postProcessProperties()方法底层原理探究 // 通过查看源码得知 postProcessProperties()方法中调用了一个私有的方法findAutowiringMetadata(beanName, bean.getClass(), pvs); 会返回一个InjectionMetadata的对象,然后会调用InjectionMetadata.inject(bean1, "bean1", null)进行依赖注入 // 通过反射调用一下 /*Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata",String.class, Class.class, PropertyValues.class); findAutowiringMetadata.setAccessible(true); // 获取Bean1上加了@Value @Autowired注解的成员变量和方法参数信息 InjectionMetadata metadata = (InjectionMetadata) findAutowiringMetadata.invoke(processor, "bean1", Bean1.class, null); System.out.println(metadata); // 2. 调用 InjectionMetaData 来进行依赖注入,注入时按类型查找值 metadata.inject(bean1, "bean1", null); System.out.println(bean1);*/ // 3. 如何去Bean工厂里面按类型查找值 // 由于InjectionMetadata.inject(bean1, "bean1", null)的源码调用链过长,摘出主要调用过程进行演示 // 3.1 @Autowired加在成员变量上,InjectionMetatadata给Bean1注入Bean3的过程 // 通过InjectionMetadata把Bean1加了@Autowired注解的属性的BeanName先拿到,这里假设拿到的BeanName就是 bean3 // 通过BeanName反射获取到这个属性, Field bean3Field = Bean1.class.getDeclaredField("bean3"); // 设置私有属性可以被访问 bean3Field.setAccessible(true); // 将这个属性封装成一个DependencyDescriptor对象 DependencyDescriptor dd1 = new DependencyDescriptor(bean3Field, false); // 再执行beanFactory的doResolveDependency Bean3 bean3Value = (Bean3) beanFactory.doResolveDependency(dd1, null, null, null); System.out.println(bean3Value); // 给Bean1的成员bean3赋值 bean3Field.set(bean1, bean3Value); System.out.println(bean1); // 3.2 @Autowired加在方法上,InjectionMetatadata给Bean1注入Bean2的过程 Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class); DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), true); Bean2 bean2Value = (Bean2) beanFactory.doResolveDependency(dd2, "bean2", null, null); System.out.println(bean2Value); // 给Bean1的setBean2()方法的参数赋值 setBean2.invoke(bean1, bean2Value); System.out.println(bean1); // 3.3 @Autowired加在方法上,方法参数为String类型,加了@Value, // InjectionMetadata给Bean1注入环境变量JAVA_HOME属性的值 Method setJava_home = Bean1.class.getDeclaredMethod("setJava_home", String.class); DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setJava_home, 0), true); String java_home = (String) beanFactory.doResolveDependency(dd3, null, null, null); System.out.println(java_home); setJava_home.invoke(bean1, java_home); System.out.println(bean1); }}
运行结果如下:
第五讲 常见Bean工厂后处理器以及模拟实现组件扫描
spring_05_beanfactorypostprocessor
这里用GenericApplicationContext
来探究一下@Component
、@ComponentScan
、@Bean
、@MapperScan
这些注解分别是由哪个后处理器来解析的。
测试代码如下:
@Slf4jpublic class TestBeanFactoryPostProcessors { @Test public void testBeanPostProcessors() throws IOException { // ⬇️GenericApplicationContext 是一个【干净】的容器,默认不会添加任何后处理器,方便做测试 GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); // 添加Bean工厂后处理器ConfigurationClassPostProcessor // 解析@ComponentScan、@Bean、@Import、@ImportResource注解 context.registerBean(ConfigurationClassPostProcessor.class); // 添加Bean工厂后处理器MapperScannerConfigurer,解析@MapperScan注解 context.registerBean(MapperScannerConfigurer.class, beanDefinition -> { // 指定扫描的包名 beanDefinition.getPropertyValues().add("basePackage", "top.jacktgq.mapper"); }); // ⬇️初始化容器 context.refresh(); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); } // ⬇️销毁容器 context.close(); }}
经过测试和运行结果的比对:
@Component
、@Bean
对应的Bean
工厂后处理器是ConfigurationClassPostProcessor
;@MapperScan
对应的Bean
工厂后处理器是MapperScannerConfigurer
。
p21 020-第五讲-工厂后处理器模拟实现-组件扫描
p22 021-第五讲-工厂后处理器模拟实现-组件扫描
自定义组件扫描Bean
工厂后处理器CandyComponentScanPostProcessor
来解析@Component
注解,代码如下:
public class CandyAtComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException { try { ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class); if (componentScan != null) { for (String basePage : componentScan.basePackages()) { System.out.println(basePage); // top.jacktgq.component -> classpath*:com/jacktgq/component/**/*.class String path = "classpath*:" + basePage.replace('.', '/') + "/**/*.class"; // System.out.println(path); CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator(); for (Resource resource : new PathMatchingResourcePatternResolver().getResources(path)) { // System.out.println(resource); // 查看对应的类上是否有@Component注解 // System.out.println("分隔符>>>>>>>>>>>>>>>>>"); MetadataReader reader = factory.getMetadataReader(resource); String className = reader.getClassMetadata().getClassName(); // System.out.println("类名:" + className); String name = Component.class.getName(); // System.out.println("是否加了 @Component注解:" + reader.getAnnotationMetadata().hasAnnotation(name)); AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata(); // System.out.println("是否加了@Component的派生注解:" + annotationMetadata.hasMetaAnnotation(name)); // 如果直接或者间接加了@Component注解 if (annotationMetadata.hasAnnotation(Component.class.getName()) || annotationMetadata.hasMetaAnnotation(name)) { // 创建Bean的定义 AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(className).getBeanDefinition(); String beanName = generator.generateBeanName(beanDefinition, beanFactory); // 将Bean定义加入工厂 beanFactory.registerBeanDefinition(beanName, beanDefinition); } } } } } catch (IOException e) { e.printStackTrace(); } } @Override // context.refresh()中会回调该方法 public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { }}
测试代码:
@Slf4jpublic class TestBeanFactoryPostProcessors { @Test // 模拟实现组件扫描 public void testMockComponentScan() throws Exception { // ⬇️GenericApplicationContext 是一个【干净】的容器,默认不会添加任何后处理器,方便做测试 GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); // 把自定义组件扫描Bean工厂后处理器加进来 context.registerBean(CandyComponentScanPostProcessor.class); // ⬇️初始化容器 context.refresh(); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); } // ⬇️销毁容器 context.close(); }}
运行结果:
p25 024-第五讲-工厂后处理器模拟实现-Mapper
自定义Bean
工厂后处理器CandyAtMapperPostProcessor
来解析@Mapper
注解,代码如下:
public class CandyAtMapperPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException { try { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources("classpath:top/jacktgq/mapper/**/*.class"); // Bean的名字生成器 AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator(); CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); for (Resource resource : resources) { MetadataReader reader = factory.getMetadataReader(resource); ClassMetadata classMetadata = reader.getClassMetadata(); if (classMetadata.isInterface()) { AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class).addConstructorArgValue(classMetadata.getClassName()).setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE).getBeanDefinition(); // 这里不能使用名字生成器和MapperFactoryBean的BeanDefinition作为参数直接生成名字, // 这样会导致多个相同的类型的对象因为名字一样产生覆盖的问题 // 解决办法 这里参考Spring源码的做法 // 用@Mapper注解修饰的接口的BeanDefinition作为参数生成名字 AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition(); String beanName = generator.generateBeanName(bd, beanFactory); beanFactory.registerBeanDefinition(beanName, beanDefinition); } } } catch (IOException e) { e.printStackTrace(); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { }}
测试代码:
@Slf4jpublic class TestBeanFactoryPostProcessors { @Test // 模拟实现@Mapper注解的解析 public void testMockAtMapperAnnotation() throws Exception { // ⬇️GenericApplicationContext 是一个【干净】的容器,默认不会添加任何后处理器,方便做测试 GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); // 先解析@Bean注解,把SqlSessionFactory加到Bean工厂里面 context.registerBean(CandyAtBeanPostProcessor.class); // 解析Mapper接口 context.registerBean(CandyAtMapperPostProcessor.class); // ⬇️初始化容器 context.refresh(); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); } // ⬇️销毁容器 context.close(); }}
运行结果:
第六讲 Aware和InitializingBean接口以及@Autowired注解失效分析
spring_06_aware_initializingbean
加了@Autowired
和@PostConstruct
注解的方法并没有被执行,而Aware
和InitializingBean
接口方法都被执行了。
修改测试代码,把解析@Autowired
和@PostConstruct
注解的Bean
后处理加进来,然后再运行一下
public class TestAwareAndInitializingBean { @Test public void testAware1() throws Exception { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("myBean", MyBean.class); // 解析 @Autowired 注解的Bean后处理器 context.registerBean(AutowiredAnnotationBeanPostProcessor.class); // 解析 @PostConstruct 注解的Bean后处理器 context.registerBean(CommonAnnotationBeanPostProcessor.class); context.refresh(); context.close(); }}
运行结果:
可以看到这下都执行了
有人可能会问:b
、c
、d
的功能用 @Autowired
注解就能实现啊,为啥还要用 Aware
接口呢?
InititalizingBean
接口可以用 @PostConstruct
注解实现,为啥还要用InititalizingBean
呢?
简单地说:
@Autowired
和@PostConstruct
注解的解析需要用到Bean
后处理器,属于扩展功能,而Aware
接口属于内置功能,不加任何扩展,Spring
就能识别;某些情况下,扩展功能会失效,而内置功能不会失效
MyConfig2
:@Slf4jpublic class MyConfig2 implements ApplicationContextAware, InitializingBean { @Override public void setApplicationContext(ApplicationContext applicationContext) { log.debug("注入 ApplicationContext"); } @Override public void afterPropertiesSet() throws Exception { log.debug("初始化"); } @Bean public BeanFactoryPostProcessor processor1() { return beanFactory -> { log.debug("执行 processor1"); }; }}
测试代码:
@Slf4jpublic class TestAwareAndInitializingBean { @Test public void testAutowiredAndInitializingBean_MyConfig2() { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("myConfig2", MyConfig2.class); // 1. 添加beanfactory后处理器;2. 添加bean后处理器;3. 初始化单例。 context.refresh(); context.close(); }}
运行结果:
Java配置类在添加了
bean
工厂后处理器后,你会发现用传统接口方式的注入和初始化依然成功,而@Autowired
和@PostConstruct
的注入和初始化失败。那是什么原因导致的呢?
配置类
@Autowired
注解失效分析Java 配置类不包含
BeanFactoryPostProcessor
的情况Java
配置类包含BeanFactoryPostProcessor
的情况根据上面的时序图可以得知,正常情况下,
BeanFactoryPostProcessor
会在Java
配置类初始化之前执行,而Java配置类里面却定义了一个BeanFactoryPostProcessor
,要创建其中的BeanFactoryPostProcessor
,必须提前创建Java
配置类,这样BeanFactoryPostProcessor
就会在Java配置类初始化后执行了,而此时的BeanPostProcessor
还未准备好,导致@Autowired
等注解失效。
总结:
Aware
接口提供了一种【内置】 的注入手段,可以注入BeanFactory
,ApplicationContext
;InitializingBean
接口提供了一种 【内置】 的初始化手段;- 内置的注入和初始化不收扩展功能的影响,总会被执行,因此
spring
框架内部的类常用它们。
第七讲 Bean的初始化与销毁
spring_07_init_destroy
可以看到,spring提供了多种初始化和销毁手段
对于
init
,三个初始化方法的执行顺序是@PostConstruct
->InitializingBean
接口 ->@Bean
的initMethod
对于
destory
, 三个销毁方法的执行顺序是@PreDestroy
->DisposableBean
接口 ->@Bean
的destroy
第八讲 Scope类型、注意事项、销毁和失效分析
spring_08_scope
p29 028-第八讲-Scope
spring
的scope
类型:
singleton
:单例prototype
:多例request
:web
请求session
:web
的会话application
:web
的ServletContext
测试scope
类型中的request
、session
、application
定义**BeanForRequest
类,加上@Component
和@Scope
注解,指定Scope
类型为request
**,在类型中定义destroy()
方法,方法上加@PreDestory
注解,代码如下:
@Slf4j@Scope("request")@Componentpublic class BeanForRequest { @PreDestroy public void destory() { log.debug("destroy"); }}
定义**BeanForSession
类,加上@Component
和@Scope
注解,指定Scope
类型为session
**,在类型中定义destroy()
方法,方法上加@PreDestory
注解,代码如下:
@Slf4j@Scope("request")@Componentpublic class BeanForRequest { @PreDestroy public void destory() { log.debug("destroy"); }}
定义**BeanForApplication
类,加上@Component
和@Scope
注解,指定Scope
类型为application
**,在类型中定义destroy()
方法,方法上加@PreDestory
注解,代码如下:
@Slf4j@Scope("request")@Componentpublic class BeanForRequest { @PreDestroy public void destory() { log.debug("destroy"); }}
编写一个MyController
类,加上@RestController
注解,在该类中通过@Autowired
注解注入BeanForRequest
、BeanForSession
和BeanForApplication
的实例,需要注意,这里还需要加@Lazy
注解(至于原因后面会解释),否则会导致@Scope
域失效,再定义一个方法tes()
,加上@GetMapping
注解,用于响应一个http
请求,在test()
方法中,打印beanForRequest
、beanForSession
和beanForApplication
,代码如下:
@RestControllerpublic class MyController { @Lazy @Autowired private BeanForRequest beanForRequest; @Lazy @Autowired private BeanForSession beanForSession; @Lazy @Autowired private BeanForApplication beanForApplication; @GetMapping(value = "/test", produces = "text/html") public String test(HttpServletRequest request, HttpSession session) { // ServletContext sc = request.getServletContext(); String result = ""
+ "request scope: " + beanForRequest + "" + "session scope: " + beanForSession + "" + "application scope: " + beanForApplication + "" + ""; return result; }}
springboot
启动类
@Slf4j@SpringBootApplicationpublic class ScopeApplicationContext { public static void main(String[] args) throws InterruptedException { testRequest_Session_Application_Scope(); } // 演示 request, session, application作用域 private static void testRequest_Session_Application_Scope() throws InterruptedException { ConfigurableApplicationContext context = SpringApplication.run(ScopeApplicationContext.class); }}
启动后,用谷歌浏览器访问 http://localhost:8080/test,
浏览器运行结果如下:
再刷新一下当前页,查看运行结果:
控制台运行结果如下:
可以看到两次刷新只有BeanForRequest
对象发生了改变,这是由于scope
为request
类型的对象,会在请求结束后销毁,再来一次请求就会重新创建,请求结束后又会销毁。
接下来我们换个Edge
浏览器访问 http://localhost:8080/test,对比两个浏览器的显示结果:
可以看到这回除了BeanForRequest
对象不同,BeanForSession
对象也不同了,这是因为开一个新的浏览器会创建一个新的会话,所以BeanForSession
对象也不同了。
继续进行测试,在application.properties
配置一个属性server.servlet.session.timeout=10s
,这个属性的默认值为30
分钟,这样10s
没有操作浏览器的话就会销毁对应session
,不过经过测试这个这个属性最少为1
分钟,低于1分钟一律按照1分钟算。具体原理看这篇博客:https://www.jianshu.com/p/9d91cca74082,里面进行了源码级别的分析。
设置好之后,重启项目, 然后去浏览器访问,1分钟后控制台会打印session
被销毁,如下图所示:
那什么时候scope
为application
的对象BeanForApplication
会销毁呢?按理说应该是在SpringBoot
程序结束,也即内置的Tomcat
服务器停止的时候调用,但是经过测试:无论是在控制台停止SpringBoot
项目,还是调用ApplicationContext
的close()
方法,都没有调用BeanForApplication
的销毁方法,有知道什么方法可以让它调用的,请评论区告知,谢谢!!!
第九讲 aop之ajc增强
aop
是spring
框架中非常重要的功能,其主要实现通常情况下是动态代理,但是这个说法并不全面,还有另外两种实现:
ajc
编译器agent
类加载
spring_09_aop_ajc
p32 031-第九讲-aop之ajc增强
先看aop
的第一种实现ajc
编译器代码增强,这是一种编译时的代码增强。
新建一个普通的maven项目
添加依赖
使用
ajc
编译器进行代码增强,首先需要在pom.xml
文件中加入ajc
编译器插件依赖<build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.11</version> <configuration> <complianceLevel>1.8</complianceLevel> <source>8</source> <target>8</target> <showWeaveInfo>true</showWeaveInfo> <verbose>true</verbose> <Xlint>ignore</Xlint> <encoding>UTF-8</encoding> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> </plugins></build>
加入
aspectjweaver
的依赖<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version></dependency>
加入日志和单元测试的依赖
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version></dependency><dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.10</version></dependency><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version></dependency>
需要增强的类
MyService
public class MyService { private static final Logger log = LoggerFactory.getLogger(MyAspect.class); public void foo() { log.debug("foo()"); }}
切面类
MyAspect
,编写execution
表达式,对MyService
类的foo()
方法进行增强@Aspect // ⬅️注意此切面并未被 Spring 管理,本项目pom文件中根本没有引入spring的相关类public class MyAspect { private static final Logger log = LoggerFactory.getLogger(MyAspect.class); @Before("execution(* top.jacktgq.service.MyService.foo())") public void before() { log.debug("before()"); }}
测试代码
public class Aop_Aspectj_Test { @Test public void testAopAjc() { new MyService().foo(); }}
编译项目,这里需要使用
maven
来编译,打开idea
中的maven
面板,点击compile
然后再运行测试代码,可以看到创建
MyService
对象并调用foo()
方法会先执行切面类中的before()
方法注:
有些小伙伴可能会遇到问题:明明按照一样的步骤来操作,可是运行以后代码并没有增强。这是由于
idea
中在执行代码之前会默认编译一遍代码,这本来是正常的,可是,如果使用maven
来编译代码,会在执行代码前将maven
编译的代码覆盖,这就会导致maven
的ajc
编译器增强的代码被覆盖,所以会看不到最终的运行效果。解决办法:在设置中将自动构建项目的选项勾上,就不会出现多次编译覆盖的问题了。
总结:
可以看到没有引入任何跟
spring
框架相关的包,MyService
类是通过直接new()
的方式获得的,所以也就不存在使用了动态代理的说法了打开编译后的
MyService.class
文件,双击以后idea会反编译该字节码文件,可以看到foo()
方法体的开头加了一行代码,这就是增强的代码,这是ajc
编译器在编译MyService
类的时候为我们添加的代码,这是一种编译时的增强。
第十讲 aop之agent增强
spring_10_aop_agent
注:还需要在resources/META-INF
目录下建一个aop.xml
配置文件,内容如下,aspectj
会自动扫描到这个配置文件,不加这个配置文件不会出效果。
<aspectj> <aspects> <aspect name="top.jacktgq.aop.MyAspect"/> <weaver options="-verbose -showWeaveInfo"> <include within="top.jacktgq.service.MyService"/> <include within="top.jacktgq.aop.MyAspect"/> </weaver> </aspects></aspectj>
运行测试代码,可以看到创建MyService
对象并调用foo()
方法会先执行切面类中的before()
方法
注:
有些小伙伴可能会遇到问题:明明按照一样的步骤来操作,可是运行以后代码并没有增强。这是由于
idea
中在执行代码之前会默认编译一遍代码,这本来是正常的,可是,如果使用maven
来编译代码,会在执行代码前将maven
编译的代码覆盖,这就会导致maven
的ajc
编译器增强的代码被覆盖,所以会看不到最终的运行效果。解决办法:在设置中将自动构建项目的选项勾上,就不会出现多次编译覆盖的问题了。
总结:
可以看到没有引入任何跟
spring
框架相关的包,MyService
类是通过直接new()
的方式获得的,所以也就不存在使用了动态代理的说法了打开编译后的
MyService.class
文件,双击以后idea会反编译该字节码文件,可以看到foo()
方法体中并没有添加多余的代码,所以就不是编译时增强了,而是类加载的时候增强的,这里可以借助阿里巴巴的Arthas工具,下载地址:https://arthas.aliyun.com/doc/en/download.html,解压以后进入到arthas的bin目录下,启动黑窗口,输入java -jar .\arthas-boot.jar
,在输出的java
进程列表里面找到我们要连接的进程,输入对应进程的序号,我这里是4
,连接上以后会打印ARTHAS
的logo
再输入
jad top.jacktgq.service.MyService
反编译内存中的MyService
类可以看到
foo()
和bar()
方法体的第一行都加了一行代码,这就说明通过添加虚拟机参数-javaagent
的方式可以在类加载的时候对代码进行增强。
第十一讲 aop之proxy增强-jdk和cglib
spring_11_aop_proxy_jdk_cglib
jdk
动态代理总结:
- 代理对象和目标对象是兄弟关系,都实现了
Foo
接口,代理对象类型不能强转成目标对象类型; - 目标类定义的时候可以加
final
修饰。
p35 034-第十一讲-aop之proxy增强-cglib
public class AopCglibProxyTest { @Test public void testCglibProxy1() { // 目标对象 Target target = new Target(); Foo fooProxy = (Foo) Enhancer.create(Target.class, (MethodInterceptor) (obj, method, args, proxy) -> { System.out.println("before..."); Object result = method.invoke(target, args); // 用方法反射调用目标 System.out.println("after..."); return result; }); System.out.println(fooProxy.getClass()); fooProxy.foo(); } @Test public void testCglibProxy2() { // 目标对象 Target target = new Target(); Foo fooProxy = (Foo) Enhancer.create(Target.class, (MethodInterceptor) (obj, method, args, proxy) -> { System.out.println("before..."); // proxy 它可以避免反射调用 Object result = proxy.invoke(target, args); // 需要传目标类 System.out.println("after..."); return result; }); System.out.println(fooProxy.getClass()); fooProxy.foo(); } @Test public void testCglibProxy3() { // 目标对象 Foo fooProxy = (Foo) Enhancer.create(Target.class, (MethodInterceptor) (obj, method, args, proxy) -> { System.out.println("before..."); // proxy 它可以避免反射调用 Object result = proxy.invokeSuper(obj, args); // 不需要目标类,需要代理自己 System.out.println("after..."); return result; }); System.out.println(fooProxy.getClass()); fooProxy.foo(); }}interface Foo { void foo();}@Slf4jclass Target implements Foo { public void foo() { log.debug("target foo"); }}
运行结果
cglib
动态代理总结:
MethodInterceptor
的intercept()
方法的第2个参数是method
,可以通过反射对目标方法进行调用Object result = method.invoke(target, args); // 用方法反射调用目标
第4个参数
proxy
,可以不用反射就能对目标方法进行调用;Object result = proxy.invoke(target, args); // 需要传目标类 (spring用的是这种)// 或者Object result = proxy.invokeSuper(obj, args); // 不需要目标类,需要代理自己
代理类不需要实现接口;
代理对象和目标对象是父子关系,代理类继承于目标类;
目标类定义的时候不能加
final
修饰,否则代理类就无法继承目标类了,会报java.lang.IllegalArgumentException: Cannot subclass final class top.jacktgq.proxy.cglib.Target
异常;目标类方法定义的时候不能加
final
修饰,否则代理类继承目标类以后就不能重写目标类的方法了。
第十二讲 jdk代理原理
spring_12_aop_proxy_jdk_cglib_principle
p36 035-第十二讲-jdk代理原理
p37 036-第十二讲-jdk代理原理
p38 037-第十二讲-jdk代理源码
为了更好地探究jdk
动态代理原理,先用代码显式地模拟一下这个过程。
先定义一个Foo
接口,里面有一个foo()
方法,再定义一个Target
类来实现这个接口,代码如下所示:
public interface Foo { void foo();}@Slf4jpublic final class Target implements Foo { public void foo() { log.debug("target foo"); }}public class $Proxy0 implements Foo { @Override public void foo() { // 1. 功能增强 System.out.println("before..."); // 2. 调用目标 new Target().foo(); }}public class Main { public static void main(String[] args) { Foo proxy = new $Proxy0(); proxy.foo(); }}
接下来对Target
类中的foo()
方法进行增强
首先想到的是,再定义一个类也同样地实现一下
Foo
接口,然后在foo()
方法中编写增强代码,接着再new
一个Target
对象,调用它的foo()
方法,代码如下所示:public class $Proxy0 implements Foo { @Override public void foo() { // 1. 功能增强 System.out.println("before..."); // 2. 调用目标 new Target().foo(); }}// 测试运行public class Main { public static void main(String[] args) { Foo proxy = new $Proxy0(); proxy.foo(); }}
上面的代码把功能增强的代码和调用目标的代码都固定在了代理类的内部,不太灵活。因此可以通过定义一个
InvocationHandler
接口的方式来将这部分代码解耦出来,代码如下:public interface InvocationHandler { void invoke();}public interface Foo { void foo();}@Slf4jpublic final class Target implements Foo { public void foo() { System.out.println("target foo"); }}public class $Proxy0 implements Foo { private InvocationHandler h; public $Proxy0(InvocationHandler h) { this.h = h; } @Override public void foo() { h.invoke(); }}public class Main { public static void main(String[] args) { Foo proxy = new $Proxy0(new InvocationHandler() { @Override public void invoke() { // 1. 功能增强 System.out.println("before..."); // 2. 调用目标 new Target().foo(); } }); proxy.foo(); }}
第2个版本的代码虽然将功能增强的代码和调用目标的代码通过接口的方式独立出来了,但还是有问题,如果此时接口中新增了一个方法
bar()
,Target
类和$Proxy0
类中都要实现bar()
方法,那么调用proxy
的foo()
和bar()
方法都将间接调用目标对象的foo()
方法,因为在InvocationHandler
的invoke()
方法中调用的是target.foo()
方法,代码如下:public interface InvocationHandler { void invoke();}public interface Foo { void foo(); void bar();}@Slf4jpublic final class Target implements Foo { public void foo() { System.out.println("target foo"); } @Override public void bar() { log.debug("target bar"); }}public class $Proxy0 implements Foo { private InvocationHandler h; public $Proxy0(InvocationHandler h) { this.h = h; } @Override public void foo() { h.invoke(); } @Override public void bar() { h.invoke(); }}public class Main { public static void main(String[] args) { Foo proxy = new $Proxy0(new InvocationHandler() { @Override public void invoke() { // 1. 功能增强 System.out.println("before..."); // 2. 调用目标 new Target().foo(); } }); proxy.foo(); proxy.bar(); }}
改进方法是,代理类中调用方法的时候,通过反射把接口中对应的方法
Method
对象作为参数传给InvocationHandler
,代码如下:public interface InvocationHandler { void invoke(Method method, Object[] args) throws Throwable;}public interface Foo { void foo(); void bar();}public final class Target implements Foo { public void foo() { System.out.println("target foo"); } @Override public void bar() { System.out.println("target bar"); }}public class $Proxy0 implements Foo { private InvocationHandler h; public $Proxy0(InvocationHandler h) { this.h = h; } @Override public void foo() { try { Method foo = Foo.class.getDeclaredMethod("foo"); h.invoke(foo, new Object[0]); } catch (Throwable e) { e.printStackTrace(); } } @Override public void bar() { try { Method bar = Foo.class.getDeclaredMethod("bar"); h.invoke(bar, new Object[0]); } catch (Throwable e) { e.printStackTrace(); } }}public class Main { public static void main(String[] args) { Foo proxy = new $Proxy0(new InvocationHandler() { @Override public void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { // 1. 功能增强 System.out.println("before..."); // 2. 调用目标 method.invoke(new Target(), args); } }); proxy.foo(); proxy.bar(); }}
第3个版本的代码其实已经离jdk动态代理生成的代码很相近了,为了更好地学习底层,更近一步,修改
Foo
接口的中bar()
方法,使其具有int
类型的返回值,因此InvocationHandler
的invoke()
方法也得有返回值,同时将代理对象本身作为第一个参数,具体代码如下:public interface InvocationHandler { Object invoke(Object proxy, Method method, Object[] args) throws Throwable;}public interface Foo { void foo(); int bar();}public final class Target implements Foo { public void foo() { System.out.println("target foo"); } @Override public int bar() { System.out.println("target bar"); return 1; }}public class $Proxy0 implements Foo { static Method foo; static Method bar; static { try { foo = Foo.class.getDeclaredMethod("foo"); bar = Foo.class.getDeclaredMethod("bar"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } private InvocationHandler h; public $Proxy0(InvocationHandler h) { this.h = h; } @Override public void foo() { try { h.invoke(this, foo, new Object[0]); } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public int bar() { try { return (int) h.invoke(this, bar, new Object[0]); } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } }}public class Main { public static void main(String[] args) { Foo proxy = new $Proxy0(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { // 1. 功能增强 System.out.println("before..."); // 2. 调用目标 return method.invoke(new Target(), args); } }); proxy.foo(); System.out.println("bar()方法返回值:" + proxy.bar()); }}
到这里跟jdk的动态代理只有些微差距了,
jdk
的动态代码会让代理类再继承一个Proxy
类,里面定义了一个InvocationHandler
接口的对象,代理类中会通过super(h)
调用父类Proxy的构造,这里建议结合视频教程理解。
p39 038-第十二讲-jdk代理字节码生成
p40 039-第十二讲-jdk反射优化
p41 040-第十三讲-cglib代理原理
p42 041-第十三讲-cglib代理原理-MethodProxy
p43 042-第十四讲-MethodProxy原理
p44 043-第十四讲-MethodProxy原理
p45 044-第十五讲-Spring选择代理
p46 045-第十五讲-Spring选择代理
p47 046-第十五讲-Spring选择代理
p48 047-第十六讲-切点匹配
p49 048-第十六讲-切点匹配
p50 049-第十七讲-Advisor与@Aspect
p51 050-第十七讲-findEligibleAdvisors
p52 051-第十七讲-wrapIfNecessary
p53 052-第十七讲-代理创建时机
p54 053-第十七讲-吐槽@Order
p55 054-第十七讲-高级切面转低级切面
p56 055-第十八讲-统一转换为环绕通知
p57 056-第十八讲-统一转换为环绕通知
p58 057-第十八讲-适配器模式
p59 058-第十八讲-调用链执行
p60 059-第十八讲-模拟实现调用链
p61 060-第十八讲-模拟实现调用链-责任链模式
p62 061-第十九讲-动态通知调用
p63 062-第十九讲-动态通知调用
p64 063-第廿讲-DispatcherServlet初始化时机
p65 064-第廿讲-DispatcherServlet初始化时机
p66 065-第廿讲-DispatcherServlet初始化执行的操作
p67 066-第廿讲-RequestMappingHandlerMapping
p68 067-第廿讲-RequestMappingHandlerAdapter
p69 068-第廿讲-RequestMappingHandlerAdapter-参数和返回值解析器
p70 069-第廿讲-RequestMappingHandlerAdapter-自定义参数解析器
p71 070-第廿讲-RequestMappingHandlerAdapter-自定义返回值解析器
p72 071-第廿一讲-参数解析器-准备
p73 072-第廿一讲-参数解析器-准备
p74 073-第廿一讲-参数解析器-@RequestParam 0-4
p75 074-第廿一讲-参数解析器-组合模式
p76 075-第廿一讲-参数解析器 5-9
p77 076-第廿一讲-参数解析器 10-12
p78 077-第廿二讲-获取参数名
p79 078-第廿二讲-获取参数名
p80 079-第廿三讲-两套底层转换接口
p81 080-第廿三讲-一套高层转换接口
p82 081-第廿三讲-类型转换与数据绑定演示
p83 082-第廿三讲-web环境下数据绑定演示
p84 083-第廿三讲-绑定器工厂
p85 084-第廿三讲-绑定器工厂-@InitBinder扩展
p86 085-第廿三讲-绑定器工厂-ConversionService扩展
p87 086-第廿三讲-绑定器工厂-默认ConversionService
p88 087-第廿三讲-加餐-如何获取泛型参数
p89 088-第廿四讲-@ControllerAdvice-@InitBinder
p90 089-第廿四讲-@ControllerAdvice-@InitBinder
p91 090-第廿五讲-控制器方法执行流程
p92 091-第廿五讲-控制器方法执行流程
p93 092-第廿五讲-控制器方法执行流程-代码
p94 093-第廿六讲-@ControllerAdvice-@ModelAttribute
p95 094-第廿七讲-返回值处理器
p96 095-第廿七讲-返回值处理器-1
p97 096-第廿七讲-返回值处理器-2-4
p98 097-第廿七讲-返回值处理器-5-7
p99 098-第廿八讲-MessageConverter
p100 099-第廿八讲-MessageConverter
p101 100-第廿九讲-@ControllerAdvice-ResponseBodyAdvice
p102 101-第廿九讲-@ControllerAdvice-ResponseBodyAdvice
p103 102-第卅讲-异常处理
p104 103-第卅讲-异常处理
p105 104-第卅一讲-@ControllerAdvice-@ExceptionHandler
p106 105-第卅二讲-tomcat异常处理
p107 106-第卅二讲-tomcat异常处理-自定义错误地址
p108 107-第卅二讲-tomcat异常处理-BasicErrorController
p109 108-第卅二讲-tomcat异常处理-BasicErrorController
p110 109-第卅三讲-HandlerMapping与HandlerAdapter-1
p111 110-第卅三讲-HandlerMapping与HandlerAdapter-自定义
p112 111-第卅四讲-HandlerMapping与HandlerAdapter-2
p113 112-第卅五讲-HandlerMapping与HandlerAdapter-3
p114 113-第卅五讲-HandlerMapping与HandlerAdapter-3-优化
p115 114-第卅五讲-HandlerMapping与HandlerAdapter-3-优化
p116 115-第卅五讲-HandlerMapping与HandlerAdapter-4-欢迎页
p117 116-第卅五讲-HandlerMapping与HandlerAdapter-总结
p118 117-第卅六讲-MVC执行流程
p119 118-第卅六讲-MVC执行流程
p120 119-第卅七讲-构建boot骨架项目
p121 120-第卅八讲-构建boot war项目
p122 121-第卅八讲-构建boot war项目-用外置tomcat测试
p123 122-第卅八讲-构建boot war项目-用内嵌tomcat测试
p124 123-第卅九讲-boot执行流程-构造
p125 124-第卅九讲-boot执行流程-构造-1
p126 125-第卅九讲-boot执行流程-构造-2
p127 126-第卅九讲-boot执行流程-构造-3
p128 127-第卅九讲-boot执行流程-构造-4-5
p129 128-第卅九讲-boot执行流程-run-1
p130 129-第卅九讲-boot执行流程-run-1
p131 130-第卅九讲-boot执行流程-run-8-11
p132 131-第卅九讲-boot执行流程-run-2,12
p133 132-第卅九讲-boot执行流程-run-3
p134 133-第卅九讲-boot执行流程-run-4
p135 134-第卅九讲-boot执行流程-run-5
p136 135-第卅九讲-boot执行流程-run-5
p137 136-第卅九讲-boot执行流程-run-6
p138 137-第卅九讲-boot执行流程-run-7
p139 138-第卅九讲-boot执行流程-小结
p140 139-第卌讲-Tomcat重要组件
p141 140-第卌讲-内嵌Tomcat
p142 141-第卌讲-内嵌Tomcat与Spring整合
p143 142-第卌一讲-自动配置类原理
p144 143-第卌一讲-自动配置类原理
p145 144-第卌一讲-AopAutoConfiguration
p146 145-第卌一讲-AopAutoConfiguration
p147 146-第卌一讲-自动配置类2-4概述
p148 147-第卌一讲-自动配置类2-DataSource
p149 148-第卌一讲-自动配置类3-MyBatis
p150 149-第卌一讲-自动配置类3-mapper扫描
p151 150-第卌一讲-自动配置类4-事务
p152 151-第卌一讲-自动配置类5-MVC
p153 152-第卌一讲-自定义自动配置类
p154 153-第卌二讲-条件装配底层1
p155 154-第卌二讲-条件装配底层2
p156 155-第卌三讲-FactoryBean
p157 156-第卌四讲-@Indexed
p158 157-第卌五讲-Spring代理的特点
p159 158-第卌五讲-Spring代理的特点
p160 159-第卌六讲-@Value注入底层1
p161 160-第卌六讲-@Value注入底层2
p162 161-第卌七讲-@Autowired注入底层-doResolveDependency外1
p163 162-第卌七讲-@Autowired注入底层-doResolveDependency外2
p164 163-第卌七讲-@Autowired注入底层-doResolveDependency内1
p165 164-第卌七讲-@Autowired注入底层-doResolveDependency内2
p166 165-第卌七讲-@Autowired注入底层-doResolveDependency内3
p167 166-第卌七讲-@Autowired注入底层-doResolveDependency内4
p168 167-第卌八讲-事件监听器1
p169 168-第卌八讲-事件监听器2
p170 169-第卌八讲-事件监听器3
p171 170-第卌八讲-事件监听器4
p172 171-第卌八讲-事件监听器5
p173 172-第卌九讲-事件发布器1
p174 173-第卌九讲-事件发布器2