Spring高级
一
、容器与bean
a01
、容器接口(BeanFactory
与ApplicationContext
的区别与联系)
①、到底什么是 BeanFactory
?
BeanFactory
接口:
首先我们先了解一下BeanFactory
接口:
我们写一段springboot项目的启动类如下:
@SpringBootApplication
public class A01Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
}
}
- 上述代码中
SpringApplication.run(A01Application.class, args)
方法返回一个容器对象,我们使用idea查看这个容器类的继承关系(快捷键:Ctrl + alt + u)如下:
-
我们发现
ConfigurableApplicationContext
继承与ApplicationContext
-
ApplicationContext
继承与BeanFactory
到底什么是BeanFactory
?
-
它是
ApplicationContext
的父接口 -
它才是
Spring
的核心容器,主要的ApplicationContext
实现都 [组合]了它的功能在web环境下,我们打印ConfigurableApplicationContext的class对象发现它其实是
AnnotationConfigServletWebServerApplicationContext
类型
我们接着查看AnnotationConfigServletWebServerApplicationContext
类的继承图,
按图所示:AnnotationConfigServletWebServerApplicationContext
又间接继承了GenericApplicationContext
我们跟进GenericApplicationContext源码:发现beanFactory是该类的成员变量。
②、BeanFactory
的功能
BeanFactory
接口中的方法:
- 表面上只有
getBean
- 实际上控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能,都由它的实现类提供
我们可以查看springboot
默认的ConfigurableApplicationContext
类中的BeanFactory
的实际类型
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 查看实际类型
// class org.springframework.beans.factory.support.DefaultListableBeanFactory
System.out.println(beanFactory.getClass());
}
从打印结果可以了解到实际类型为DefaultListableBeanFactory
,所以这里以BeanFactory
的一个实现类DefaultListableBeanFactory
作为出发点,进行分析。
这里我们暂且不细看DefaultListableBeanFactory
,先看DefaultListableBeanFactory
的父类DefaultSingletonBeanRegistry
,先选中它,然后双击,可以跳转到对应的源码,可以看到有个私有的成员变量singletonObjects
知道了这些关系,我们可以利用反射获取singletonObjects
进行查看代码如下:
- 首先我们人为往spring容器中添加两个bean
//bean1
package com.colin.a01;
import org.springframework.stereotype.Component;
@Component
public class Component1 {
}
//bean2
package com.colin.a01;
import org.springframework.stereotype.Component;
@Component
public class Component2 {
}
- 利用反射查看我们定义的bean是否在
singletonObjects
中
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);
System.out.println(beanFactory.getClass());
map.entrySet().stream().filter(entry -> entry.getKey().startsWith("component")).forEach(System.out::println);
}
结果如下:说明单例池在BeanFactory
。
③、ApplicationContext
比 BeanFactory
多点啥?
-
多实现了四个接口:
MessageSource
: 国际化功能,支持多种语言ResourcePatternResolver
: 通配符匹配资源路径EnvironmentCapable
: 环境信息,系统环境变量,.properties、.application.yml等配置文件中的值ApplicationEventPublisher
: 发布事件对象
蓝色框标识:
⑴、演示MessageSource
: 国际化功能
-
我们在resource下建立三个语言翻译的properties
其实还应该有一个默认语言环境的properties名字为
messages.properties
当所有语言环境都不匹配时它就生效图省事没有创建,真实开发一定要创建
内容如下: -
我们编写java代码从测试: 其中
GenericApplicationContext
是干净的ApplicationContext 什么功能都没有我们编程注入一个bean ,bean的类型为
MessageSource
调用
GenericApplicationContext
的refresh
方法加载或刷新配置使用
Locale
指定语言环境说明: 真实开发,语言环境由前台浏览器携带请求头传递,这里只是演示,真实开发是使用@Bean注入
messageSource
只是演示
public static void main(String[] args) {
//干净的ApplicationContext 什么功能都没有
GenericApplicationContext context = new GenericApplicationContext();
//编程注入一个bean bean的类型为MessageSource
context.registerBean("messageSource", MessageSource.class, () -> {
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
ms.setDefaultEncoding("utf-8");
ms.setBasename("messages");
return ms;
});
//加载容器
context.refresh();
//测试
System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
System.out.println(context.getMessage("hi", null, Locale.CHINESE));
System.out.println(context.getMessage("hi", null, Locale.JAPANESE));
}
- 结果如下: 我们发现由于语言环境的不同spring帮我们返回来不同的键值。实现了国际化的功能
注意:
- ApplicationContext 中 MessageSource bean 的名字固定为 messageSource
- 使用 SpringBoot 时,国际化文件名固定为 messages
- 空的 messages.properties 也必须存在
⑵、演示ResourcePatternResolver
: 通配符匹配资源路径
- 获取类路径下的
messages
开头的配置文件
//测试ResourcePatternResolver
Resource[] resources = context.getResources("classpath:messages*.properties");
for (Resource resource : resources) {
System.out.println(resource);
}
运行结果:
- 获取
spring
相关jar
包中的spring.factories
配置文件
//测试获取spring相关jar包中的spring.factories配置文件
Resource[] resources = context.getResources("classpath*:META-INF/spring.factories");
for (Resource resource : resources) {
System.out.println(resource);
}
运行结果:
⑶、演示EnvironmentCapable
: 环境信息
获取系统环境变量中的java_home
和项目的application.yml
中的server.port
属性:
//测试获取系统环境变量中的`java_home`和项目的`application.yml`中的`server.port`属性
System.out.println(context.getEnvironment().getProperty("java_home"));
System.out.println(context.getEnvironment().getProperty("server.port"));
结果如下:
⑷、演示ApplicationEventPublisher
: 发布事件对象(重要)
我们模拟用户注册发布事件:
- 定义一个用户注册事件类,继承自
ApplicationEvent
类
import org.springframework.context.ApplicationEvent;
public class UserRegisteredEvent extends ApplicationEvent {
public UserRegisteredEvent(Object source) {
super(source);
}
}
- 定义一个监听器类,主要用来监听事件,处理事件。类头上需要加
@Component
注解,将该类交给spring
管理,定义一个处理事件的方法,参数类型为用户注册事件类的对象,方法头上需要加上@EventListener
注解
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class UserRegisteredListener {
@EventListener
public void userRegist(UserRegisteredEvent event) {
System.out.println("UserRegisteredEvent...");
log.debug("{}", event);
}
}
- 定义一个service 用来模拟用户注册业务,注册完毕发送用户注册事件
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class UserService {
@Autowired
private ApplicationEventPublisher context;
public void register(String username, String password) {
log.debug("新用户注册,账号:" + username + ",密码:" + password);
context.publishEvent(new UserRegisteredEvent(this));
}
}
- 在启动类模拟调用
//模拟调用事件
UserService userService = context.getBean(UserService.class);
userService.register("colin","123456");
结果如下:
④、a01
总结:
学到了什么?
a. BeanFactory 与 ApplicationContext 并不仅仅是简单接口继承的关系, ApplicationContext 组合并扩展了 BeanFactory 的功能
b. 又新学一种代码之间解耦途径(发布事件)
a02
、容器的实现
Spring 的发展历史较为悠久,因此很多资料还在讲解它较旧的实现,这里出于怀旧的原因,把它们都列出来,供大家参考
- DefaultListableBeanFactory,是 BeanFactory 最重要的实现,像控制反转和依赖注入功能,都是它来实现
- ClassPathXmlApplicationContext,从类路径查找 XML 配置文件,创建容器(旧)
- FileSystemXmlApplicationContext,从磁盘路径查找 XML 配置文件,创建容器(旧)
- XmlWebApplicationContext,传统 SSM 整合时,基于 XML 配置文件的容器(旧)
- AnnotationConfigWebApplicationContext,传统 SSM 整合时,基于 java 配置类的容器(旧)
- AnnotationConfigApplicationContext,Spring boot 中非 web 环境容器(新)
- AnnotationConfigServletWebServerApplicationContext,Spring boot 中 servlet web 环境容器(新)
- AnnotationConfigReactiveWebServerApplicationContext,Spring boot 中 reactive web 环境容器(新)
另外要注意的是:后面这些带有 ApplicationContext 的类都是 ApplicationContext 接口的实现,但它们是组合了 DefaultListableBeanFactory 的功能,并非继承而来
①、DefaultListableBeanFactory
接着第一讲中的内容,执行以下代码,可以了解到
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 查看实际类型
// class org.springframework.beans.factory.support.DefaultListableBeanFactory
System.out.println(beanFactory.getClass());
ConfigurableApplicationContext
类内部组合的BeanFactory
实际类型为DefaultListableBeanFactory
spring
底层创建实体类就是依赖于这个类,所以它是BeanFactory
接口最重要的一个实现类。
-
模拟一下
spring
使用DefaultListableBeanFactory
类创建其他实体类对象的过程。-
准备工作首先我们在类中定义一个配置类,并增加几个bean的声明
package com.colin.a02; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; 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.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; public class TestBeanFactory { public static void main(String[] args) { } @Configuration static class Config { @Bean public Bean1 bean1() { return new Bean1(); } @Bean public Bean2 bean2() { return new Bean2(); } } static class Bean1 { public Bean1() { log.debug("构造 Bean1()"); } } static class Bean2 { } }
-
紧接着我们在main方法中利用
DefaultListableBeanFactory
和bean的定义信息BeanDefinition
把配置类注册到spring容器。打印所有的bean的定义信息。public static void main(String[] args) { //创建BeanFactory重要的实现类DefaultListableBeanFactory DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); /** * 准备bean 的定义信息(class 类信息、scope作用域、初始化方法,销毁方法) */ AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder .genericBeanDefinition(Config.class) .setScope("singleton") .getBeanDefinition(); //将bean定义注册到beanfactory beanFactory.registerBeanDefinition("config",beanDefinition); //查看所有bean的定义信息 for (String name : beanFactory.getBeanDefinitionNames()) { System.out.println(name); } }
结果如下:
我们通过结果发现:容器中只有config类,但在配置类中的@Bean
标注的Bean1、Bean2都不在容器中。这是因为beanFactory没有能力处理
@Configuration
和@Bean
注解,我们需要为beanFactory添加一些Bean工厂的后处理器,让后处理器处理这些事情。 -
我们为
beanFactory
注入常用的处理器,并再次打印容器中bean的所有定义// 给 BeanFactory 添加一些常用的后处理器 AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory); //查看所有bean的定义信息 for (String name : beanFactory.getBeanDefinitionNames()) { System.out.println(name); }
结果如下:
我们发现除了config,又多了很多bean的定义信息,这些多的bean就是AnnotationConfigUtils
为我们添加的。但还是没有我们自己定义的bean信息,这是因为,我们只是将后处理器放入到了容器中,并没有调用它们。 -
我们接着手动调用这些后处理器(Bean工厂的后处理器都实现了
BeanFactoryPostProcessor
接口)//调用 BeanFactory 的后处理器 // BeanFactory 后处理器主要功能,补充了一些 bean 定义 System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> { //查看类型 System.out.println(beanFactoryPostProcessor.getClass()); //手动调用后处理器 beanFactoryPostProcessor.postProcessBeanFactory(beanFactory); }); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); //再次查看所有bean的定义信息 for (String name : beanFactory.getBeanDefinitionNames()) { System.out.println(name); }
输出结果:
可以看到:我们手动调用了以下后处理器。
- org.springframework.context.annotation.ConfigurationClassPostProcessor
- org.springframework.context.event.EventListenerMethodProcessor
再次查看所有bean的定义信息我们看到了bean1、bean2
-
现在我们继续研究,我们已经看到了bean1的定义,那下面我们试一下这些bean能不能正常使用
System.out.println(beanFactory.getBean(Bean1.class));
结果:
这里有一个细节(打印了 构造 Bean1()):说明只有我们
getBean
的时候BeanFactory
才会真正的根据bean的定义信息创建这个bean
,BeanFactory
并不会主动创建bean
-
可以看到可以使用,那我们改造一下代码,改变一下代码把依赖注入加上
//改造bean1 static class Bean1 { private static final Logger log = LoggerFactory.getLogger(Bean1.class); public Bean1() { log.debug("构造 Bean1()"); } @Autowired private Bean2 bean2; public Bean2 getBean2() { return bean2; } }
调用bean1的getBean2方法
System.out.println(beanFactory.getBean(Bean1.class).getBean2());
结果:
我们发现打印为null,这是因为BeanFactory
没有处理@Autowired
的能力,那是不是缺少相应的后处理器呢?这里注意的是BeanFactory 后处理器主要功能,补充了一些 bean 定义。 而依赖注入是创建bean的时期要处理的。并不是用来补充 bean的 定义。
我们是要添加后处理器,但这个后处理器不是针对
BeanFactory
的,而是针对bean
,我们把针对 bean 的生命周期的各个阶段提供扩展的后处理器称为Bean 后处理器-
AnnotationConfigUtilsy
已经为我们添加了Bean 后处理器的定义信息,我们要将Bean 后处理器设置给BeanFactory
让其发挥作用Bean 后处理器都实现了
BeanPostProcessor
接口// System.out.println(beanFactory.getBean(Bean1.class)); // System.out.println(beanFactory.getBean(Bean1.class).getBean2()); //给beanFactory设置bean后处理器 beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream().forEach( beanPostProcessor -> { System.out.println(">>>>>" + beanPostProcessor); beanFactory.addBeanPostProcessor(beanPostProcessor); } ); //再次查看bean1 System.out.println(beanFactory.getBean(Bean1.class)); System.out.println(beanFactory.getBean(Bean1.class).getBean2());
结果:
我们可以看到:
给beanFactory设置bean后处理器:
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor bean1也已经有了bean2:
com.colin.a02.TestBeanFactory$Bean1@33d512c1
com.colin.a02.TestBeanFactory$Bean2@515c6049
-
-
探究
Bean
后处理器有排序的逻辑-
准备工作定义一个接口Inter,再定义两个Bean,名称分别为Bean3和Bean4,都继承Inter,接着在Config中通过@Bean注解将Bean3和Bean4都加进Bean工厂中,然后在Bean1中定义一个Inter对象,通过@Autowired注解将实现类注入进来。
@Configuration static class Config { @Bean public Bean1 bean1() { return new Bean1(); } @Bean public Bean2 bean2() { return new Bean2(); } //将bean3加进Bean工厂 @Bean public Bean3 bean3() { return new Bean3(); } //将bean4加进Bean工厂 @Bean public Bean4 bean4() { return new Bean4(); } } //定义接口 interface Inter { } //bean3 static class Bean3 implements Inter { } //bean4 static class Bean4 implements Inter { } static class Bean1 { private static final Logger log = LoggerFactory.getLogger(Bean1.class); public Bean1() { log.debug("构造 Bean1()"); } @Autowired private Bean2 bean2; public Bean2 getBean2() { return bean2; } //注入Inter @Autowired private Inter bean3; public Inter getInter() { return bean3; } } static class Bean2 { }
-
由于
BeanFactory
不会主动创建bean
,所以我们调用BeanFactory
类中重要的方法preInstantiateSingletons()
preInstantiateSingletons()
方法会准备好了所有的单例bean
我们从容器中取出
bean1
然后调用getInter()
看成员变量bean3注入的类型到底是什么类型?public static void main(String[] args) { //.....以前代码 //调用BeanFactory类中重要的方法preInstantiateSingletons准备好了所有的单例 beanFactory.preInstantiateSingletons(); // 准备好所有单例 //取出bean1 Bean1 bean1 = beanFactory.getBean(Bean1.class); //调用getInter() System.out.println(bean1.getInter()); }
结果如下:
- 我们发现注入的是
bean3
。 - 我们分析一下原因:
@Autowired
是根据类型注入,但容器中是Inter
类型的有两个,bean3
、bean4
,所以@Autowired
会根据变量的名称去配置bean的名称,所以注入的是bean3
- 我们发现注入的是
-
我们接着实验,我们除了使用
@Autowired
注解还可以使用@Resource
注解实现依赖注入。所以我们去除@Autowired
使用@Resource
并指明bean的名字,查看结果。//改造bean1 static class Bean1 { private static final Logger log = LoggerFactory.getLogger(Bean1.class); public Bean1() { log.debug("构造 Bean1()"); } @Autowired private Bean2 bean2; public Bean2 getBean2() { return bean2; } @Resource(name = "bean4") private Inter bean3; public Inter getInter() { return bean3; } }
确实注入对象变成了
bean4
-
我们接着如果
@Autowired
和@Resource
同时存在(正常开发,不会怎么干,这只是实验),会注入什么对象呢?//改造bean1 static class Bean1 { private static final Logger log = LoggerFactory.getLogger(Bean1.class); public Bean1() { log.debug("构造 Bean1()"); } @Autowired private Bean2 bean2; public Bean2 getBean2() { return bean2; } @Autowired @Resource(name = "bean4") private Inter bean3; public Inter getInter() { return bean3; } }
结果又变成了
bean3
,这是为什么呢? 我们研究一下。-
首先我们知道,
BeanFactory
并部具备处理这些依赖注入相关注解的能力,处理@Autowired
和@Resource
都是bean后处理器工作。而这些后处理器是我们调用AnnotationConfigUtils
工具类内的方法添加的。 -
以往学习我们知道
AnnotationConfigUtils
为BeanFactory
添加的bean后处理器主要有两个org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor(处理@Autowired
)
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor(处理@Resource
) -
我们点进去
AnnotationConfigUtils
的registerAnnotationConfigProcessors()
方法看一下源码- 我们发现
AutowiredAnnotationBeanPostProcessor
确实比CommonAnnotationBeanPostProcessor
顺序靠前
- 我们发现
-
AnnotationConfigUtils
不仅为BeanFcatory
添加了一些常用后处理器,同时也为BeanFcatory
添加了默认的后处理排序规则, 补充后处理器内有order
属性就是排序的依据,数字越小,排名越往前,反之越靠后。 -
我们通过代码试着改变一下后处理顺序,看一下效果
//改变bean后处理顺序前 System.out.println("改变顺序前"); beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream().forEach( beanPostProcessor -> { System.out.println(">>>>>" + beanPostProcessor); } ); //改变顺序后 System.out.println("改变顺序后"); beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream() .sorted(beanFactory.getDependencyComparator()) .forEach(beanPostProcessor -> { System.out.println(">>>>" + beanPostProcessor); beanFactory.addBeanPostProcessor(beanPostProcessor); }); //调用BeanFactory类中重要的方法preInstantiateSingletons准备好了所有的单例 beanFactory.preInstantiateSingletons(); // 准备好所有单例 //取出bean1 Bean1 bean1 = beanFactory.getBean(Bean1.class); //调用getInter() System.out.println(bean1.getInter());
结果:如下图所示,改变顺序后
CommonAnnotationBeanPostProcessor
排在了AutowiredAnnotationBeanPostProcessor
之前,依赖注入只会进行一次,所以在依赖注入时先是@Resource
被解析完成了依赖注入,@Autowired
后被解析,但依赖注入已经完成,所以不起效果,所以注入的对象是bean4
-
-
-
⑴、DefaultListableBeanFactory总结:
学到了什么:
beanFactory 不会做的事
a.不会主动调用 BeanFactory 后处理器
b.不会主动添加 Bean 后处理器
c.不会主动初始化单例
d.不会解析: beanFactory 还不会解析 ${ } 与 #{ }
e. bean 后处理器会有排序的逻辑
②、常见 ApplicationContext 实现
-
四个重要的
ApplicationContext
接口的实现类ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
AnnotationConfigApplicationContext
AnnotationConfigServletWebServerApplication
⑴、测试ClassPathXmlApplicationContext
-
在
resources
下新建a02.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 控制反转, 让 bean1 被 Spring 容器管理 --> <bean id="bean1" class="com.colin.a02.A02.Bean1"/> <!-- 控制反转, 让 bean2 被 Spring 容器管理 --> <bean id="bean2" class="com.colin.a02.A02.Bean2"> <!-- 依赖注入, 建立与 bean1 的依赖关系 --> <property name="bean1" ref="bean1"/> </bean>
-
main方法测试
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.mvc.Controller; /* 常见 ApplicationContext 实现 */ public class A02 { private static final Logger log = LoggerFactory.getLogger(A02.class); public static void main(String[] args) { testClassPathXmlApplicationContext(); } // ⬇️较为经典的容器, 基于 classpath 下 xml 格式的配置文件来创建 private static void testClassPathXmlApplicationContext() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("a02.xml"); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); } System.out.println(context.getBean(Bean2.class).getBean1()); }
测试结果:
⑵、测试FileSystemXmlApplicationContext
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.Controller;
/*
常见 ApplicationContext 实现
*/
public class A02 {
private static final Logger log = LoggerFactory.getLogger(A02.class);
public static void main(String[] args) {
testFileSystemXmlApplicationContext();
}
// ⬇️基于磁盘路径下 xml 格式的配置文件来创建
private static void testFileSystemXmlApplicationContext() {
FileSystemXmlApplicationContext context =
new FileSystemXmlApplicationContext(
"src\\main\\resources\\a02.xml");
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}
}
测试结果:
⑶、测试AnnotationConfigApplicationContext
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.Controller;
/*
常见 ApplicationContext 实现
*/
public class A02 {
private static final Logger log = LoggerFactory.getLogger(A02.class);
public static void main(String[] args) {
testAnnotationConfigApplicationContext();
}
// ⬇️较为经典的容器, 基于 java 配置类来创建
private static void testAnnotationConfigApplicationContext() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}
@Configuration
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public Bean2 bean2(Bean1 bean1) {
Bean2 bean2 = new Bean2();
bean2.setBean1(bean1);
return bean2;
}
}
static class Bean1 {
}
static class Bean2 {
private Bean1 bean1;
public void setBean1(Bean1 bean1) {
this.bean1 = bean1;
}
public Bean1 getBean1() {
return bean1;
}
}
}
测试结果:
⑷、测试AnnotationConfigServletWebServerApplication
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.Controller;
/*
常见 ApplicationContext 实现
*/
public class A02 {
private static final Logger log = LoggerFactory.getLogger(A02.class);
public static void main(String[] args) {
testAnnotationConfigServletWebServerApplicationContext();
}
// ⬇️较为经典的容器, 基于 java 配置类来创建, 用于 web 环境
private static void testAnnotationConfigServletWebServerApplicationContext() {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
@Configuration
static class WebConfig {
/**
* 创建web 环境:
* 声明一个web容器工厂,
* 声明一个DispatcherServlet,
* 将DispatcherServlet注册到web容器
*
* @return
*/
@Bean
public ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}
//最简单的控制器 bean的名字就是访问路径 路径是:/hello
@Bean("/hello")
public Controller controller1() {
return (request, response) -> {
response.getWriter().print("hello");
return null;
};
}
}
}
测试结果:
访问浏览器http://localhost:8080/hello
⑸、模拟ApplicationContext
实现都干了什么?
-
以
ClassPathXmlApplicationContex
t示例public static void main(String[] args) { 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(new ClassPathResource("a02.xml")); for (String name : beanFactory.getBeanDefinitionNames()) { System.out.println(name); } }
测试结果:
⑹、总结
学到了什么
a. 常见的 ApplicationContext 容器实现
b. 内嵌容器、DispatcherServlet 的创建方法、作用
a03
、Bean
的生命周期
- 一个受 Spring 管理的 bean,生命周期主要阶段有
- 创建:根据 bean 的构造方法或者工厂方法来创建 bean 实例对象
- 依赖注入:根据 @Autowired,@Value 或其它一些手段,为 bean 的成员变量填充值、建立关系
- 初始化:回调各种 Aware 接口,调用对象的各种初始化方法
- 销毁:在容器关闭时,会销毁所有单例对象(即调用它们的销毁方法)
- prototype 对象也能够销毁,不过需要容器这边主动调用
一些资料会提到,生命周期中还有一类 bean 后处理器:BeanPostProcessor,会在 bean 的初始化的前后,提供一些扩展逻辑。但这种说法是不完整的因为bean 后处理器可能在bean的各个生命周期里发挥作用。
①、演示1 - bean 生命周期
-
springboot
项目启动类@SpringBootApplication public class A03 { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A03.class, args); context.close(); } }
-
定义一个
LifeCycleBean
,加上@Component
注解,再编写一些方法,给这些方法加上Bean
的生命周期过程中的注解import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Slf4j @Component public class LifeCycleBean { public LifeCycleBean() { log.info("构造方法"); } @Autowired public void autowire(@Value("${java_home}") String home) { log.info("依赖注入:{}", home); } @PostConstruct public void init() { log.info("初始化方法"); } @PreDestroy public void destory() { log.info("销毁方法"); } }
结果打印:
执行顺序是:
构造方法 》》 依赖注入 》》 初始化方法 》》 销毁方法
-
下面我们编写自定义
Bean
的后处理器,对lifeCycleBean
的生命周期过程进行扩展,编写自定义Bean
的后处理器需要实现InstantiationAwareBeanPostProcessor
和DestructionAwareBeanPostProcessor
接口,并加上@Component
注解。import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import org.springframework.stereotype.Component; /** * 自定义bean后处理器 */ @Slf4j @Component public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor { /** * 在销毁之前调用 * @param bean * @param beanName * @throws BeansException */ @Override public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { if (beanName.equals("lifeCycleBean")) { log.info("<<<<<< 销毁之前执行 (postProcessBeforeDestruction), 如 @PreDestroy"); } } /** * 实例化之前(未调用构造方法)执行 * 有返回值 会替代原本的 bean * @param beanClass * @param beanName * @return * @throws BeansException */ @Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { if (beanName.equals("lifeCycleBean")) { log.info("<<<<<< 实例化之前(未调用构造方法)执行(postProcessBeforeInstantiation), 这里返回的对象会替换掉原本的 bean"); } return null; } /** * 实例化之后(调用构造方法后)执行 * 如果返回 false 会跳过依赖注入阶段 * @param bean * @param beanName * @return * @throws BeansException */ @Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { if (beanName.equals("lifeCycleBean")) { log.info("<<<<<< 实例化之后(调用构造方法后)执行(postProcessAfterInstantiation), 这里如果返回 false 会跳过依赖注入阶段"); // return false; } return true; } /** * 依赖注入阶段执行 * @param pvs * @param bean * @param beanName * @return * @throws BeansException */ @Override public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException { if (beanName.equals("lifeCycleBean")) { log.info("<<<<<< 依赖注入阶段执行(postProcessProperties), 如 @Autowired、@Value、@Resource"); } return pvs; } /** * 初始化之前执行 * 有返回值 会替代原本的 bean * @param bean * @param beanName * @return * @throws BeansException */ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (beanName.equals("lifeCycleBean")) { log.info("<<<<<< 初始化之前执行(postProcessBeforeInitialization), 这里返回的对象会替换掉原本的 bean, 如 @PostConstruct、@ConfigurationProperties"); } return bean; } /** * 初始化之后执行 * 有返回值 会替代原本的 bean * @param bean * @param beanName * @return * @throws BeansException */ @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (beanName.equals("lifeCycleBean")) { log.info("<<<<<< 初始化之后执行(postProcessAfterInitialization), 这里返回的对象会替换掉原本的 bean, 如代理增强"); } return bean; } }
结果打印:
把
postProcessAfterInstantiation
返回值改为false,让bean跳过依赖注入阶段结果:依赖注入语句无打印
②、演示2 - 模板方法设计模式
- 提高代码的扩展性。
import java.util.ArrayList;
import java.util.List;
public class TestMethodTemplate {
public static void main(String[] args) {
MyBeanFactory beanFactory = new MyBeanFactory();
beanFactory.addBeanPostProcessor(bean -> System.out.println("解析 @Autowired"));
beanFactory.addBeanPostProcessor(bean -> System.out.println("解析 @Resource"));
beanFactory.getBean();
}
// 模板方法 Template Method Pattern
static class MyBeanFactory {
public Object getBean() {
Object bean = new Object();
System.out.println("构造 " + bean);
System.out.println("依赖注入 " + bean); // @Autowired, @Resource
for (BeanPostProcessor processor : processors) {
processor.inject(bean);
}
System.out.println("初始化 " + bean);
return bean;
}
private List<BeanPostProcessor> processors = new ArrayList<>();
public void addBeanPostProcessor(BeanPostProcessor processor) {
processors.add(processor);
}
}
static interface BeanPostProcessor {
public void inject(Object bean); // 对依赖注入阶段的扩展
}
}
④、演示3 - bean 后处理器排序
package com.colin.a03;
import cn.hutool.core.collection.ListUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.Order;
import java.util.ArrayList;
import java.util.List;
/*
* @Author Colin
* @Description //TODO 测试后处理器的排序
* @Date 2022/6/4 10:33
**/
@Slf4j
public class TestProcessorOrder {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
List<BeanPostProcessor> list = new ArrayList<>(ListUtil.of(new P1(), new P2(), new P3(), new P4(), new P5()));
list.sort(beanFactory.getDependencyComparator());
list.forEach(processor -> {
processor.postProcessBeforeInitialization(new Object(), "");
});
/*
学到了什么
1. 实现了 PriorityOrdered 接口的优先级最高
2. 实现了 Ordered 接口与加了 @Order 注解的平级, 按数字升序
3. 其它的排在最后
*/
}
@Order(1)
static class P1 implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
log.info("postProcessBeforeInitialization @Order(1)");
return bean;
}
}
@Order(2)
static class P2 implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
log.info("postProcessBeforeInitialization @Order(2)");
return bean;
}
}
static class P3 implements BeanPostProcessor, PriorityOrdered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
log.info("postProcessBeforeInitialization PriorityOrdered");
return bean;
}
@Override
public int getOrder() {
return 100;
}
}
static class P4 implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
log.info("postProcessBeforeInitialization Ordered");
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
static class P5 implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
log.info("postProcessBeforeInitialization");
return bean;
}
}
}
运行结果:
我们可以从运行结果看到:
-
实现了 PriorityOrdered 接口的优先级最高
-
实现了 Ordered 接口与加了 @Order 注解的平级, 按数字升序
-
其它的排在最后
a04
、Bean
后处理器
①、演示1 - 后处理器作用
-
Bean
后处理器是为Bean
服务的,准备工作: 准备4个bean并加上响应的解析注解。-
bean1
package com.colin.a04; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Slf4j @ToString public class Bean1 { private Bean2 bean2; @Autowired public void setBean2(Bean2 bean2) { log.info("@Autowired 生效:{}", bean2); this.bean2 = bean2; } @Autowired private Bean3 bean3; private String home; @Autowired public void setHome(@Value("${JAVA_HOME}") String home) { log.info("@Value 生效: {}", home); this.home = home; } @PostConstruct public void init() { log.debug("@PostConstruct 生效"); } @PreDestroy public void destroy() { log.debug("@PreDestroy 生效"); } }
-
bean2
package com.colin.a04; public class Bean2 { }
-
bean3
package com.colin.a04; public class Bean3 { }
-
bean4
package com.colin.a04; import lombok.Getter; import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; @Slf4j @Getter @Setter @ToString @ConfigurationProperties(prefix = "java") public class Bean4 { private String home; private String version; }
-
我们开始测试,准备一个干净的spring容器【
GenericApplicationContext
】用编程方式注册这四个bean
,他们会被注册为beanfactory
的BeanDefinition
我们再手动调用容器的初始化方法refresh()
让其根据BeanDefinition
创建单例bean
package com.colin.a04; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor; import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver; import org.springframework.context.support.GenericApplicationContext; public class A04 { public static void main(String[] args) { //⬇️GenericApplicationContext 是一个【干净】的容器 GenericApplicationContext context = new GenericApplicationContext(); //⬇️用原始方法注册三个bean context.registerBean("bean1", Bean1.class); context.registerBean("bean2", Bean2.class); context.registerBean("bean3", Bean3.class); context.registerBean("bean4", Bean4.class); // ⬇️初始化容器 context.refresh(); // 执行beanFactory后处理器, 添加bean后处理器, 初始化所有单例 //打印bean4 System.out.println(context.getBean(Bean1.class)); //打印bean4 System.out.println(context.getBean(Bean4.class)); // ⬇️销毁容器 context.close(); } }
结果:
-
结果中没有任何@Value
和@Autowired
以及初始化方法销毁方法的打印,bean1的依赖注入没有生效,bean4的属性也是空的。
-
解决
@Autowired
、@Value
没有生效,为beanfactory 添加处理@Autowired
、@Value
注解的bean
后处理器AutowiredAnnotationBeanPostProcessor
//解析@Autowired @Value的bean后处理器 AutowiredAnnotationBeanPostProcessor context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
运行结果:
-
为什么会这样呢?
- 这是因为
AutowiredAnnotationBeanPostProcessor
虽然有依赖注入的能力,但是它无法获取@Value的值${JAVA_HOME}
,获取值需要另外的类进行处理ContextAnnotationAutowireCandidateResolver
- 解析
${JAVA_HOME}
需要用到${}
的解析器在初始化容器时refresh()
会自动添加。
//用来获取@Value的值 为beanfactory 设置自动装配解析器 //ContextAnnotationAutowireCandidateResolver context.getDefaultListableBeanFactory().setAutowireCandidat2eResolver(new ContextAnnotationAutowireCandidateResolver());
再次运行:
此时的@Autowired
、@Value
就已经生效了。 - 这是因为
-
-
解决
@ConfigurationProperties
没有生效,添加bean
后处理器ConfigurationPropertiesBindingPostProcessor
//解析 @ConfigurationProperties注解 //ConfigurationPropertiesBindingPostProcessor ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());
再次运行:
bean4也恢复了正常。 -
解决
@PostConstruct
以及PreDestroy
没有生效,添加bean
后处理器CommonAnnotationBeanPostProcessor
//解析@Resource @PostConstruct @PreDestroy的bean后处理器 //CommonAnnotationBeanPostProcessor // @Resource @PostConstruct @PreDestroy context.registerBean(CommonAnnotationBeanPostProcessor.class);
结果:
总体代码:
package com.colin.a04;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.context.support.GenericApplicationContext;
public class A04 {
public static void main(String[] args) {
//⬇️GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
//⬇️用原始方法注册三个bean
context.registerBean("bean1", Bean1.class);
context.registerBean("bean2", Bean2.class);
context.registerBean("bean3", Bean3.class);
context.registerBean("bean4", Bean4.class);
//用来获取@Value的值 为beanfactory 设置自动装配解析器
//ContextAnnotationAutowireCandidateResolver
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
//解析@Autowired @Value的bean后处理器
//AutowiredAnnotationBeanPostProcessor
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
//解析@Resource @PostConstruct @PreDestroy的bean后处理器
//CommonAnnotationBeanPostProcessor
//@Resource @PostConstruct @PreDestroy
context.registerBean(CommonAnnotationBeanPostProcessor.class);
//解析 @ConfigurationProperties注解
//ConfigurationPropertiesBindingPostProcessor
ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());
// ⬇️初始化容器
context.refresh(); // 执行beanFactory后处理器, 添加bean后处理器, 初始化所有单例
System.out.println(context.getBean(Bean1.class));
System.out.println(context.getBean(Bean4.class));
// ⬇️销毁容器
context.close();
}
}
⑴、总结
- @Autowired 等注解的解析属于 bean 生命周期阶段(依赖注入, 初始化)的扩展功能
- 这些扩展功能由 bean 后处理器来完成
②、演示2 - @Autowired bean 后处理器运行分析
-
原始步骤拆解
package com.colin.a04; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver; import org.springframework.core.env.StandardEnvironment; /* * @Author Colin * @Description //TODO 模拟autowired解析过程 * @Date 2022/6/4 11:54 **/ public class DiInAutowired { public static void main(String[] args) { //测试原始步骤 base(); } static void base() { //创建beanfacory DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); //注册bean beanFactory.registerSingleton("bean2", new Bean2());//new Bean2() 它会认为这是成品对象。会跳过创建过程(依赖注入,初始化) beanFactory.registerSingleton("bean3", new Bean3()); //设置@value注解解析器 beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); //手动设置 ${}解析器 beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders); //创建处理@Autowired的bean后处理器 AutowiredAnnotationBeanPostProcessor beanPostProcessor = new AutowiredAnnotationBeanPostProcessor(); beanPostProcessor.setBeanFactory(beanFactory); Bean1 bean1 = new Bean1(); System.out.println("手动调用前处理器前"); System.out.println(bean1); //手动调用后处理器 beanPostProcessor.postProcessProperties(null, bean1, "bean1"); System.out.println("手动调用后处理器后"); beanPostProcessor.postProcessProperties(null, bean1, "bean1"); System.out.println(bean1); } }
测试结果:
我们发现手动调用后处理器的
postProcessProperties
方法实现了依赖注入和@Value
的解析,我们重点关注一下postProcessProperties
方法。我们看一下源码:
我们看到源码里,它先是根据我们传递的参数找到了一个类型为InjectionMetadata
的结果,然后调用了该类的inject
方法。
-
我们通过反射调用
findAutowiringMetadata()
,得到InjectionMetadata
并调用inject()
方法实现依赖注入。/** * processor.postProcessProperties(null, bean1, "bean1") * 第一步调用findAutowiringMetadata获取bean的Autowire注解的元数据 * 我们用反射直接调用私有方法findAutowiringMetadata */ 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);
结果:
-
依赖注入是如何按类型查找值的? 模拟按类型查找值
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); //new Bean2() 它认为这是成品对象 所以会跳过创建过程(依赖注入,初始化) beanFactory.registerSingleton("bean2", new Bean2()); beanFactory.registerSingleton("bean3", new Bean3()); beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); // @Value beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders); // ${} 的解析器 // 1. 查找哪些属性、方法加了 @Autowired, 这称之为 InjectionMetadata AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor(); processor.setBeanFactory(beanFactory); //模拟按类型查找值 /** * 3、如何按类型查找值 */ Field bean3 = Bean1.class.getDeclaredField("bean3"); //spring会把属性封装成DependencyDescriptor对象(依赖描述对象) DependencyDescriptor dd1 = new DependencyDescriptor(bean3,false); Object o = beanFactory.doResolveDependency(dd1, null, null,null); System.out.println(o); Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class); DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), true); Object o1 = beanFactory.doResolveDependency(dd2, null, null, null); System.out.println(o1); Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class); DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true); Object o2 = beanFactory.doResolveDependency(dd3, null, null, null); System.out.println(o2);
结果:
⑴、总结
AutowiredAnnotationBeanPostProcessor
.findAutowiringMetadata
用来获取某个 bean 上加了 @Value @Autowired 的成员变量,方法参数的信息,表示为 InjectionMetadataInjectionMetadata
可以完成依赖注入InjectionMetadata
内部根据成员变量,方法参数封装为DependencyDescriptor
类型- 有了
DependencyDescriptor
,就可以利用beanFactory.doResolveDependency
方法进行基于类型的查找
a05
、BeanFactory
后处理器
①、演示1 - BeanFactory 后处理器的作用
⑴、演示ConfigurationClassPostProcessor
-
准备工作:代码结构如下,compent包下有三个bean,compent父级目录含有一个bean1、配置类,以及测试类A05
-
代码如下:
-
Bean2:
@Slf4j @Component public class Bean2 { public Bean2() { log.info("我被spring管理啦"); } }
-
Bean3
@Slf4j @Controller public class Bean3 { public Bean3() { log.info("我被 Spring 管理啦"); } }
-
Bean4
@Slf4j public class Bean4 { public Bean4() { log.info("我被 Spring 管理啦"); } }
-
Bean1
@Slf4j public class Bean1 { public Bean1() { log.info("我被spring管理啦"); } }
-
Config:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class Config { @Bean public Bean1 bean1() { return new Bean1(); } }
-
-
准备工作完毕,我们开始编写测试类,准备一个干净的容器。并将配置类【Config类】注册到容器中,并进行容器的初始化。
package com.colin.a05; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.context.support.GenericApplicationContext; @Slf4j public class A05 { public static void main(String[] args) { // ⬇️GenericApplicationContext 是一个【干净】的容器 GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); // ⬇️初始化容器 context.refresh(); // ⬇️销毁容器 context.close(); } }
运行结果:
-
结果显示只用config本身被spring管理,在容器中创建了单例。而在Config类中用
@Bean
标注的方法生成bean1
没有生效,这是因为容器本身不具备解析
@Bean
的能力,需要借助BeanFactory
后处理器才能完成这项工作。 -
改造代码: 为容器注册
BeanFactory
后处理器:ConfigurationClassPostProcessor
@Slf4j public class A05 { public static void main(String[] args) { // ⬇️GenericApplicationContext 是一个【干净】的容器 GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); /** * 添加BeanFactory 后处理器 * 可以解析的注解: @ComponentScan @Bean @Import @ImportResource */ context.registerBean(ConfigurationClassPostProcessor.class); // ⬇️初始化容器 context.refresh(); // ⬇️销毁容器 context.close(); } }
再次运行:我们可以看到bean1被创建了。
-
ConfigurationClassPostProcessor
不仅可以识别解析@Bean
,它还可以解析@ComponentScan
、@Import
、@ImportResource
等注解。改造Config类加入@ComponentScan
注解。@Configuration @ComponentScan("com.colin.a05.component") public class Config { @Bean public Bean1 bean1() { return new Bean1(); } }
再次运行A05结果:
- 除了bean1,被
@Component
修饰的bean2,以及被@Controller
修饰的bean3都加入了容器,而bean4没有任何注解修饰,就不被解析了。
- 除了bean1,被
⑵、演示MapperScannerConfigurer
-
准备工作 代码结构如下,mapper包下有三个bean,mapper父级目录含有一个配置类,以及测试类A05
-
Mapper1
public interface Mapper1 { }
-
Mapper2
public interface Mapper2 { }
-
Mapper3
public class Mapper3 { }
-
Config类新增数据库与mybatis的相关配置bean
import com.alibaba.druid.pool.DruidDataSource; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class Config { /** * 数据源 * * @param dataSource * @return */ @Bean(initMethod = "init") public DruidDataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } /** * SqlSession 工厂 * * @param dataSource * @return */ @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } }
-
-
准备工作完毕,我们开始编写测试类,准备一个干净的容器。并将配置类【Config类】注册到容器中,并进行容器的初始化。
@Slf4j public class A05 { public static void main(String[] args) { // ⬇️GenericApplicationContext 是一个【干净】的容器 GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); // ⬇️初始化容器 context.refresh(); // ⬇️销毁容器 context.close(); } }
-
运行:结果,只用数据源和
SqlSession
工厂在容器中,我们定义的Mapper
并不在容器
-
-
首先我们要明确我们写的
Mapper
是一个接口,接口是不能被创建的,Mybatis
会为每个Mapper
接口创建响应的代理类,代理类实现了我们定义的接口,所以真正工作的是那个代理类对象。MapperFactoryBean
是Mybatis
把Mapper
生成具体对象的工厂,我们可以通过往容器中放入工厂从而得到Mapper
代理对象。改造
Config
类手动为Mapper1
以及Mapper2
创建工厂对象。package com.colin.a05; import com.alibaba.druid.pool.DruidDataSource; import com.colin.a05.mapper.Mapper1; import com.colin.a05.mapper.Mapper2; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class Config { @Bean(initMethod = "init") public DruidDataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } /** * SqlSession 工厂 * * @param dataSource * @return */ @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } /* * 能把Mapper生成具体对象的工厂 * @param sqlSessionFactory * @return */ @Bean public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) { MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class); factory.setSqlSessionFactory(sqlSessionFactory); return factory; } /* * 能把Mapper生成具体对象的工厂 * @param sqlSessionFactory * @return */ @Bean public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) { MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class); factory.setSqlSessionFactory(sqlSessionFactory); return factory; } }
运行结果:Mapper1 与 Mapper2被spring管理啦
输出Mapper1 与 Mapper2 可以看到它们是代理对象。
package com.colin.a05; import com.colin.a05.mapper.Mapper1; import com.colin.a05.mapper.Mapper2; import lombok.extern.slf4j.Slf4j; import org.springframework.context.support.GenericApplicationContext; @Slf4j public class A05 { public static void main(String[] args) { // ⬇️GenericApplicationContext 是一个【干净】的容器 GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); // ⬇️初始化容器 context.refresh(); //获取 Mapper1 mapper1 = context.getBean(Mapper1.class); Mapper2 mapper2 = context.getBean(Mapper2.class); System.out.println(mapper1); System.out.println(mapper2); // ⬇️销毁容器 context.close(); } }
-
但是真实开发中,我们不可能每个Mapper都手动创建它的工厂,我们会通过注解的方式,然后通过
BeanFactory
后处理器解析注解生成对应的代理类对象为我们使用。就是把活交给BeanFactory
后处理器。改造代码,添加
BeanFactory
后处理器MapperScannerConfigurer
,添加时需要指明基础包路径。将Config类的MapperFactoryBean
注释- Config
package com.colin.a05; import com.alibaba.druid.pool.DruidDataSource; import com.colin.a05.mapper.Mapper1; import com.colin.a05.mapper.Mapper2; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class Config { ..... /* * 能把Mapper生成具体对象的工厂 * @param sqlSessionFactory * @return */ /**@Bean public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) { MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class); factory.setSqlSessionFactory(sqlSessionFactory); return factory; }**/ /* * 能把Mapper生成具体对象的工厂 * @param sqlSessionFactory * @return */ /**@Bean public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) { MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class); factory.setSqlSessionFactory(sqlSessionFactory); return factory; }**/ }
- A05
package com.colin.a05; import com.colin.a05.mapper.Mapper1; import com.colin.a05.mapper.Mapper2; import lombok.extern.slf4j.Slf4j; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.support.GenericApplicationContext; @Slf4j public class A05 { public static void main(String[] args) { // ⬇️GenericApplicationContext 是一个【干净】的容器 GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); /** * 添加BeanFactory 后处理器 * 可以解析的注解: @MapperScanner */ context.registerBean(MapperScannerConfigurer.class, bd -> { bd.getPropertyValues().add("basePackage","com.colin.a05.mapper"); }); // ⬇️初始化容器 context.refresh(); //获取 Mapper1 mapper1 = context.getBean(Mapper1.class); Mapper2 mapper2 = context.getBean(Mapper2.class); System.out.println(mapper1); System.out.println(mapper2); // ⬇️销毁容器 context.close(); } }
再次运行:结果是一样的
⑶、总结
- ConfigurationClassPostProcessor 可以解析
- @ComponentScan
- @Bean
- @Import
- @ImportResource
- MapperScannerConfigurer 可以解析
- Mapper 接口
收获💡
- @ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
- 这些扩展功能由不同的 BeanFactory 后处理器来完成,其实主要就是补充了一些 bean 定义
②、演示2 - 模拟解析 @ComponentScan
-
我们手动模拟一下解析
@ComponentScan
新建ResolveComponentScan
类,代码步骤如下-
1、获取Config类的@ComponentScan注解,使用Spring提供的注解工具类AnnotationUtils
//1、取得@ComponentScan注解 /** * 使用Spring提供的注解工具类 */ ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
-
2、获取basePackages属性,并遍历
//2、遍历basePackages属性 for (String basePackage : componentScan.basePackages()) { }
-
3、获取包指向的类路径并将包路径转换为类路径。(循环体内)
//3、获取包指向的类路径---》把包路径转换为类路径 //转换路径 com.colin.a05.component -> classpath*:com/colin/a05/component/**/*.class String path = "classpath*:" + basePackage.replace(".", "/") + "/**/*.class"; log.info("包的类路径:{}", path);
-
4、根据类路径获取资源,其实就是获取这些类,并遍历资源(循环体内)
Resource[] resources = context.getResources(path); for (Resource resource : resources) { }
-
5、获取资源类的元信息 ,通过spring提供的一个工厂类 CachingMetadataReaderFactory 可以获取类的元信息。(遍历资源循环体内)
log.info("类资源:{}", resource); //5、获取类的元信息 /** * 通过spring提供的一个工厂类 * CachingMetadataReaderFactory 可以获取类的元信息 */ MetadataReader metadataReader = factory.getMetadataReader(resource); log.info("类名:{}", metadataReader.getClassMetadata().getClassName()); log.info("注解信息(是否加了Component):{}", metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName())); log.info("注解信息(是否加了Component或者Component派生注解):{}", metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName()));
-
6、根据类的元信息,筛选出我们感兴趣的类(加了Component注解,或者Component注解的派生注解)(遍历资源循环体内)
if (metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName()) || metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName())) { }
-
7、将这些类加入加入bean工厂的bean定义(遍历资源循环体内)
//如果资源类加了Component注解,或者Component注解的派生注解 进行处理 //7、将这些类加入加入bean工厂的bean定义 AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder .genericBeanDefinition(metadataReader.getClassMetadata().getClassName()) .getBeanDefinition();
-
8、生成bean 的名字 使用spring提供的注解bean名字生成器
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator(); String beanName = generator.generateBeanName(beanDefinition, context.getDefaultListableBeanFactory());
-
9、放入bean工厂。 将bean注册到bean工厂
context.getDefaultListableBeanFactory() .registerBeanDefinition(beanName, beanDefinition);
-
-
完整代码:
package com.colin.a05; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.io.Resource; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.stereotype.Component; import java.io.IOException; /* * @Author Colin * @Description //TODO 模拟@ComponentScan解析 * @Date 2022/6/5 17:01 **/ @Slf4j public class ResolveComponentScan { public static void main(String[] args) throws IOException { GenericApplicationContext context = new GenericApplicationContext(); //1、取得@ComponentScan注解 /** * 使用Spring提供的注解工具类 */ ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class); if (componentScan != null) { //标注了ComponentScan注解 //2、遍历basePackages属性 for (String basePackage : componentScan.basePackages()) { //3、获取包指向的类路径---》把包路径转换为类路径 //转换路径 com.colin.a05.component -> classpath*:com/colin/a05/component/**/*.class String path = "classpath*:" + basePackage.replace(".", "/") + "/**/*.class"; log.info("包的类路径:{}", path); //4、根据类路径获取资源,其实就是获取这些类 CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); Resource[] resources = context.getResources(path); for (Resource resource : resources) { log.info("类资源:{}", resource); //5、获取类的元信息 /** * 通过spring提供的一个工厂类 * CachingMetadataReaderFactory 可以获取类的元信息 */ MetadataReader metadataReader = factory.getMetadataReader(resource); log.info("类名:{}", metadataReader.getClassMetadata().getClassName()); log.info("注解信息(是否加了Component):{}", metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName())); log.info("注解信息(是否加了Component或者Component派生注解):{}", metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName())); /** * 6、根据类的元信息,筛选出我们感兴趣的类(加了Component注解,或者Component注解的派生注解) */ if (metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName()) || metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName())) { //如果资源类加了Component注解,或者Component注解的派生注解 进行处理 //7、将这些类加入加入bean工厂的bean定义 AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder .genericBeanDefinition(metadataReader.getClassMetadata().getClassName()) .getBeanDefinition(); //8、生成bean 的名字 使用spring提供的注解bean名字生成器 AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator(); String beanName = generator.generateBeanName(beanDefinition, context.getDefaultListableBeanFactory()); //9、放入bean工厂。 将bean注册到bean工厂 context.getDefaultListableBeanFactory() .registerBeanDefinition(beanName, beanDefinition); log.info("==============放入beanfactory===================>>"); } log.info("=============================================>>"); } // ⬇️初始化容器 context.refresh(); // ⬇️销毁容器 context.close(); } } } }
运行结果:
⑴、总结
- Spring 操作元数据的工具类 CachingMetadataReaderFactory
- 通过注解元数据(AnnotationMetadata)获取直接或间接标注的注解信息
- 通过类元数据(ClassMetadata)获取类名,AnnotationBeanNameGenerator 生成 bean 名
- 解析元数据是基于 ASM 技术
③、演示3 - 模拟解析 @Bean
-
我们手动模拟一下解析
@Bean
新建ResolveBean
类,代码步骤如下-
1、将配置类注册到bean工厂(后续会用到)
/** * 1、将配置类注册到bean工厂 */ GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config",Config.class);
-
2、读取配置类的元信息,使用spring提供的一个工厂类CachingMetadataReaderFactory
/** * 2、读取配置类的元信息, * 使用spring提供的一个工厂类CachingMetadataReaderFactory */ CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); MetadataReader metadataReader = factory.getMetadataReader(Config.class.getName());
-
3、获取所有被
@Bean
注解标志的方法Set<MethodMetadata> annotatedMethods = metadataReader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
-
4、遍历被@bean 注解标志的方法,将这些方法转化为bean的定义放入bean工厂
for (MethodMetadata annotatedMethod : annotatedMethods) { log.info("annotatedMethod:{}", annotatedMethod); 。。。。。 }
-
4.1 解析注解的属性, 以initMethod属性为例
//将@bean 注解标志的方法(spring会将此方法作为工厂方法) BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(); //4.1 解析注解的属性, 以initMethod属性为例 String initMethod = annotatedMethod.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString(); if (initMethod.length() > 0) { beanDefinitionBuilder.setInitMethodName(initMethod); log.info("initMethod:{}", initMethod); }
-
4.2 完善BeanDefinition
//4.2 完善BeanDefinition /** * 因为@bean 注解标志的方法(spring会将此方法作为工厂方法), * 所以我们要设置工厂方法相关的bean定义 * * annotatedMethod.getMethodName(): 工厂方法的名字 * config: 工厂类的bean名字 -- 工厂类必须已经存在 所以要先把Config注册成bean */ beanDefinitionBuilder.setFactoryMethodOnBean(annotatedMethod.getMethodName(), "config");
-
4.4 注册到BeanFactory
context.getDefaultListableBeanFactory() .registerBeanDefinition(annotatedMethod.getMethodName(), beanDefinitionBuilder.getBeanDefinition());
运行代码:发现出错,SqlSessionFactoryBean无法创建。
分析问题:我们查看SqlSessionFactoryBean的方法定义发现这个方法是有参数的。而我们定义BeanDefinition对有参数的方法并没有处理。/** * SqlSession 工厂 * * @param dataSource * @return */ @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; }
解决方法,在4.2与4.4步之间加上4.3步,
-
4.3加入自动装配
beanDefinitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
再次运行:错误消失了。
-
-
-
完整代码:
package com.colin.a05; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import java.io.IOException; import java.util.Set; /* * @Author Colin * @Description //TODO 模拟@Bean解析 * @Date 2022/6/5 17:01 **/ @Slf4j public class ResolveBean { public static void main(String[] args) throws IOException { /** * 1、将配置类注册到bean工厂 */ GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); /** * 2、读取配置类的元信息, * 使用spring提供的一个工厂类CachingMetadataReaderFactory */ CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); MetadataReader metadataReader = factory.getMetadataReader(Config.class.getName()); /** * 3、获取所有被@bean 注解标志的方法 */ Set<MethodMetadata> annotatedMethods = metadataReader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName()); /** * 4、遍历被@bean 注解标志的方法,将这些方法 * 转化为bean的定义放入bean工厂 */ for (MethodMetadata annotatedMethod : annotatedMethods) { log.info("annotatedMethod:{}", annotatedMethod); //将@bean 注解标志的方法(spring会将此方法作为工厂方法) BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(); //4.1 解析注解的属性, 以initMethod属性为例 String initMethod = annotatedMethod.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString(); if (initMethod.length() > 0) { beanDefinitionBuilder.setInitMethodName(initMethod); log.info("initMethod:{}", initMethod); } //4.2 完善BeanDefinition /** * 因为@bean 注解标志的方法(spring会将此方法作为工厂方法), * 所以我们要设置工厂方法相关的bean定义 * * annotatedMethod.getMethodName(): 工厂方法的名字 * config: 工厂类的bean名字 */ beanDefinitionBuilder.setFactoryMethodOnBean(annotatedMethod.getMethodName(), "config"); //4.3加入自动装配 /** * 用来解决那些有参数的工厂方法,让其参数可以自动注入 * 比如:sqlSessionFactoryBean这个方法 * @Bean * public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { * SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); * sqlSessionFactoryBean.setDataSource(dataSource); * return sqlSessionFactoryBean; * } */ beanDefinitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); //4.4 注册到BeanFactory context.getDefaultListableBeanFactory() .registerBeanDefinition(annotatedMethod.getMethodName(), beanDefinitionBuilder.getBeanDefinition()); } // ⬇️初始化容器 context.refresh(); // ⬇️销毁容器 context.close(); } }
Config:
package com.colin.a05; import com.alibaba.druid.pool.DruidDataSource; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration @ComponentScan("com.colin.a05.component") public class Config { @Bean public Bean1 bean1() { return new Bean1(); } @Bean(initMethod = "init") public DruidDataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } /** * SqlSession 工厂 * * @param dataSource * @return */ @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } /* * 能把Mapper生成具体对象的工厂 * @param sqlSessionFactory * @return */ /*@Bean public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) { MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class); factory.setSqlSessionFactory(sqlSessionFactory); return factory; }*/ /* * 能把Mapper生成具体对象的工厂 * @param sqlSessionFactory * @return */ /* @Bean public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) { MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class); factory.setSqlSessionFactory(sqlSessionFactory); return factory; }*/ }
④、演示4 - 模拟解析 Mapper 接口
-
我们手动模拟一下解析
Mapper
接口 新建ResolveMapper
类,代码步骤如下-
1、获取指定路径所有类资源
//1、获取指定路径所有类资源 Resource[] resources = context.getResources("classpath:com/colin/a05/mapper/**/*.class");
-
2、遍历所有类资源
//2、遍历所有类资源 for (Resource resource : resources) { log.info("类资源:{}", resource); ...... }
-
3、获取类资源的元信息 使用spring工厂类CachingMetadataReaderFactory 可以获取类的元信息(循环体内)
//3、获取类资源的元信息 使用spring工厂类CachingMetadataReaderFactory 可以获取类的元信息 CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); MetadataReader metadataReader = factory.getMetadataReader(resource);
-
4、判断类是不是接口(循环体内)
if (metadataReader.getClassMetadata().isInterface()) { }
-
5、是接口的话,定义BeanDefinition 根据接口定义出 MapperFactoryBean(把Mapper生成对象的工厂bean)
//是接口 //5、定义BeanDefinition 根据接口定义出 MapperFactoryBean(把Mapper生成对象的工厂bean) AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder .genericBeanDefinition(MapperFactoryBean.class) //设置构造 .addConstructorArgValue(metadataReader.getClassMetadata().getClassName()) .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE) .getBeanDefinition();
-
6、生成bean的名字 使用spring提供的注解bean名字生成器
注意事项
- 注意这里要根据mapper接口生成bean的名字
- 如果根据MapperFactoryBean工厂类生成名字,多个mapper接口都会是MapperFactoryBean类型 名字都是mapperFactoryBean
- 相同名字的bean会被覆盖 所以需要用mapper接口类型生成bean的名字
//6、生成bean的名字 使用spring提供的注解bean名字生成器 /** * 注意这里要根据mapper接口生成bean的名字 * 如果根据MapperFactoryBean工厂类生成名字, * 多个mapper接口都会是MapperFactoryBean类型 名字都是mapperFactoryBean * 相同名字的bean会被覆盖 所以需要用mapper接口类型生成bean的名字 */ AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator(); AbstractBeanDefinition bd2 = BeanDefinitionBuilder .genericBeanDefinition(metadataReader.getClassMetadata().getClassName()) .getBeanDefinition(); String beanName = generator.generateBeanName(bd2, context.getDefaultListableBeanFactory());
-
7、 将bean定义注册到BeanFactory
//7. 将bean定义注册到BeanFactory context.registerBeanDefinition(beanName, beanDefinition);
-
-
完整代码:
package com.colin.a05; import lombok.extern.slf4j.Slf4j; import org.mybatis.spring.mapper.MapperFactoryBean; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.io.Resource; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import java.io.IOException; /* * @Author Colin * @Description //TODO 模拟@Mapper解析 * @Date 2022/6/5 17:01 **/ @Slf4j public class ResolveMapper { public static void main(String[] args) throws IOException { GenericApplicationContext context = new GenericApplicationContext(); /** * 准备工作, mapper依赖于数据库与mybatis所有先将相关需要的bean注册到beanfactory */ context.registerBean("config", Config.class); /** * 添加BeanFactory 后处理器 * 可以解析的注解: @ComponentScan @Bean @Import @ImportResource */ context.registerBean(ConfigurationClassPostProcessor.class); //准备工作完毕================================== //1、获取指定路径所有类资源 Resource[] resources = context.getResources("classpath:com/colin/a05/mapper/**/*.class"); //2、遍历所有类资源 for (Resource resource : resources) { log.info("类资源:{}", resource); //3、获取类资源的元信息 使用spring工厂类CachingMetadataReaderFactory 可以获取类的元信息 CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); MetadataReader metadataReader = factory.getMetadataReader(resource); //4、判断类是不是接口 if (metadataReader.getClassMetadata().isInterface()) { //是接口 //5、定义BeanDefinition 根据接口定义出 MapperFactoryBean(把Mapper生成对象的工厂bean) AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder .genericBeanDefinition(MapperFactoryBean.class) //设置构造 .addConstructorArgValue(metadataReader.getClassMetadata().getClassName()) .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE) .getBeanDefinition(); //6、生成bean的名字 使用spring提供的注解bean名字生成器 /** * 注意这里要根据mapper接口生成bean的名字 * 如果根据MapperFactoryBean工厂类生成名字, * 多个mapper接口都会是MapperFactoryBean类型 名字都是mapperFactoryBean * 相同名字的bean会被覆盖 所以需要用mapper接口类型生成bean的名字 */ AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator(); AbstractBeanDefinition bd2 = BeanDefinitionBuilder .genericBeanDefinition(metadataReader.getClassMetadata().getClassName()) .getBeanDefinition(); String beanName = generator.generateBeanName(bd2, context.getDefaultListableBeanFactory()); //7. 将bean定义注册到BeanFactory context.registerBeanDefinition(beanName, beanDefinition); } } // ⬇️初始化容器 context.refresh(); // ⬇️销毁容器 context.close(); } }
运行结果:
⑴、总结
- Mapper 接口被 Spring 管理的本质:实际根据Mapper接口定义 MapperFactoryBean工厂,并将工厂 注册到容器中
- Spring 的诡异做法,根据接口生成的 BeanDefinition 仅为根据接口名生成 bean 名
⑤、根据演示自定义BeanFactory后处理器
-
自定义的处理ComponentScan的BeanFactory 后处理器
-
定义
ComponentScanPostProcessor
类实现BeanDefinitionRegistryPostProcessor
接口根据演示2 实现相关逻辑package com.colin.a05; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.context.annotation.ComponentScan; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.stereotype.Component; import java.io.IOException; /** * 自定义的处理ComponentScan的BeanFactory 后处理器 */ public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { // context.refresh } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException { try { ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class); if (componentScan != null) { for (String p : componentScan.basePackages()) { System.out.println(p); // com.colin.a05.component -> classpath*:com/colin/a05/component/**/*.class String path = "classpath*:" + p.replace(".", "/") + "/**/*.class"; System.out.println(path); CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path); AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator(); for (Resource resource : resources) { // System.out.println(resource); MetadataReader reader = factory.getMetadataReader(resource); // System.out.println("类名:" + reader.getClassMetadata().getClassName()); AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata(); // System.out.println("是否加了 @Component:" + annotationMetadata.hasAnnotation(Component.class.getName())); // System.out.println("是否加了 @Component 派生:" + annotationMetadata.hasMetaAnnotation(Component.class.getName())); if (annotationMetadata.hasAnnotation(Component.class.getName()) || annotationMetadata.hasMetaAnnotation(Component.class.getName())) { AbstractBeanDefinition bd = BeanDefinitionBuilder .genericBeanDefinition(reader.getClassMetadata().getClassName()) .getBeanDefinition(); String name = generator.generateBeanName(bd, beanFactory); beanFactory.registerBeanDefinition(name, bd); } } } } } catch (IOException e) { e.printStackTrace(); } } }
-
-
自定义的处理@Bean的BeanFactory 后处理器
-
定义
BeanPostProcessor
类实现BeanDefinitionRegistryPostProcessor
接口根据演示3 实现相关逻辑package com.colin.a05; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.core.io.ClassPathResource; import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import java.io.IOException; import java.util.Set; /* * @Author Colin * @Description //TODO 自定义的处理@Bean的BeanFactory 后处理器 * @Date 2022/6/5 18:48 **/ public class BeanPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException { try { CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/colin/a05/Config.class")); Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName()); for (MethodMetadata method : methods) { System.out.println(method); String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString(); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); builder.setFactoryMethodOnBean(method.getMethodName(), "config"); builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); if (initMethod.length() > 0) { builder.setInitMethodName(initMethod); } AbstractBeanDefinition bd = builder.getBeanDefinition(); beanFactory.registerBeanDefinition(method.getMethodName(), bd); } } catch (IOException e) { e.printStackTrace(); } } }
-
-
自定义的处理Mapper接口的BeanFactory 后处理器
-
定义
MapperPostProcessor
类实现BeanDefinitionRegistryPostProcessor
接口根据演示4 实现相关逻辑package com.colin.a05; import org.mybatis.spring.mapper.MapperFactoryBean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import java.io.IOException; /* * @Author Colin * @Description //TODO 自定义的处理 Mapper接口的BeanFactory 后处理器 * @Date 2022/6/5 18:50 **/ public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException { try { PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources("classpath:com/colin/a05/mapper/**/*.class"); 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 bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class) .addConstructorArgValue(classMetadata.getClassName()) .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE) .getBeanDefinition(); AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition(); String name = generator.generateBeanName(bd2, beanFactory); beanFactory.registerBeanDefinition(name, bd); } } } catch (IOException e) { e.printStackTrace(); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } }
-
⑴、测试
-
创建
A05_1
测试类如下:package com.colin.a05; import com.colin.a05.mapper.Mapper1; import com.colin.a05.mapper.Mapper2; import lombok.extern.slf4j.Slf4j; import org.springframework.context.support.GenericApplicationContext; @Slf4j public class A05_1 { public static void main(String[] args) { // ⬇️GenericApplicationContext 是一个【干净】的容器 GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); //⬇️添加我们自己的BeanFactory后处理器 context.registerBean(ComponentScanPostProcessor.class); // 解析 @ComponentScan context.registerBean(BeanPostProcessor.class); // 解析 @Bean context.registerBean(MapperPostProcessor.class); // 解析 Mapper 接口 // ⬇️初始化容器 context.refresh(); //获取 Mapper1 mapper1 = context.getBean(Mapper1.class); Mapper2 mapper2 = context.getBean(Mapper2.class); log.info(mapper1.toString()); log.info(mapper2.toString()); // ⬇️销毁容器 context.close(); } }
-
运行结果
a06
、Aware
接口及 InitializingBean
接口
①、演示1 - Aware 接口及 InitializingBean 接口
-
准备工作: 创建一个MyBean类,实现常用的Aware 接口以及 InitializingBean 接口,并在响应的方法中执行打印。
package com.colin.a06; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.util.StringValueResolver; /** * @Author Colin * @Description 测试Aware接口及 InitializingBean 接口 * @Date 2022/6/6 10:15 */ @Slf4j public class MyBean implements BeanNameAware, InitializingBean, BeanFactoryAware, ApplicationContextAware, EmbeddedValueResolverAware { /** * BeanNameAware 接口抽象方法 * 会将bean的名字注入 * * @param name */ @Override public void setBeanName(String name) { log.info("MyBean在BeanFactory的名字为:{}", name); } /** * InitializingBean接口 提供了一种【内置】的初始化手段 * 会在扩展功能后被调用 (如依赖注入扩展、初始化方法 @PostConstruct扩展) * * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { log.info("InitializingBean接口 【内置】的初始化手段"); } /** * BeanFactoryAware 接口 * 会为bean注入BeanFactory 容器 * * @param beanFactory * @throws BeansException */ @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { log.info("我是BeanFactoryAware 接口 为bean注入BeanFactory :{}", beanFactory); } /** * ApplicationContextAware 接口 * 会为bean注入ApplicationContext 容器 * * @param applicationContext * @throws BeansException */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { log.info("我是ApplicationContextAware 接口 为bean注入applicationContext :{}", applicationContext); } /** * EmbeddedValueResolverAware接口 * 会为bean 注入 ${} 解析器 * * @param resolver */ @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { log.info("我是EmbeddedValueResolverAware接口 为bean注入 ${} 解析器 解析器:{}", resolver); String value = resolver.resolveStringValue("${JAVA_HOME}"); log.info("解析 ${JAVA_HOME}的值为:{}", value); } }
-
编写测试类 A06,准备一个干净的spring容器,并将MyBean注册到容器中,执行容器的初始化方法。
package com.colin.a06; import org.springframework.context.support.GenericApplicationContext; public class A06 { public static void main(String[] args) { //1、准备一个干净的容器 GenericApplicationContext context = new GenericApplicationContext(); //2、将MyBean注册到容器中 context.registerBean(MyBean.class); //3、初始化容器 context.refresh(); //4、关闭容器 context.close(); } }
-
运行结果:
⑴、结果分析
Aware 接口提供了一种【内置】 的注入手段,例如
BeanNameAware 注入 bean 的名字
BeanFactoryAware 注入 BeanFactory 容器
ApplicationContextAware 注入 ApplicationContext 容器
EmbeddedValueResolverAware 注入 ${} 解析器
InitializingBean 接口提供了一种【内置】的初始化手段
②、演示2 - 扩展功能失效问题
-
演示1中我们可以看到
Aware
接口可以为bean注入各种各样其他的bean,InitializingBean
接口可以提供初始化功能。但这些提供的功能都可以被扩展代替,比如
Aware
接口的功能可以被@Autowired
代替,InitializingBean
接口可以被@PostConstruct
代替。那他们存在的意义在哪呢? 我们来探究一下。
⑴、功能替代以及扩展失效演示
-
演示代替功能:创建一个MyBean2类,实现
@Autowired
以及@PostConstruct
代替常用的Aware
接口以及InitializingBean
接口。package com.colin.a06; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import javax.annotation.PostConstruct; /** * @Author Colin * @Description 测试Aware接口及 InitializingBean 接口 * @Date 2022/6/6 10:15 */ @Slf4j public class MyBean2 { /** * @throws Exception * @PostConstruct初始化调用 */ @PostConstruct public void afterPropertiesSet() throws Exception { log.info(" @PostConstruct 初始化手段"); } /** * Autowired注入BeanFactory 容器 * * @param beanFactory * @throws BeansException */ @Autowired public void setBeanFactory(BeanFactory beanFactory) throws BeansException { log.info("Autowired注入BeanFactory 容器 :{}", beanFactory); } /** * Autowired注入ApplicationContext 容器 * * @param applicationContext * @throws BeansException */ @Autowired public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { log.info("Autowired注入ApplicationContext 容器 :{}", applicationContext); } }
- 编写测试类A06_1 ,准备一个干净的spring容器,注册解析相应注解的bean后处理器,并将MyBean2注册到容器中,执行容器的初始化方法。
package com.colin.a06; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor; import org.springframework.context.support.GenericApplicationContext; public class A06_1 { public static void main(String[] args) { //1、准备一个干净的容器 GenericApplicationContext context = new GenericApplicationContext(); //2、将MyBean注册到容器中 context.registerBean(MyBean2.class); //3、加入一些bean的后处理器 /** * 为context提供处理@Autowired注解的能力 */ context.registerBean(AutowiredAnnotationBeanPostProcessor.class); /** * 为context提供处理@PostConstruct注解的能力 */ context.registerBean(CommonAnnotationBeanPostProcessor.class); //4、初始化容器 context.refresh(); //5、关闭容器 context.close(); } }
- 运行结果:
-
@Autowired
的解析需要用到 bean 后处理器, 属于扩展功能,而Aware
接口属于内置功能, 不加任何扩展, Spring 就能识别。某些情况下, 扩展功能会失效, 而内置功能不会失效。 下面演示@Autowired
失效的情况。-
编写一个配置类MyConfig,里面使用
@Autowired
以及@PostConstruct
,并且使用@Bean
为容器添加一个BeanFactory
后处理器,代码如下:package com.colin.a06; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; @Slf4j @Configuration public class MyConfig { @Autowired public void setApplicationContext(ApplicationContext applicationContext) { log.info("注入 ApplicationContext"); } @PostConstruct public void init() { log.info("初始化"); } /** * beanFactory 后处理器 这里会导致@Autowired,@value...等扩展会失效 * * @return */ @Bean public BeanFactoryPostProcessor processor() { return beanFactory -> log.info("执行processor,补充bean的定义"); } }
-
编写测试类A06_2,准备一个干净的spring容器,注册解析相应注解的后处理器,并将MyConfig注册到容器中,执行容器的初始化方法。
package com.colin.a06; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor; import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.context.support.GenericApplicationContext; public class A06_2 { public static void main(String[] args) { //1、准备一个干净的容器 GenericApplicationContext context = new GenericApplicationContext(); //2、将MyConfig注册到容器中 context.registerBean(MyConfig.class); //3、加入一些bean工厂的后处理器 /** *可以解析的注解: @ComponentScan @Bean @Import @ImportResource */ context.registerBean(ConfigurationClassPostProcessor.class); //4、加入一些bean的后处理器 /** * 为context提供处理@Autowired注解的能力 */ context.registerBean(AutowiredAnnotationBeanPostProcessor.class); /** * 为context提供处理@PostConstruct注解的能力 */ context.registerBean(CommonAnnotationBeanPostProcessor.class); //5、初始化容器 context.refresh(); //6、关闭容器 context.close(); } }
-
运行结果:只执行了
@Bean
修饰的方法,@Autowired
以及@PostConstruct
都没有打印,功能失效了。
-
⑵、扩展失效的原因分析
-
@Autowired
失效分析-
Java 配置类不包含 BeanFactoryPostProcessor 的情况.spring初始化容器时步骤大致如下。
- 1、首先执行bean工厂的后处理器,完善一些bean的定义。
- 2、准备好相关的bean后处理器,因为创建bean的时候,可能要对bean做一些扩展,从而完善bean的功能。
- 3、开始根据bean的定义创建bean(反射调用构造)
- 4、执行扩展,根据bean后处理器器排序规则解析bean相关注解对bean做一些扩展。(先进行依赖注入,在进行初始化扩展)
- 5、执行内置的Aware接口以及 InitializingBean对bean做一些扩展
- 6、创建成功
-
Java 配置类包含
BeanFactoryPostProcessor
的情况,因此要创建其中的
BeanFactoryPostProcessor
必须提前创建 Java 配置类,而此时的
BeanPostProcessor
还未准备好,导致@Autowired
等注解失效- 1、因为java类中有bean工厂的后处理器,而bean工厂的后处理一开始就需要,因为要把bean的定义先准备好,所以为了获得完整的bean工厂的后处理器,必须先创建java类(因为
@Bean
注解修饰的方法会变成工厂方法,java类是工厂bean),执行java类的创建 - 2、执行内置的Aware接口以及 InitializingBean对bean做一些扩展
- 3、创建成功
- 4、执行bean工厂的后处理器,完善一些bean的定义
- 5、准备好相关的bean后处理器
- 1、因为java类中有bean工厂的后处理器,而bean工厂的后处理一开始就需要,因为要把bean的定义先准备好,所以为了获得完整的bean工厂的后处理器,必须先创建java类(因为
-
⑶、扩展失效的解决方法
-
用内置依赖注入和初始化取代扩展依赖注入和初始化 ,效果代码请看演示1
-
用静态工厂方法代替实例工厂方法,避免工厂对象提前被创建,比如改造演示2中MyConfig的代码变成MyConfig1如下所示,将
processor()
方法变成静态方法。package com.colin.a06; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import javax.annotation.PostConstruct; @Slf4j public class MyConfig1 { @Autowired public void setApplicationContext(ApplicationContext applicationContext) { log.info("注入 ApplicationContext"); } @PostConstruct public void init() { log.info("初始化"); } /** * beanFactory 后处理器 这里会导致@Autowired,@value...等扩展会失效 * * @return */ @Bean public static BeanFactoryPostProcessor processor() { return beanFactory -> log.info("执行processor,补充bean的定义"); } }
测试:结果就正常了。
⑷、总结
Aware 接口提供了一种【内置】 的注入手段,例如
BeanNameAware 注入 bean 的名字
BeanFactoryAware 注入 BeanFactory 容器
ApplicationContextAware 注入 ApplicationContext 容器
EmbeddedValueResolverAware 注入 ${} 解析器
InitializingBean 接口提供了一种【内置】的初始化手段
对比
内置的注入和初始化不受扩展功能的影响,总会被执行
而扩展功能受某些情况影响可能会失效
因此 Spring 框架内部的类常用内置注入和初始化
解决扩展功能失效
用内置依赖注入和初始化取代扩展依赖注入和初始化
用静态工厂方法代替实例工厂方法,避免工厂对象提前被创建
a07
、初始化与销毁
①、演示1 - 初始化顺序
-
Spring 提供了多种初始化手段,除
@PostConstruct
,@Bean(initMethod)
之外,还可以实现InitializingBean
接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个初始化方法,那么它们的执行顺序是什么样的呢?-
创建
Bean1
类package com.colin.a07; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import javax.annotation.PostConstruct; @Slf4j public class Bean1 implements InitializingBean { @PostConstruct public void init() { log.info("使用@PostConstruct 注解 初始化1"); } @Override public void afterPropertiesSet() throws Exception { log.info("实现 InitializingBean 接口,初始化方法2"); } public void init3() { log.debug("初始化3"); } }
-
创建测试类A07 并在类中使用
@Bean
注解指明bean1的初始化方法并将bean1加入容器,准备一个干净的容器,加入一些用到的后处理器,初始化容器。package com.colin.a07; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor; import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.context.support.GenericApplicationContext; public class A07 { public static void main(String[] args) { //1、准备一个干净的容器 GenericApplicationContext context = new GenericApplicationContext(); //2、将A07注册到容器中 context.registerBean(A07.class); //3、加入一些bean工厂的后处理器 /** *可以解析的注解: @ComponentScan @Bean @Import @ImportResource */ context.registerBean(ConfigurationClassPostProcessor.class); //4、加入一些bean的后处理器 /** * 为context提供处理@PostConstruct注解的能力 */ context.registerBean(CommonAnnotationBeanPostProcessor.class); //5、初始化容器 context.refresh(); //6、关闭容器 context.close(); } @Bean(initMethod = "init3") public Bean1 bean() { return new Bean1(); } }
-
运行测试:可以看到执行顺序:
@PostConstruct
>>>>InitializingBean
>>>>@Bean(initMethod)
-
②、演示2 - 销毁顺序
-
同样的Spring也 提供了多种销毁手段,除
@PreDestroy
,@Bean(destroyMethod)
之外,还可以实现DisposableBean
接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个销毁方法,那么它们的执行顺序是什么样的呢?-
创建
Bean2
类package com.colin.a07; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.DisposableBean; import javax.annotation.PreDestroy; @Slf4j public class Bean2 implements DisposableBean { @PreDestroy public void preDestroy() { log.info("使用@PreDestroy 注解 销毁方法1"); } @Override public void destroy() throws Exception { log.info("实现 DisposableBean 接口,销毁方法2"); } public void destroy3() { log.debug("销毁方法3"); } }
-
创建测试类A07_1并在类中使用
@Bean
注解指明bean2的销毁方法并将bean2加入容器,准备一个干净的容器,加入一些用到的后处理器,初始化容器。package com.colin.a07; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor; import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.context.support.GenericApplicationContext; public class A07_1 { public static void main(String[] args) { //1、准备一个干净的容器 GenericApplicationContext context = new GenericApplicationContext(); //2、将A07注册到容器中 context.registerBean(A07_1.class); //3、加入一些bean工厂的后处理器 /** *可以解析的注解: @ComponentScan @Bean @Import @ImportResource */ context.registerBean(ConfigurationClassPostProcessor.class); //4、加入一些bean的后处理器 /** * 为context提供处理@PostConstruct注解的能力 */ context.registerBean(CommonAnnotationBeanPostProcessor.class); //5、初始化容器 context.refresh(); //6、关闭容器 context.close(); } @Bean(destroyMethod = "destroy3") public Bean2 bean() { return new Bean2(); } }
-
运行测试:可以看到执行顺序:
@PreDestroy
>>>>DisposableBean
>>>>@Bean(destroyMethod)
-
③、总结
初始化方法,它们的执行顺序
@PostConstruct 标注的初始化方法
InitializingBean 接口的初始化方法
@Bean(initMethod) 指定的初始化方法
销毁方法,它们的执行顺序
@PreDestroy 标注的销毁方法
DisposableBean 接口的销毁方法
@Bean(destroyMethod) 指定的销毁方法
a08
、bean的作用域(Scope
)
-
总述
在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope
- singleton,容器启动时创建(未设置延迟,即未设置延迟加载),容器关闭时销毁
- prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
- request,每次请求用到此 bean 时创建,请求结束时销毁
- session,每个会话用到此 bean 时创建,会话结束时销毁
- application,web 容器用到此 bean 时创建,容器停止时销毁
有些文章提到有 globalSession 这一 Scope,也是陈旧的说法,目前 Spring 中已废弃
①、演示1 - request, session, application 作用域
-
创建响应演示代码,代码结构如下:
- 因为测试需要用到web 环境,使用
SpringBoot
快速构建web环境,A08为启动类 - BeanForRequest、BeanForSession、BeanForApplication为我们自定义的bean,并指定相应的作用域
- MyController是我们的控制器类
- 因为测试需要用到web 环境,使用
-
具体代码如下:
-
A08
package com.colin.a08; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class A08 { public static void main(String[] args) { SpringApplication.run(A08.class, args); } }
-
BeanForRequest
package com.colin.a08; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; /** * 作用域在request的bean */ @Scope("request") @Component public class BeanForRequest { }
-
BeanForSession
package com.colin.a08; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; /** * 作用域在session */ @Scope("session") @Component public class BeanForSession { }
-
BeanForApplicaton
package com.colin.a08; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; /** * 作用域在application */ @Scope("application") @Component public class BeanForApplication { }
-
MyController
这里注意:
BeanForRequest与BeanForSession要加@Lazy注解,不然会出错,原因后续分析
package com.colin.a08; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class MyController { @Lazy @Autowired private BeanForRequest beanForRequest; @Lazy @Autowired private BeanForSession beanForSession; @Autowired private BeanForApplication application; @GetMapping(value = "/test", produces = "text/html") @ResponseBody public String test() { /** * 这里调用了的beanForRequest的tostring 是调用了代理的tostring * 如果没有,就会反射调用object的tostring方法 java > 9 会报非法访问异常 (IllegalAccessException) */ String sb = "<ul>" + "<li>" + "request scope:" + beanForRequest + "</li>" + "<li>" + "session scope:" + beanForSession + "</li>" + "<li>" + "application scope:" + application + "</li>" + "</ul>"; return sb; } }
-
运行,浏览器访问http://localhost:8080/test, 发现后台报错
- 报错信息为: 非法访问异常 (IllegalAccessException)
- 这是因为,在jdk >= 9 的情况下 如果反射调用 jdk 中的一些方法 会报非法访问异常 (IllegalAccessException)
- jdk <= 8 不会有问题
- 解决方法1:可以在BeanForRequest中自己重写toString方法,这样反射调用时,就不会调用Object类的方法,就不会产生此问题
- 解决方法2:可以添加虚拟机参数
--add-opens java.base/java.lang=ALL-UNNAMED
打开限制
-
添加虚拟机参数
--add-opens java.base/java.lang=ALL-UNNAMED
后运行程序,再次访问http://localhost:8080/test-
多次刷新:发现每一次刷新BeanForRequest的值都会改变。因为 作用域在
request
域,每次请求都会创建一个新的bean。 -
打开另一个浏览器访问http://localhost:8080/test,发现BeanForSession也会改变,作用域在
session
域,会在会话过程中存活,直到会话失效。 -
无论开多少个浏览器BeanForApplication的值都不会改变,作用域在
application
域,会与应用共存活,直到应用程序停止才会失效。
-
-
②、演示2 - singleton 注入其它 scope 失效问题
-
准备工作:准备单例对象E,注入多例对象F
-
E类
package com.colin.a08.sub; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Slf4j @Component public class E { private F f; public E() { log.info("E() 构造方法"); } @Autowired public void setF(F f) { this.f = f; log.info("setF(F f) {}", f.getClass()); } public F getF() { return f; } }
-
F类
package com.colin.a08.sub; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Slf4j @Scope("prototype") @Component public class F { public F() { log.info("F() 构造方法"); } }
-
新建测试类A08_1进行测试
package com.colin.a08; import com.colin.a08.sub.E; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; @Slf4j @ComponentScan("com.colin.a08.sub") public class A08_1 { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A08_1.class); E e = context.getBean(E.class); log.info("{}", e.getF()); log.info("{}", e.getF()); log.info("{}", e.getF()); } }
-
测试结果:
- 结果分析,我们发现虽然F是多例的,但是我们每次获取F都是同一个对象,而不是期望的多例对象。
-
③、singleton 注入其它 scope 失效问题分析
- 对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F
④、singleton 注入其它 scope 失效问题解决方案
-
使用 @Lazy 生成代理,依赖注入注入代理对象
-
代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理创建新的 F 对象。
-
创建多例对象F2,在E中依赖注入F2,但是与注入F不同,加上
@Lasy
F2类
package com.colin.a08.sub; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Slf4j @Scope("prototype") @Component public class F2 { public F2() { log.info("F2() 构造方法"); } }
E类
@Slf4j @Component public class E { private F2 f2; ..... @Lazy @Autowired public void setF2(F2 f2) { this.f2 = f2; log.info("setF2(F2 f2) {}", f2.getClass()); } public F2 getF2() { return f2; } }
在A08_1进行测试 注意 如果 jdk > 8, 运行时请添加
--add-opens java.base/java.lang=ALL-UNNAMED
,因为代码会调用到jdk 的类的方法@Slf4j @ComponentScan("com.colin.a08.sub") public class A08_1 { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A08_1.class); E e = context.getBean(E.class); log.info("{}", e.getF()); log.info("{}", e.getF()); log.info("{}", e.getF()); log.info("=====================================>>>>"); log.info("{}", e.getF2()); log.info("{}", e.getF2()); log.info("{}", e.getF2()); } }
结果:可以看到加了@Lazy注解的F2每次获取都是一个新对象。
注意:
- @Lazy 加在也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了
- @Autowired 加在 set 方法的目的类似
-
-
-
使用
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
,定义作用域 的时候指明代理模式-
创建多例对象F3,F3指明作用域时,指明代理模式为
ScopedProxyMode.TARGET_CLASS
(模式说明:ScopedProxyMode.TARGET_CLASS 创建基于类的代理(使用 CGLIB))F3类:
package com.colin.a08.sub; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.stereotype.Component; @Slf4j @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) @Component public class F3 { public F3() { log.info("F3() 构造方法"); } }
E类,注入F3
package com.colin.a08.sub; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @Slf4j @Component public class E { ...... private F3 f3; ...... @Autowired public void setF3(F3 f3) { this.f3 = f3; log.info("setF3(F3 f3) {}", f3.getClass()); } public F3 getF3() { return f3; } }
在A08_1进行测试 注意 如果 jdk > 8, 运行时请添加
--add-opens java.base/java.lang=ALL-UNNAMED
,因为代码会调用到jdk 的类的方法/* 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED */ @Slf4j @ComponentScan("com.colin.a08.sub") public class A08_1 { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A08_1.class); E e = context.getBean(E.class); ...... log.info("=====================================>>>>"); log.info("{}", e.getF3()); log.info("{}", e.getF3()); log.info("{}", e.getF3()); } }
结果:可以看到F3每次获取都是一个新对象
-
-
使用
ObjectFactory
对象工厂,E想使用多例对象,不直接注入多例对象,而是注入对象的工厂。-
创建多例对象F4,E对象注入生产F4的工厂对象
-
F4类
package com.colin.a08.sub; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Slf4j @Scope(value = "prototype") @Component public class F4 { public F4() { log.info("F4() 构造方法"); } }
-
E类
package com.colin.a08.sub; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Slf4j @Component public class E { ...... @Autowired private ObjectFactory<F4> f4; ...... public F4 getF4() { return f4.getObject(); } }
-
在A08_1进行测试 注意 如果 jdk > 8, 运行时请添加
--add-opens java.base/java.lang=ALL-UNNAMED
,因为代码会调用到jdk 的类的方法/* 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED */ @Slf4j @ComponentScan("com.colin.a08.sub") public class A08_1 { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A08_1.class); E e = context.getBean(E.class); ...... log.info("=====================================>>>>"); log.info("{}", e.getF4()); log.info("{}", e.getF4()); log.info("{}", e.getF4()); } }
-
结果:可以看到F4每次获取都是一个新对象
-
-
-
注入
ApplicationContext
容器,简介获取对象-
创建多例对象F5,E对象注入
ApplicationContext
容器-
F5类
package com.colin.a08.sub; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Slf4j @Scope(value = "prototype") @Component public class F5 { public F5() { log.info("F5() 构造方法"); } }
-
E类
package com.colin.a08.sub; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @Slf4j @Component public class E { ...... @Autowired private ApplicationContext context; ...... public F5 getF5() { return context.getBean(F5.class); } }
-
在A08_1进行测试 注意 如果 jdk > 8, 运行时请添加
--add-opens java.base/java.lang=ALL-UNNAMED
,因为代码会调用到jdk 的类的方法/* 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED */ @Slf4j @ComponentScan("com.colin.a08.sub") public class A08_1 { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A08_1.class); E e = context.getBean(E.class); ...... log.info("=====================================>>>>"); log.info("{}", e.getF5()); log.info("{}", e.getF5()); log.info("{}", e.getF5()); } }
-
结果:可以看到F5每次获取都是一个新对象
-
-
⑤、总结
- 单例注入其它 scope 的四种解决方法
- @Lazy
- @Scope(value = “prototype”, proxyMode = ScopedProxyMode.TARGET_CLASS)
- ObjectFactory
- ApplicationContext
- 解决方法虽然不同,但理念上殊途同归: 都是推迟其它 scope bean 的获取
二
、AOP
-
总述
AOP
底层实现方式之一是代理,由代理结合通知和目标,提供增强功能除此以外,
aspectj
提供了两种另外的AOP
底层实现:- 第一种是通过
ajc 编译器
在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中 - 第二种是通过
agent
在加载目标类时,修改目标类的字节码,织入增强功能 - 作为对比,之前学习的代理是运行时生成新的字节码
简单比较的话:
aspectj
在编译和加载时,修改目标字节码,性能较高aspectj
因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强- 但
aspectj
侵入性较强,且需要学习新的aspectj
特有语法,因此没有广泛流行
- 第一种是通过
a09
、AOP
实现之 ajc 编译器
①、ajc 编译器
的演示
-
演示准备,新创建一个module,**注意!!!**sdk一定要选择jdk16及以下,因为目前的
aspectj-maven-plugin 1.14.0
(ajc编译插件) 最高只支持到 java 16。-
工程目录结构
-
pom
文件<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.colin</groupId> <artifactId>A09</artifactId> <version>1.0</version> <!-- jdk8 --> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--aspectj相关依赖--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- aspectj编译插件 --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.14.0</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> <!-- use this goal to weave all your main classes --> <goal>compile</goal> <!-- use this goal to weave all your test classes --> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
-
MyAspect切面类: 值得注意的是,这个切面并没有被spring管理,解析切面的工作是编译时,编译器去做的。
package com.colin.a09; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @Author Colin * @Description 切面 * @Date 2022/6/6 17:06 */ @Aspect // ⬅️注意此切面并未被 Spring 管理 public class MyAspect { private static final Logger log = LoggerFactory.getLogger(MyAspect.class); @Before("execution(* com.colin.a09.MyService.foo())") public void before() { log.info("before()"); } }
-
MyService业务类
package com.colin.a09; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class MyService { private static final Logger log = LoggerFactory.getLogger(MyAspect.class); public static void foo() { log.info("foo()"); } }
-
A09启动类,为啥省事,直接在启动类进行代码测试。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; /** * 注意几点 * 1. 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16 * 2. 一定要用 maven 的 compile 来编译, idea 不会调用 ajc 编译器 */ @SpringBootApplication public class A09 { private static final Logger log = LoggerFactory.getLogger(A09.class); public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A09.class, args); MyService service = context.getBean(MyService.class); log.info("service class: {}", service.getClass()); MyService.foo(); } }
-
运行程序: 发现目标被增强了,并且目标类并不是代理,而是目标类本身。如果运行没被增强,需要自己手动执行一下maven的编译(compile),让ajc编译器能够发生作用。
-
我们通过idea看一下生成的MyService类的字节码(通过idea的反编译)
- 我们看到了神奇的一幕:MyService的foo方法多了一步切面增强方法的调用。原来这就是增强的原因,相当于直接将增强的逻辑写在了原方法里,等同于改源代码。
-
②、总结
- 编译器也能修改 class 实现增强
- 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强
注意
- 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16
- 一定要用 maven 的 compile 来编译, idea 不会调用 ajc 编译器
a10
、 AOP 实现之 agent
类加载
Java Agent
介绍- Java Agent 又叫做 Java 探针,是在 JDK1.5 引入的一种可以动态修改 Java 字节码的技术。
- Java 类编译之后形成字节码被 JVM 执行,在 JVM 在执行这些字节码之前获取这些字节码信息,并且通过字节码转换器对这些字节码进行修改,来完成一些额外的功能。
- 微服务技术链路追踪
Skywalking
就是基于探针。
①、agent
的演示
-
演示准备,新创建一个module,sdk选择jdk8
-
项目代码与
a09
几乎一致,唯一不同的是pom
不需要ajc
的编译器插件,在resource下新建META-INF
目录,目录下创建aop.xml,这是为了告诉探针程序切面与目标。 -
pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.colin</groupId> <artifactId>A10</artifactId> <version>1.0</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- aspectj 相关依赖--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> </dependency> </dependencies> </project>
-
MyAspect
package com.colin.a10; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Aspect // ⬅️注意此切面并未被 Spring 管理 public class MyAspect { private static final Logger log = LoggerFactory.getLogger(MyAspect.class); @Before("execution(* com.colin.a10.MyService.*())") public void before() { log.info("before()"); } }
-
MyService
package com.colin.a10; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class MyService { private static final Logger log = LoggerFactory.getLogger(MyService.class); final public void foo() { log.info("foo()"); this.bar(); } public void bar() { log.info("bar()"); } }
-
aop.xml
<aspectj> <aspects> <aspect name="com.colin.a10.MyAspect"/> <weaver options="-verbose -showWeaveInfo"> <include within="com.colin.a10.MyService"/> <include within="com.colin.a10.MyAspect"/> </weaver> </aspects> </aspectj>
-
A10
package com.colin.a10; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; /* 注意几点 1. 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16 2. 运行时需要在 VM options 里加入 -javaagent:D:\\aspectjweaver-1.9.7.jar (指向jar包路径) */ @SpringBootApplication public class A10 { private static final Logger log = LoggerFactory.getLogger(A10.class); public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(A10.class, args); MyService service = context.getBean(MyService.class); // ⬇️MyService 并非代理, 但 foo 方法也被增强了, 做增强的 java agent, 在加载类时, 修改了 class 字节码 log.info("service class: {}", service.getClass()); service.foo(); context.close(); } }
-
-
下载
agent
增强jar
包: 下载地址:Maven Repository: org.aspectj » aspectjweaver » 1.9.7 (mvnrepository.com) -
开始测试,运行A10, 运行时需要在 VM options 里加入 -javaagent:D:\aspectjweaver-1.9.7.jar (指向jar包路径)。
-
运行结果:
-
可以看到MyService 的方法付都已经增强了,并且多了些红色字体的打印,说明探针(agent)发挥了作用。
-
我们通过idea看一下生成的MyService类的字节码(通过idea的反编译), 发现字节码没有发生改变,这是因为增强功能是探针通过动态修改 Java 字节码实现的,是在
类加载时
实现的.
-
-
②、总结
-
- aop 的原理并非代理一种, agent 也能, 只要字节码变了, 行为就变了。
a11
、AOP 实现之 proxy
①、演示1 - jdk 动态代理
-
新建一个类
JdkProxyDemo
,使用JDK实现代理,从而增强目标方法。package com.colin.a11; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @Slf4j public class JdkProxyDemo { /** * 定义一个接口, * JDK 的动态代理,要求目标类(被增强类)实现一个接口 */ interface Foo { void foo(); } /** * 声明一个目标类 */ static final class Target implements Foo { @Override public void foo() { log.info("target foo"); } } public static void main(String[] args) { /** * jdk 只能针对接口增强 */ //1、创建目标对象 Target target = new Target(); //2、使用jdk 提供的类Proxy,调用Proxy的newProxyInstance创建代理对象 /** * newProxyInstance三个参数 * classLoader: 类加载器, * 因为生成代理是动态生成代理类的字节码(没有源代码编译阶段),需要有类加载器加载动态生成的字节码 * new Class[]{Foo.class}: 被代理类实现的接口 * * InvocationHandler : 第三个参数是一个接口,是需要我们编写的增强的逻辑处理。 * invoke方法: proxy 代理类本身 * method 被代理类正在执行的方法对象 * args 被代理类正在执行的方法参数 * */ ClassLoader classLoader = JdkProxyDemo.class.getClassLoader(); Foo proxy = (Foo) Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("before"); // 目标.方法(参数) // 方法.invoke(目标, 参数); 反射调用目标方法 Object result = method.invoke(target, args); log.info("after...."); return result; // 让代理也返回目标方法执行的结果 } } ); //打印代理类 类型 log.info(proxy.getClass().toString()); //执行代理对象方法 proxy.foo(); } }
-
代码解析:
- 首先定义一个接口
Foo
,这是因为JDK 的动态代理,要求目标类(被增强类)实现一个接口 - 声明一个目标类
Target
实现Foo
接口,这个类被作为被代理类(即被增强类) - Main创建目标对象,并根据目标类生成代理类,生成步骤
- 使用jdk 提供的类Proxy,调用Proxy的newProxyInstance创建代理。
- newProxyInstance方法参数一:classLoader: 类加载器, 因为生成代理是动态生成代理类的字节码(没有源代码编译阶段),需要有类加载器加载动态生成的字节码
- newProxyInstance方法参数二:new Class[]{Foo.class}: 被代理类实现的接口
- newProxyInstance方法参数三:InvocationHandler 第三个参数是一个接口,是需要我们编写的增强的逻辑处理。
- InvocationHandler 接口的 invoke方法:
- 参数一: proxy 代理类本身
- 参数二: method 被代理类正在执行的方法对象
- 参数三: args 被代理类正在执行的方法参数
- 使用jdk 提供的类Proxy,调用Proxy的newProxyInstance创建代理。
- 在InvocationHandler 接口的 invoke方法编写增强逻辑,并使用反射调用目标对象的方法。
- 执行代理对象方法。
- 首先定义一个接口
-
执行结果:
⑴、总结
- jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系。
- 生成代理是动态生成代理类的字节码(没有源代码编译阶段)
②、演示2 - cglib 代理
-
新建一个类
CglibProxyDemo
,使用cglib 实现代理,从而增强目标方法。package com.colin.a11; import lombok.extern.slf4j.Slf4j; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @Author Colin * @Description Cglib生成代理 * @Date 2022/6/7 11:27 */ @Slf4j public class CglibProxyDemo { /** * 声明一个目标类 */ static class Target { public void foo() { log.info("target foo"); } } public static void main(String[] args) { /** * 代理是子类型, 目标是父类型 */ //1、创建目标对象 Target target = new Target(); //2、使用cglib 提供的类Enhancer,调用Enhancer的create创建代理对象 /** * 参数1:父类类型(生成的代理类是目标类的子类型) * 参数2:Callback接口 是方法增强逻辑接口,等同于jdk代理中的InvocationHandler接口 * 但我们一般用它的子类型接口MethodInterceptor */ /** * * @param o 代理对象本身 * @param method 被代理类执行的方法对象 * @param objects 被代理类执行的方法参数 * @param methodProxy 被代理类执行的方法类的代理类对象 * @return * @throws Throwable */ /** * 调用目标方法: cglib提供了多种方式 */ // 方式一:用方法反射调用目标 需要使用目标对象 // 方式二:使用 methodProxy 的invoke 方法,需要使用目标对象 但它可以避免反射调用 // Object result = methodProxy.invoke(target, args); // 内部没有用反射, 需要目标 (spring 默认实现方法) // 方式三:使用 methodProxy 的invokeSuper 方法 不需要使用目标对象(需要代理对象) 也可以避免反射调用 //Object result = methodProxy.invokeSuper(o, args); // 内部没有用反射, 需要代理 Target proxy = (Target) Enhancer.create(Target.class, new MethodInterceptor() { /** * * @param o 代理对象本身 * @param method 被代理类执行的方法对象 * @param objects 被代理类执行的方法参数 * @param methodProxy 被代理类执行的方法类的代理类对象 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { log.info("before..."); /** * 调用目标方法: cglib提供了多种方式 */ // 方式一:用方法反射调用目标 需要使用目标对象 Object result = method.invoke(target, args); // 方式二:使用 methodProxy 的invoke 方法,需要使用目标对象 但它可以避免反射调用 // Object result = methodProxy.invoke(target, args); // 内部没有用反射, 需要目标 (spring 默认实现方法) // 方式三:使用 methodProxy 的invokeSuper 方法 不需要使用目标对象(需要代理对象) 也可以避免反射调用 //Object result = methodProxy.invokeSuper(o, args); // 内部没有用反射, 需要代理 log.info("after..."); return result; } }); //打印代理类对象类型 log.info(proxy.getClass().toString()); //使用代理类对象调用方法 proxy.foo(); } }
-
代码解析
- 声明一个目标类
Target
,这个类被作为被代理类(即被增强类),注意的是这个类不能被final
修饰,因为cglib 动态代理是通过继承,让代理类成为目标类的子类,从而通过重写方法的方式增强目标类,如果目标类被final
修饰,即不允许有子类,就无法生成代理。 - 使用cglib 提供的类
Enhancer
,调用Enhancer
的create创建代理对象- create方法参数解析:
- 参数1:父类类型(生成的代理类是目标类的子类型)
- 参数2:Callback接口 是方法增强逻辑接口,等同于jdk代理中的InvocationHandler接口但我们一般用它的子类型接口
MethodInterceptor
MethodInterceptor
接口intercept
方法参数解析 (根据代码对比)- o 代理对象本身
- method 被代理类执行的方法对象
- objects 被代理类执行的方法参数
- methodProxy 被代理类执行的方法类的代理类对象
- create方法参数解析:
MethodInterceptor
接口intercept
方法实现增强逻辑,并调用目标方法。- 调用目标方法: cglib提供了多种方式
- 方式一:用方法反射调用目标 需要使用目标对象 : 如代码78行
Object result = method.invoke(target, args);
这里的调用需要目标对象。 - 方式二:使用 methodProxy 的invoke 方法,如代码80行
Object result = methodProxy.invoke(target, args);
需要使用目标对象 但它可以避免反射调用,效率更高。 - 方式三:使用 methodProxy 的invokeSuper 方法 ,如代码82行
Object result = methodProxy.invokeSuper(o, args);
不需要使用目标对象(需要代理对象) ,也可以避免反射调用,效率更高。
- 方式一:用方法反射调用目标 需要使用目标对象 : 如代码78行
- 调用目标方法: cglib提供了多种方式
- 声明一个目标类
-
运行结果:
⑴、总结
- cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系
- 限制⛔:根据上述分析 final 类无法被 cglib 增强
- 调用目标方法: cglib提供了多种方式,即可以反射调用,又可以避免反射调用,效率更高。
a12
、JDK 动态代理进阶(探究JDK动态代理原理)
-
我们知道,我们增强目标类,是为其生成了代理类,我们可不可以看一下生成的代理类源吗?
- 很遗憾,我们看不到它的源代码,这是因为代理类的生成是动态的,JDK直接生成的是代理类的字节码,在内存中直接进行字节码的类加载。
-
我们换个思路,模拟一下手动试着编写一下代理类的源代码。
①、演示1 - 模拟 jdk 动态代理
-
原始版本:
-
我们新建一个接口Foo,定义两个方法,一个有返回值,一个没有返回值。因为JDK代理需要有接口。
package com.colin.a12.base; /** *接口 */ public interface Foo { void foo(); int bar(); }
-
新建目标类Target,实现Foo接口。
package com.colin.a12.base; import lombok.extern.slf4j.Slf4j; /** * 待增强的目标类 */ @Slf4j public class Target implements Foo { @Override public void foo() { log.info("Target foo()"); } @Override public int bar() { log.info("Target bar()"); return 0; } }
-
创建代理类$Proxy0,模仿JDK代理类的命名。我们不知道怎么写,但我们知道代理增强无非是原有方法的增强。于是我们写下来以下代码
package com.colin.a12.base; import lombok.extern.slf4j.Slf4j; /** * 模拟编写 的代理类 */ @Slf4j public class $Proxy0 implements Foo{ @Override public void foo() { log.info("$Proxy0 增强 before。。。。"); //调用目标 new Target().foo(); } @Override public int bar() { return 0; } }
-
编写测试类A12,调用代理类。
package com.colin.a12.base; public class A12 { public static void main(String[] args) { //测试代理类 $Proxy0 proxy = new $Proxy0(); proxy.foo(); } }
-
结果:结果确实增强了,代码很简单,我们以后慢慢去优化它。把它慢慢变成JDK代理类的样子。
-
-
改进版本1 将增强逻辑抽离。
-
经历了原始版本,我们发现一个问题,就是我们的增强逻辑是耦合在代码里的,但是我们知道增强逻辑是不固定的,
比如:有的增强是对数据库事务增强的,有的是权限的。。。。
解决方法我们定义一个接口,作为代理类的成员变量,将增强逻辑通过接口传递给方法。
-
创建接口
InvocationHandler
package com.colin.a12.improve1; /** * 增强逻辑接口 */ public interface InvocationHandler { void invoke(); }
-
修改代理类的逻辑,如下,将增强逻辑变成接口调用。
package com.colin.a12.improve1; /** * 模拟编写 的代理类 */ public class $Proxy0 implements Foo { private InvocationHandler invocationHandler; public $Proxy0(InvocationHandler invocationHandler) { this.invocationHandler = invocationHandler; } @Override public void foo() { invocationHandler.invoke(); } @Override public int bar() { invocationHandler.invoke(); return 0; } }
-
测试
package com.colin.a12.improve1; import com.colin.a12.base.Target; import lombok.extern.slf4j.Slf4j; @Slf4j public class A12 { public static void main(String[] args) { //测试代理类 $Proxy0 proxy = new $Proxy0(() -> { log.info("事务增强"); new Target().foo(); }); $Proxy0 proxy1 = new $Proxy0(() -> { log.info("权限增强"); new Target().foo(); }); proxy.foo(); proxy1.foo(); } }
-
结果:确实可以实现不同增强逻辑的增强。
-
出现的问题:我们使用代理类调用bar方法。发现我们不管调用的是代理类的什么方法都调用的是目标类的foo方法。
package com.colin.a12.improve1; import com.colin.a12.base.Target; import lombok.extern.slf4j.Slf4j; @Slf4j public class A12 { public static void main(String[] args) { ...... /** * 出现的问题 * 不管调用的是代理类的什么方法都调用的是目标类的foo方法。 */ //调用bar方法 log.info("=============================>>"); proxy.bar(); proxy1.bar(); } }
-
结果:
-
-
改进版本2 处理改进版本1出现的问题
-
分析原因: 改进版本1出现问题,不管调用的是代理类的什么方法都调用的是目标类的foo方法。
- 代理类中方法重写逻辑中,只是接口的调用,并不能感知用户现在调用的是那个方法。
- 代理类的接口内的逻辑,是通过构造传递的,对于同一个代理对象来说,接口的逻辑中调用目标的逻辑就定死了(无论调用代理类的那个方法都不会改变)
-
修改
InvocationHandler
接口,我们把调用方法与参数也通过接口传递给代理类package com.colin.a12.improve2; import java.lang.reflect.Method; /** * 增强逻辑接口 */ public interface InvocationHandler { /** * @param method 当前调用的方法 * @param args 方法可能有参数,所以用一个Object数组,提高通用性 */ void invoke(Method method, Object[] args) throws Throwable; }
-
修改代理类代码,给接口传递方法对象
package com.colin.a12.improve2; import java.lang.reflect.Method; /** * 模拟编写 的代理类 */ public class $Proxy0 implements Foo { private InvocationHandler invocationHandler; public $Proxy0(InvocationHandler invocationHandler) { this.invocationHandler = invocationHandler; } @Override public void foo() { /** * 修改逻辑 传递当前调用方法 */ try { Method foo = Foo.class.getMethod("foo"); invocationHandler.invoke(foo, new Object[0]); } catch (Throwable throwable) { throwable.printStackTrace(); } } @Override public int bar() { try { Method bar = Foo.class.getMethod("bar"); invocationHandler.invoke(bar, new Object[0]); } catch (Throwable throwable) { throwable.printStackTrace(); } return 0; } }
-
测试
package com.colin.a12.improve2; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.Method; @Slf4j public class A12 { public static void main(String[] args) { //目标类 Target target = new Target(); //测试代理类 $Proxy0 proxy = new $Proxy0(new InvocationHandler() { @Override public void invoke(Method method, Object[] args) throws Throwable { //增强逻辑 log.info("前置增强。。。。。。。。。。。。。"); //调用的方法对象已经有啦,通过反射调用方法 method.invoke(target, args); } }); proxy.foo(); proxy.bar(); } }
-
测试 : 测试结果达到了我们的预期但还是有很大的改进空间。
-
-
改进版本3 我们解决了改进版本1的问题,但还是和JDK生成的代理类有很大差别。
-
JDK对异常的处理很规范,还支持返回值,我们整体规范一下代码,异常处理、返回值支持。
-
改进版本2中代理类,每次方法调用就会创建一个新的方法对象,这无疑也是一种浪费,优化一下。
-
修改
InvocationHandler
接口增加对返回值的支持import java.lang.reflect.Method; /** * 增强逻辑接口 */ public interface InvocationHandler { /** * @param method 当前调用的方法 * @param args 方法可能有参数,所以用一个Object数组,提高通用性 */ Object invoke(Method method, Object[] args) throws Throwable; }
-
修改代理类 优化代码
- JDK代理类异常处理分两种情况
- 一种是运行时异常,对于运行时异常JDK的做法是直接抛出
- 一种是检查型异常,对于这种异常JDK会把它包装成运行时异常并抛出
- 方法对象的优化
- JDK代理类中会将方法对象声明成静态的方法对象,在静态代码块中,只初始化一次。
package com.colin.a12.improve3; import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; /** * 模拟编写 的代理类 */ public class $Proxy0 implements Foo { private InvocationHandler invocationHandler; public $Proxy0(InvocationHandler invocationHandler) { this.invocationHandler = invocationHandler; } /** * 将方法对象声明成静态成员变量,防止重复创建 */ static Method foo; static Method bar; /** * 在静态代码块中初始化一次 */ static { try { foo = Foo.class.getMethod("foo"); bar = Foo.class.getMethod("bar"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } @Override public void foo() { /** * 修改逻辑 传递当前调用方法 */ try { invocationHandler.invoke(foo, new Object[0]); } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public int bar() { try { Object result = invocationHandler.invoke(bar, new Object[0]); return (int) result; } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } }
- JDK代理类异常处理分两种情况
-
测试
package com.colin.a12.improve3; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.Method; @Slf4j public class A12 { public static void main(String[] args) { //目标类 Target target = new Target(); //测试代理类 $Proxy0 proxy = new $Proxy0(new InvocationHandler() { @Override public Object invoke(Method method, Object[] args) throws Throwable { //增强逻辑 log.info("前置增强。。。。。。。。。。。。。"); //调用的方法对象已经有啦,通过反射调用方法 return method.invoke(target, args); } }); proxy.foo(); log.info(proxy.bar() + ""); } }
-
结果
-
-
到这,我们自己编写的代理类已经和JDK,几乎一模一样了,只不过JDK的代理类会继承一个父类Proxy,
Proxy
父类中含有一个JDK自带的InvocationHandler
接口。JDK自带的
InvocationHandler
接口会把代理对象本身传递过去。 -
我们把
InvocationHandler
接口换成JDK自带的接口,再稍微改一下代理类的代码,继承Proxy
父类。于是我们就得到了如下这个和JDK生成的代理类一模一样的类了package com.colin.a12.finalcode; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; /** * 模拟编写 的代理类 最终版 */ public class $Proxy0 extends Proxy implements Foo { public $Proxy0(InvocationHandler invocationHandler) { super(invocationHandler); } /** * 将方法对象声明成静态成员变量,防止重复创建 */ static Method foo; static Method bar; /** * 在静态代码块中初始化一次 */ static { try { foo = Foo.class.getMethod("foo"); bar = Foo.class.getMethod("bar"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } @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 { Object result = h.invoke(this, bar, new Object[0]); return (int) result; } catch (RuntimeException | Error e) { throw e; } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } }
⑴、探究:JDK生成的代理类(看一看它的源码)
-
我们无法看到JDK生成的代理类,但是我们就是好奇它长什么样子,我们可以通过一个强大的工具看到它,这个工具可以检测运行中的java程序,并可以反编译它们,它就是阿里巴巴出品Java诊断工具
Arthas
(阿尔萨斯)官网(Arthas 应用诊断利器 (aliyun.com))描述:
当你遇到以下类似问题而束手无策时,
Arthas
可以帮助你解决:- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从JVM内查找某个类的实例?
-
准备工作: 利用a11的代码
JdkProxyDemo
,因为Arthas
要检测运行的java类,所以我们不能让JdkProxyDemo
停止,我们在代码中加上一句控制台输入,让程序一直等到控制台输入,而不停止package com.colin.a11; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @Slf4j public class JdkProxyDemo { ..... public static void main(String[] args) throws IOException { ...... //A12演示使用为了不让程序停止 System.in.read(); } }
-
启动应用程序,并且启动
Arthas
(java -jar arthas-boot.jar ) -
在cmd 窗口可以看到当前程序编号为9 ,所以我们输入编号9,介入应用程序。
-
使用名称 jad 反编译运行中的类,命令: jad 类的全限定类名,这里我们输入
jad com.colin.a11.$Proxy0
我们就得到了JDK生成的代理类的源码。我把它粘贴出来
package com.colin.a11; import com.colin.a11.JdkProxyDemo; import java.lang.invoke.MethodHandles; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; final class $Proxy0 extends Proxy implements JdkProxyDemo.Foo { private static final Method m0; private static final Method m1; private static final Method m2; private static final Method m3; public $Proxy0(InvocationHandler invocationHandler) { super(invocationHandler); } static { try { m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("com.colin.a11.JdkProxyDemo$Foo").getMethod("foo", new Class[0]); return; } catch (NoSuchMethodException noSuchMethodException) { throw new NoSuchMethodError(noSuchMethodException.getMessage()); } catch (ClassNotFoundException classNotFoundException) { throw new NoClassDefFoundError(classNotFoundException.getMessage()); } } public final boolean equals(Object object) { try { return (Boolean)this.h.invoke(this, m1, new Object[]{object}); } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)this.h.invoke(this, m2, null); } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode() { try { return (Integer)this.h.invoke(this, m0, null); } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void foo() { try { this.h.invoke(this, m3, null); return; } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup lookup) throws IllegalAccessException { if (lookup.lookupClass() == Proxy.class && lookup.hasFullPrivilegeAccess()) { return MethodHandles.lookup(); } throw new IllegalAccessException(lookup.toString()); } }
- JDK生成的代理类,还考虑了 Object中方法:
hashCode
,equals
,toString
- JDK生成的代理类,还考虑了 Object中方法:
⑵、探究:JDK生成的代理类(动态生成代理类字节码的技术ASM)
-
简介
- ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
- 其实大白话就是通过代码生成一个类的字节码,通过程序生成程序(太高端了),不过ASM的代码学习成本太高,因为涉及到字节码指令。自己写写不出来。
-
虽然我们写不出来,但丝毫不影响我们对它的兴趣,我们可以换个思路。
- 我们可以通过工具将
演示1
中我们模仿JDK写的代理类生成ASM代码 (idea中有插件,ASM Bytecode Outline)。 - 我们测试运行ASM代码得到字节码byte数组,把它输出到文件里 xxx.class。
- 再通过idea,反编译这个xxx.class,看反编译的结果是不是和我们模仿JDK写的代理类一模一样。如果一样,说明ASM代码确实有如此神通。
- 我们可以通过工具将
-
注意
ASM Bytecode Outline
插件只支持JDK8 我们利用插件 把com.colin.a12.finalcode.$Proxy0
生成ASM代码 (右键选择 Show Bytecode outline) -
我们得到了ASM代码:
package asm.com.colin.a12.finalcode; import java.util.*; import org.objectweb.asm.*; public class $Proxy0Dump implements Opcodes { public static byte[] dump() throws Exception { ClassWriter cw = new ClassWriter(0); FieldVisitor fv; MethodVisitor mv; AnnotationVisitor av0; cw.visit(52, ACC_PUBLIC + ACC_SUPER, "com/colin/a12/finalcode/$Proxy0", null, "java/lang/reflect/Proxy", new String[]{"com/colin/a12/finalcode/Foo"}); cw.visitSource("$Proxy0.java", null); { fv = cw.visitField(ACC_STATIC, "foo", "Ljava/lang/reflect/Method;", null, null); fv.visitEnd(); } { fv = cw.visitField(ACC_STATIC, "bar", "Ljava/lang/reflect/Method;", null, null); fv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null); mv.visitParameter("invocationHandler", 0); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(15, l0); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLineNumber(16, l1); mv.visitInsn(RETURN); Label l2 = new Label(); mv.visitLabel(l2); mv.visitLocalVariable("this", "Lcom/colin/a12/finalcode/$Proxy0;", null, l0, l2, 0); mv.visitLocalVariable("invocationHandler", "Ljava/lang/reflect/InvocationHandler;", null, l0, l2, 1); mv.visitMaxs(2, 2); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "foo", "()V", null, null); mv.visitCode(); Label l0 = new Label(); Label l1 = new Label(); Label l2 = new Label(); mv.visitTryCatchBlock(l0, l1, l2, "java/lang/RuntimeException"); mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Error"); Label l3 = new Label(); mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Throwable"); mv.visitLabel(l0); mv.visitLineNumber(42, l0); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, "com/colin/a12/finalcode/$Proxy0", "h", "Ljava/lang/reflect/InvocationHandler;"); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETSTATIC, "com/colin/a12/finalcode/$Proxy0", "foo", "Ljava/lang/reflect/Method;"); mv.visitInsn(ICONST_0); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true); mv.visitInsn(POP); mv.visitLabel(l1); mv.visitLineNumber(47, l1); Label l4 = new Label(); mv.visitJumpInsn(GOTO, l4); mv.visitLabel(l2); mv.visitLineNumber(43, l2); mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"}); mv.visitVarInsn(ASTORE, 1); Label l5 = new Label(); mv.visitLabel(l5); mv.visitLineNumber(44, l5); mv.visitVarInsn(ALOAD, 1); mv.visitInsn(ATHROW); mv.visitLabel(l3); mv.visitLineNumber(45, l3); mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"}); mv.visitVarInsn(ASTORE, 1); Label l6 = new Label(); mv.visitLabel(l6); mv.visitLineNumber(46, l6); mv.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V", false); mv.visitInsn(ATHROW); mv.visitLabel(l4); mv.visitLineNumber(48, l4); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitInsn(RETURN); Label l7 = new Label(); mv.visitLabel(l7); mv.visitLocalVariable("e", "Ljava/lang/Throwable;", null, l5, l3, 1); mv.visitLocalVariable("e", "Ljava/lang/Throwable;", null, l6, l4, 1); mv.visitLocalVariable("this", "Lcom/colin/a12/finalcode/$Proxy0;", null, l0, l7, 0); mv.visitMaxs(4, 2); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "bar", "()I", null, null); mv.visitCode(); Label l0 = new Label(); Label l1 = new Label(); Label l2 = new Label(); mv.visitTryCatchBlock(l0, l1, l2, "java/lang/RuntimeException"); mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Error"); Label l3 = new Label(); mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Throwable"); mv.visitLabel(l0); mv.visitLineNumber(53, l0); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, "com/colin/a12/finalcode/$Proxy0", "h", "Ljava/lang/reflect/InvocationHandler;"); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETSTATIC, "com/colin/a12/finalcode/$Proxy0", "bar", "Ljava/lang/reflect/Method;"); mv.visitInsn(ICONST_0); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true); mv.visitVarInsn(ASTORE, 1); Label l4 = new Label(); mv.visitLabel(l4); mv.visitLineNumber(54, l4); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); mv.visitLabel(l1); mv.visitInsn(IRETURN); mv.visitLabel(l2); mv.visitLineNumber(55, l2); mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"}); mv.visitVarInsn(ASTORE, 1); Label l5 = new Label(); mv.visitLabel(l5); mv.visitLineNumber(56, l5); mv.visitVarInsn(ALOAD, 1); mv.visitInsn(ATHROW); mv.visitLabel(l3); mv.visitLineNumber(57, l3); mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"}); mv.visitVarInsn(ASTORE, 1); Label l6 = new Label(); mv.visitLabel(l6); mv.visitLineNumber(58, l6); mv.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V", false); mv.visitInsn(ATHROW); Label l7 = new Label(); mv.visitLabel(l7); mv.visitLocalVariable("result", "Ljava/lang/Object;", null, l4, l2, 1); mv.visitLocalVariable("e", "Ljava/lang/Throwable;", null, l5, l3, 1); mv.visitLocalVariable("e", "Ljava/lang/Throwable;", null, l6, l7, 1); mv.visitLocalVariable("this", "Lcom/colin/a12/finalcode/$Proxy0;", null, l0, l7, 0); mv.visitMaxs(4, 2); mv.visitEnd(); } { mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); mv.visitCode(); Label l0 = new Label(); Label l1 = new Label(); Label l2 = new Label(); mv.visitTryCatchBlock(l0, l1, l2, "java/lang/NoSuchMethodException"); mv.visitLabel(l0); mv.visitLineNumber(29, l0); mv.visitLdcInsn(Type.getType("Lcom/colin/a12/finalcode/Foo;")); mv.visitLdcInsn("foo"); mv.visitInsn(ICONST_0); mv.visitTypeInsn(ANEWARRAY, "java/lang/Class"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); mv.visitFieldInsn(PUTSTATIC, "com/colin/a12/finalcode/$Proxy0", "foo", "Ljava/lang/reflect/Method;"); Label l3 = new Label(); mv.visitLabel(l3); mv.visitLineNumber(30, l3); mv.visitLdcInsn(Type.getType("Lcom/colin/a12/finalcode/Foo;")); mv.visitLdcInsn("bar"); mv.visitInsn(ICONST_0); mv.visitTypeInsn(ANEWARRAY, "java/lang/Class"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); mv.visitFieldInsn(PUTSTATIC, "com/colin/a12/finalcode/$Proxy0", "bar", "Ljava/lang/reflect/Method;"); mv.visitLabel(l1); mv.visitLineNumber(33, l1); Label l4 = new Label(); mv.visitJumpInsn(GOTO, l4); mv.visitLabel(l2); mv.visitLineNumber(31, l2); mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/NoSuchMethodException"}); mv.visitVarInsn(ASTORE, 0); Label l5 = new Label(); mv.visitLabel(l5); mv.visitLineNumber(32, l5); mv.visitTypeInsn(NEW, "java/lang/NoSuchMethodError"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/NoSuchMethodException", "getMessage", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoSuchMethodError", "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); mv.visitLabel(l4); mv.visitLineNumber(34, l4); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitInsn(RETURN); mv.visitLocalVariable("e", "Ljava/lang/NoSuchMethodException;", null, l5, l4, 0); mv.visitMaxs(3, 1); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } }
-
开始测试
package com.colin.a12.testASM; import java.io.File; import java.io.FileOutputStream; public class Test { public static void main(String[] args) throws Exception { byte[] classByte = $Proxy0Dump.dump(); FileOutputStream outputStream = new FileOutputStream(new File("D:\\1.class")); outputStream.write(classByte); } }
-
进行反编译:发现跟我们编写的代码一模一样,不仅感叹 确实技术的魅力就在此处了。
-
我们还可以直接加载这个字节码,不需要输出到文件
package com.colin.a12.testASM; import com.colin.a12.finalcode.Foo; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class Test1 { public static void main(String[] args) throws Exception { byte[] classByte = $Proxy0Dump.dump(); //直接使用自定义类加载器加载字节码 ClassLoader loader = new ClassLoader() { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { return super.defineClass(name, classByte, 0, classByte.length); } }; //加载类对象 Class<?> proxyClass = loader.loadClass("com.colin.a12.finalcode.$Proxy0"); //利用反射获取类的构造 Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class); //利用反射创建实例 Foo proxy = (Foo) constructor.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before..."); System.out.println("调用目标"); return null; } }); //调用 proxy.foo(); } }
-
结果:
-
⑶、总结
代理一点都不难,无非就是利用了多态、反射的知识
-
方法重写可以增强逻辑,只不过这【增强逻辑】千变万化,不能写死在代理内部
-
通过接口回调将【增强逻辑】置于代理类之外
-
配合接口方法反射(是多态调用),就可以再联动调用目标方法
-
会用 arthas 的 jad 工具反编译代理类
-
限制⛔:代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过代理实现
②、演示2 - 方法反射优化
-
方法的调用很耗性能,高版本的JDK对反射调用进行了优化。
-
JDK 方法反射调用,底层是使用MethodAccessor 的实现类。
-
编写
TestMethodInvoke
类,测试方法调用,并利用反射查看调用的具体底层实现类package com.colin.a12.testMethodInvoke; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * 运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED */ public class TestMethodInvoke { public static void main(String[] args) throws Exception { Method foo = TestMethodInvoke.class.getMethod("foo", int.class); for (int i = 1; i <= 17; i++) { show(i, foo); foo.invoke(null, i); } System.in.read(); } /** * JDK 方法反射调用, * 底层是使用MethodAccessor 的实现类 */ //通过反射查看内部细节 private static void show(int i, Method foo) throws Exception { Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor"); getMethodAccessor.setAccessible(true); Object invoke = getMethodAccessor.invoke(foo); if (invoke == null) { System.out.println((i + ":" + null)); return; } Field delegate = Class.forName("jdk.internal.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate"); delegate.setAccessible(true); System.out.println((i + ":" + delegate.get(invoke))); } public static void foo(int i) { System.out.println(i + ":" + "foo"); } }
-
运行结果:前16次都是真正的反射调用,但第17次调用反射方法时,调用的具体底层实现类变成了
GeneratedMethodAccessor2
-
我们通过
Arthas
看一下它的源码,可以清晰的看到方法调用变成了直接调用,从而提高了性能。
-
-
⑴、总结
- 前 16 次反射性能较低
- 第 17 次调用会生成代理类,优化为非反射调用
- 会用 arthas 的 jad 工具反编译第 17 次调用生成的代理类
注意
运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
a13
、cglib 代理进阶
①、演示1 - 模拟 cglib 动态代理
-
有了a12的铺垫,cglib 和 jdk 动态代理原理查不多
- 回调的接口换了一下,InvocationHandler 改成了
MethodInterceptor
。
- 回调的接口换了一下,InvocationHandler 改成了
-
调用目标时有所改进,增加了无反射调用目标的机制。
MethodInterceptor
接口的intercept
方法参数
-
参数一 Object : 代理对象本身。
- 参数二 Method: 被代理类执行的方法对象
-
参数三 Objects : 被代理类执行的方法参数
- 参数四 MethodProxy :被代理类执行的方法类的代理类对象(避免反射要用到,这里先不涉及)
-
我们按照JDK代理的套路,写出cglib代理的基础版本(基础版本不涉及参数四 MethodProxy,以及避免反射逻辑)
-
Target 目标类
package com.colin.a13.base; import lombok.extern.slf4j.Slf4j; /** * 目标类 * cglib不需要目标类实现接口 * cglib生成的代理类是目标类的子类 */ @Slf4j public class Target { public void save() { log.info("Target save()"); } public void save(int i) { log.info("Target save(int)"); } public void save(long j) { log.info("Target save(long)"); } }
-
Proxy代理类 :基础版本 :
MethodInterceptor
接口的intercept
方法参数的参数四 MethodProxy全部传递null。package com.colin.a13.base; import lombok.extern.slf4j.Slf4j; import org.springframework.cglib.proxy.MethodInterceptor; import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; /** * 代理类 基础版本 * 模拟cglib生成的代理类, * 代理类是目标类的子类 */ @Slf4j public class Proxy extends Target { private MethodInterceptor methodInterceptor; public Proxy(MethodInterceptor methodInterceptor) { this.methodInterceptor = methodInterceptor; } /** * 定义方法对象 */ static Method save0; static Method save1; static Method save2; static { try { save0 = Target.class.getMethod("save"); save1 = Target.class.getMethod("save", int.class); save2 = Target.class.getMethod("save", long.class); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } @Override public void save() { try { methodInterceptor.intercept(this, save0, new Object[0], null); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public void save(int i) { try { methodInterceptor.intercept(this, save1, new Object[]{i}, null); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public void save(long j) { try { methodInterceptor.intercept(this, save2, new Object[]{j}, null); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } }
-
测试
package com.colin.a13.base; import lombok.extern.slf4j.Slf4j; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; @Slf4j public class A13 { public static void main(String[] args) { /** * 测试基础版本 */ //1、目标对象 Target target = new Target(); //2、代理类对象 Proxy proxy = new Proxy(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //前置增强 log.info("前置增强。。。。。。。。。。。。。"); return method.invoke(target, objects); } }); //3、调用代理 proxy.save(); proxy.save(1); proxy.save(2L); } }
-
运行结果
-
②、MethodProxy
-
MethodProxy
是方法的代理对象,有了它,我们就可以不利用反射就可以调用目标类的方法,原理后续研究。 -
这里我们先来研究
MethodProxy
如何创建。MethodProxy
类中有静态方法create
可以创建一个MethodProxy
对象 :public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2)
- 参数说明:
- c1是目标类对象
- c2是代理类对象
- desc是要增强方法的参数返回值描述符(写法 偏底层,使用java字节码描述方法的描述符)
- name1代理类中增强方法的方法名
- name2代理类中未被增强方法名
- 参数说明:
-
create
方法参数中第五个参数需要未被增强的原始方法名,所以我们要在代理类中,增加一组不增强的原始方法。-
以
save
方法为例//======================================不带增强逻辑的方法 原始方法======================= public void saveSuper() { super.save(); } //======================================带增强逻辑的方法 ======================= @Override public void save() { try { methodInterceptor.intercept(this, save0, new Object[0], null); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } }
-
以
save
方法为例创建MethodProxy
对象()V: ()表示方法参数为无参 V:表示方法的返回值 无返回值(void)
static MethodProxy save0Proxy; static { try { //()V: ()表示方法参数为无参 V:表示方法的返回值 无返回值(void) save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } }
-
我们将另外两个方法补齐,于是代理类被我们改造成了这样。
- save0Proxy = MethodProxy.create(Target.class, Proxy.class, “()V”, “save”, “saveSuper”);
- ()V: ()表示方法参数为无参 V:表示方法的返回值 无返回值(void)
- save1Proxy = MethodProxy.create(Target.class, Proxy.class, “(I)V”, “save”, “saveSuper”);
- (I)V: (I)表示方法参数为int V:表示方法的返回值 无返回值(void)
- save2Proxy = MethodProxy.create(Target.class, Proxy.class, “(J)V”, “save”, “saveSuper”);
- (J)V: (J)表示方法参数为long V:表示方法的返回值 无返回值(void)
package com.colin.a13.finalCode; import lombok.extern.slf4j.Slf4j; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; /** * 代理类 基础版本 * 模拟cglib生成的代理类, * 代理类是目标类的子类 */ @Slf4j public class Proxy extends Target { private MethodInterceptor methodInterceptor; public Proxy(MethodInterceptor methodInterceptor) { this.methodInterceptor = methodInterceptor; } /** * 定义方法对象 */ static Method save0; static Method save1; static Method save2; static MethodProxy save0Proxy; static MethodProxy save1Proxy; static MethodProxy save2Proxy; static { try { save0 = Target.class.getMethod("save"); save1 = Target.class.getMethod("save", int.class); save2 = Target.class.getMethod("save", long.class); //()V: ()表示方法参数为无参 V:表示方法的返回值 无返回值(void) save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper"); //(I)V: (I)表示方法参数为int V:表示方法的返回值 无返回值(void) save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper"); //(J)V: (J)表示方法参数为long V:表示方法的返回值 无返回值(void) save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } //======================================不带增强逻辑的方法 原始方法======================= public void saveSuper() { super.save(); } public void saveSuper(int i) { super.save(i); } public void saveSuper(long j) { super.save(j); } //======================================带增强逻辑的方法 ======================= @Override public void save() { try { methodInterceptor.intercept(this, save0, new Object[0], save0Proxy); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public void save(int i) { try { methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } @Override public void save(long j) { try { methodInterceptor.intercept(this, save2, new Object[]{j}, save2Proxy); } catch (Throwable e) { throw new UndeclaredThrowableException(e); } } }
-
运行测试
package com.colin.a13.finalCode; import lombok.extern.slf4j.Slf4j; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; @Slf4j public class A13 { public static void main(String[] args) { /** * 测试基础版本 */ //1、目标对象 Target target = new Target(); //2、代理类对象 Proxy proxy = new Proxy(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //前置增强 log.info("前置增强。。。。。。。。。。。。。"); //这是反射调用 //return method.invoke(target, objects); //使用methodProxy.invoke 避免反射调用 需要目标对象 //return methodProxy.invoke(target, objects); //使用methodProxy.invokeSuper 避免反射调用 需要代理对象 return methodProxy.invokeSuper(o, objects); } }); //3、调用代理 proxy.save(); proxy.save(1); proxy.save(2L); } }
-
效果和反射调用是一样的。
-
a14
、cglib 避免反射调用
项目源码:https://gitee.com/ZhongZaiMaYun/spring-senior.git
spring的工具类:
-
注解工具类AnnotationUtils
//获取某个类的某个注解 ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
-
工厂类
CachingMetadataReaderFactory
可以获取类的元信息String path = "classpath*:com/colin/a05/component/bean1.class"; Resource resource = context.getResource(path); CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); MetadataReader metadataReader = factory.getMetadataReader(resource); log.info("类名:{}", metadataReader.getClassMetadata().getClassName()); log.info("注解信息(是否加了Component):{}", metadataReader.getAnnotationMetadata().hasAnnotation(Component.class.getName())); log.info("注解信息(是否加了Component或者Component派生注解):{}", metadataReader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName()));
-
生成bean 的名字 使用spring提供的注解bean名字生成器AnnotationBeanNameGenerator
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(metadataReader.getClassMetadata().getClassName())
.getBeanDefinition();
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
String beanName = generator.generateBeanName(beanDefinition, context.getDefaultListableBeanFactory());