当前位置: 首页 > wzjs >正文

长沙做官方网站手机app开发编程自学

长沙做官方网站,手机app开发编程自学,广州十大高端网站建设公司,wordpress子页面怎么修改密码Spring 源码学习 2:Bean 后处理器 在前篇中介绍了,Spring 提供了一些 bean 的后处理器(实现了 BeanPostProsessor 接口),用于处理 bean 的依赖注入等。实际上,我们可以通过自定义一个实现了相应接口的后处…

Spring 源码学习 2:Bean 后处理器

在前篇中介绍了,Spring 提供了一些 bean 的后处理器(实现了 BeanPostProsessor 接口),用于处理 bean 的依赖注入等。实际上,我们可以通过自定义一个实现了相应接口的后处理器来观察 bean 的生命周期。

Bean 生命周期

创建一个用于打印不同生命周期方法钩子的 bean 后处理器:

static class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {@Overridepublic void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessBeforeDestruction() is called.");}@Overridepublic boolean requiresDestruction(Object bean) {return DestructionAwareBeanPostProcessor.super.requiresDestruction(bean);}@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessBeforeInstantiation() is called.");return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInstantiation(beanClass, beanName);}@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessAfterInstantiation() is called.");return InstantiationAwareBeanPostProcessor.super.postProcessAfterInstantiation(bean, beanName);}@Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessProperties() is called.");return InstantiationAwareBeanPostProcessor.super.postProcessProperties(pvs, bean, beanName);}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessBeforeInitialization() is called.");return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if ("myBean".equals(beanName))System.out.println("postProcessAfterInitialization() is called.");return InstantiationAwareBeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}
}

创建一个 Bean,在不同的阶段打印信息:

static class MyBean {private ApplicationContext applicationContext;public MyBean() {System.out.println("MyBean's constructor is called.");}@Autowiredpublic void setApplicationContext(ApplicationContext applicationContext) {System.out.println("MyBean's dependency injection method is called.");this.applicationContext = applicationContext;}@PostConstructpublic void postConstruct() {System.out.println("MyBean's postConstruct is called.");}@PreDestroypublic void preDestroy() {System.out.println("MyBean's preDestroy is called.");}
}

创建配置类,用 bean 方法添加这两个 bean:

@Configuration
static class Config {@BeanMyBeanPostProcessor myBeanPostProcessor() {return new MyBeanPostProcessor();}@BeanMyBean myBean() {return new MyBean();}
}

这里用测试类通过加载 Spring 上下文的方式进行测试,相关内容可以阅读这里。

输出:

postProcessBeforeInstantiation() is called.
MyBean's constructor is called.
postProcessAfterInstantiation() is called.
postProcessProperties() is called.
MyBean's dependency injection method is called.
postProcessBeforeInitialization() is called.
MyBean's postConstruct is called.
postProcessAfterInitialization() is called.
postProcessBeforeDestruction() is called.
MyBean's preDestroy is called.

可以看到执行顺序如下:

  1. postProcessBeforeInstantiation()钩子被调用。
  2. bean 工厂通过相应 bean 的构造器创建 bean 实例。
  3. postProcessAfterInstantiation()钩子被调用。
  4. postProcessProperties()钩子被调用。
  5. bean 工厂对实例进行依赖注入。
  6. postProcessBeforeInitialization()钩子被调用。
  7. bean 的postConstruct()方法被调用(通常在这里执行用户自定义初始化)。
  8. postProcessAfterInitialization钩子被调用。
  9. postProcessBeforeDestruction钩子被调用。
  10. bean 的preDestroy()方法被调用(通常在这里执行资源释放和环境清理)。

可以看到 bean 的生命周期主要包含这么几部分:

  • 实例化(通过构造器)
  • 依赖注入(属性注入或方法注入)
  • 初始化
  • 销毁

在相应的阶段前后,bean 后处理器中对应的生命周期钩子都会被调用。

模版方法模式

实际上这里应用了设计模式中的模版方法模式。

下面展示如何用模版方法模式实现一个在创建 Bean 时使用生命周期钩子的最简单的 Bean 工厂。

首先定义生命周期钩子接口:

interface LifeCycleHook {void beforeConstruct();<T> void afterConstruct(T instance);
}

定义自定义的 bean 工厂:

static class MyBeanFactory {private final List<LifeCycleHook> lifeCycleHooks = new ArrayList<>();public void add(LifeCycleHook lifeCycleHook) {lifeCycleHooks.add(lifeCycleHook);}public <T> T getBean(Class<T> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {Constructor<T> constructor = clazz.getConstructor();constructor.setAccessible(true);// 调用实例化前钩子for (LifeCycleHook lifeCycleHook : lifeCycleHooks) {lifeCycleHook.beforeConstruct();}// 实例化T newInstance = constructor.newInstance();// 调用实例化后钩子for (LifeCycleHook lifeCycleHook : lifeCycleHooks) {lifeCycleHook.afterConstruct(newInstance);}return newInstance;}
}

在 bean 工厂中,通过反射的方式获取构造器,并创建实例。为了简单起见,这里默认类型拥有一个无参构造器。

bean 工厂持有一个生命周期钩子列表,在实例化前后会依次执行生命周期钩子中的对应方法。

注解处理和工作原理

除了实现特定接口以介入 Bean 的生命周期外,Bean 的后处理器还能够处理 Bean 中的特殊注解。

虽然在前文提到过 Bean 的后处理器可以处理@Aurowired注解,但在这里还是需要再次说明和演示。

注解处理

有这样的类型:

static class Bean1 {
}static class Bean2 {
}@Getter
@ToString
static class Bean3 {private Bean1 bean1;private Bean2 bean2;private String javaHome;@Autowiredpublic void setBean1(Bean1 bean1) {System.out.println("setBean1() is called.");this.bean1 = bean1;}@Resourcepublic void setBean2(Bean2 bean2) {System.out.println("setBean2() is called.");this.bean2 = bean2;}@Autowiredpublic void setJavaHome(@Value("${JAVA_HOME}") String javaHome) {System.out.println("setJavaHome() is called.");this.javaHome = javaHome;}@PostConstructpublic void postConstruct() {System.out.println("postConstruct() is called.");}@PreDestroypublic void preDestroy() {System.out.println("preDestroy() is called.");}
}

Bean3中分别通过@Autowired@Resource注解用方法注入了两个属性,javaHome属性也是利用@Autowired方法注入,不同的是其来源不是 Bean,而是环境变量字符串,所以使用@Value注解。此外,还用两个方法是通过注解定义的 Bean 的生命周期钩子。

使用容器加载这些 Bean 定义并获取实例:

GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("bean1", Bean1.class);
context.registerBean("bean2", Bean2.class);
context.registerBean("bean3", Bean3.class);
// 调用工厂的后处理器,添加 bean 的后处理器,实例化 bean 等
context.refresh();
Bean3 bean3 = context.getBean(Bean3.class);
System.out.println(bean3);
// 关闭容器
context.close();

这里使用GenericApplicationContext而不是DefaultListableBeanFactory,原因在于前者会自动调用工厂的后处理器并注册 bean 后处理器。

执行代码可以看到,输出中不包括任何依赖注入方法调用或生命周期钩子调用。这表示容器并没有正确处理@Autowired@Resource注解。

@Autowired

我们已经知道,这些 Bean 的注解是由 Bean 的后处理器处理的,因此这里添加处理@Autowired注解的后处理器:

context.registerBean("bean3", Bean3.class);
// 添加处理 @Autowired 注解的后处理器
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
// 调用工厂的后处理器,添加 bean 的后处理器,实例化 bean 等
context.refresh();

执行代码会报错,因为这里有一个需要从环境变量解析的依赖注入,因此还需要为容器添加一个解析器:

// 添加处理 @Autowired 注解的后处理器
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
// 添加处理 @Value 注解的从环境变量、配置中解析字符串的解析器
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());

再执行就能观察到 @Autowired@Value注解已经被正确处理。

@Resource

现在还有@Resource注入和生命周期钩子注解没有被处理,同样的,加入相应的 Bean 后处理器:

// 添加处理 @Resource 注解和生命周期钩子注解的后处理器
context.registerBean(CommonAnnotationBeanPostProcessor.class);
// 调用工厂的后处理器,添加 bean 的后处理器,实例化 bean 等
context.refresh();

再执行就能看到所有依赖了注入和生命周期钩子都正常工作了。

@ConfigurationProperties

假设有一个类使用@ConfigurationProperties注解利用配置信息注入属性:

@ConfigurationProperties(prefix = "myapp")
@ToString
@Setter
static class Bean4{private String home;private String version;
}

配置文件:

myapp.home=D://home
myapp.version=21

要让这个注解生效,就需要使用新的后处理器:

// 添加处理 @ConfigurationProperties 注解的后处理器
ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());

添加方式略有不同,是因为除了添加 Bean 定义,这个注解处理器还需要绑定属性等。

现在执行不会得到想要的效果,这是因为容器并没有读取配置文件并加载配置信息。要手动加载:

// 1. 创建属性源
org.springframework.core.io.Resource resource = new ClassPathResource("application.properties");
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 2. 将属性添加到 Environment
MutablePropertySources propertySources = context.getEnvironment().getPropertySources();
propertySources.addFirst(new PropertiesPropertySource("appConfig", properties));
// 调用工厂的后处理器,添加 bean 的后处理器,实例化 bean 等
context.refresh();

工作原理

示例类型如下:

static class Bean1 {
}static class Bean2 {
}@ToString
static class Bean3 {@Autowiredprivate Bean1 bean1;private Bean2 bean2;private String javaHome;@Autowiredpublic void setBean2(Bean2 bean2) {this.bean2 = bean2;}@Autowiredpublic void setJavaHome(@Value("${JAVA_HOME}") String javaHome) {this.javaHome = javaHome;}
}

Bean3中分别通过@Autowired注解进行了属性注入、方法注入和用环境变量方法注入。

下面展示实现这些注入的原理。

首先,看最外层的后处理器调用实现:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("bean1", new Bean1());
beanFactory.registerSingleton("bean2", new Bean2());
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
AutowiredAnnotationBeanPostProcessor beanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
// 关联 bean 工厂
Bean3 bean3 = new Bean3();
beanPostProcessor.setBeanFactory(beanFactory);
// 模拟 bean 的后处理器调用
beanPostProcessor.postProcessProperties(null, bean3, "bean3");
System.out.println(bean3);

输出结果中的环境变量表达式没有被正确处理(javaHome=${JAVA_HOME}),因此需要添加一个解析器:

beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
// 添加对`${...}`表达式的解析器
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

查看源码会发现,postProcessProperties方法中主要调用了一个私有方法:

image-20250620164622998

其,返回类型为InjectionMetadata,这个方法的用途是检索目标 Bean 定义,将查找到的需要注入的属性/方法记录到InjectionMetadata对象中:

image-20250620164810318

可以通过反射模拟该方法调用:

// 模拟 bean 的后处理器调用
Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
findAutowiringMetadata.setAccessible(true);
InjectionMetadata metadata = (InjectionMetadata)findAutowiringMetadata.invoke(beanPostProcessor, "bean3", Bean3.class, null);
metadata.inject(bean3, "bean3", null);
System.out.println(bean3);

现在已经知道哪些地方需要注入了,就可以通过反射调用来模拟注入过程。

模拟字段注入:

// 模拟属性注入
Field bean1Field = Bean3.class.getDeclaredField("bean1");
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(bean1Field, true);
Bean1 bean1 = (Bean1) beanFactory.doResolveDependency(dependencyDescriptor, null, null, null);
bean1Field.set(bean3, bean1);

模拟方法注入:

// 模拟方法注入
Method setBean2 = Bean3.class.getDeclaredMethod("setBean2", Bean2.class);
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(new MethodParameter(setBean2, 0), true);
Bean2 bean2 = (Bean2) beanFactory.doResolveDependency(dependencyDescriptor, null, null, null);
setBean2.invoke(bean3, bean2);

模拟方法注入环境变量:

// 模拟 方法注入 Value
Method setJavaHome = Bean3.class.getDeclaredMethod("setJavaHome", String.class);
String javaHome = (String) beanFactory.doResolveDependency(new DependencyDescriptor(new MethodParameter(setJavaHome, 0), true), null, null, null);
setJavaHome.invoke(bean3, javaHome);

本文的完整示例代码可以从这里获取。

The End.


文章转载自:

http://LCjqnMaF.zqdhr.cn
http://UKHAae3v.zqdhr.cn
http://aLh6GUgx.zqdhr.cn
http://z0k3v7br.zqdhr.cn
http://zGLhGqxA.zqdhr.cn
http://bPnZVE2c.zqdhr.cn
http://eGrF33UJ.zqdhr.cn
http://NmSPUYNv.zqdhr.cn
http://CmmrodFh.zqdhr.cn
http://7CGGeCXF.zqdhr.cn
http://SaYArKHt.zqdhr.cn
http://3YSzYLVA.zqdhr.cn
http://MxcYCzsx.zqdhr.cn
http://9GQrwbKH.zqdhr.cn
http://SeoO9jsE.zqdhr.cn
http://cWtepuDv.zqdhr.cn
http://P7cN2zca.zqdhr.cn
http://FBr9pn6h.zqdhr.cn
http://EYwoHE0n.zqdhr.cn
http://0ziCza0l.zqdhr.cn
http://M2NIX0WB.zqdhr.cn
http://nIwag7Tn.zqdhr.cn
http://HxQ0iHFA.zqdhr.cn
http://GHWFCuwP.zqdhr.cn
http://pAPsM7Gg.zqdhr.cn
http://EEFKdTCX.zqdhr.cn
http://yfEUOXWJ.zqdhr.cn
http://QgIIDi2W.zqdhr.cn
http://8mKkd8zr.zqdhr.cn
http://2RrAtoQQ.zqdhr.cn
http://www.dtcms.com/wzjs/617859.html

相关文章:

  • 广州网站建设业务血液中心网站建设规范
  • 婚庆设计图网站中国纪检监察网官网
  • 外汇交易网站开发有没有做文创的网站
  • 专做民宿的网站请人做网站要注意什么
  • 深圳网站建设服务中心一级a做爰片免费网站 新闻
  • 免费的建网站软件北京所有做招聘类网站建站公司
  • 兼职做问卷调查的网站万维网网站续费
  • 织梦网站栏目添加外链发布平台大全
  • 在线做头像的网站vi设计经典案例
  • 中学生旅游网站开发的论文怎么写网站建设公司网站模版
  • 湖南营销网站建设网络营销题库案例题
  • 做编程的网站一个月多少钱杭州 高端网站建设
  • 织梦网站添加广告位哈尔滨高端网站建设
  • 网站建设翻译手机网站怎么制作
  • 网站开发外包wordpress语言文件编辑
  • 盐城网站推广电话网络营销具有哪些优势和吸引力
  • 建湖做网站哪家公司好方城网站制作
  • 网站怎么做微信接口福州最新消息
  • 怎么打帮人 做网站开发的广告哪个建站平台较好
  • 类似微薄利网站怎么做云南省公共资源交易中心官网
  • 旺苍网站建设18款未成年软件入口
  • 网站怎么做镜像网站制作
  • 官方网站建设流程及费用爱吧
  • 企业网站备案管理系统常州建设网站
  • 马鞍山市网站建设简洁的企业网站源码
  • 免费的站内推广方式有哪些专业的佛山网站设计
  • 贵阳建设职业技术学院招聘信息网站成都装修公司口碑最好的是哪家
  • 济南市做网站公司python做网页
  • 龙岗永湖网站建设如何打开wordpress
  • 棠下手机网站建设电话电脑课程培训零基础