实现Spring底层机制-03

7.实现任务阶段5

7.1分析

阶段5目标:bean后置处理器的实现

7.2代码实现

新增:

1.创建 InitializingBean 接口,实现该接口的 Bean 需要实现 Bean 的初始化方法

bean后置处理器的两个方法,调用时机分别在 Bean 初始化方法的前后。因此要实现bean后置处理器,首先要实现 Bean 的初始化方法。

可以参考原生 Spring 规范来定义这个接口

package com.li.spring.processor;

/**
* @author 李
* @version 1.0
* 说明:
* 1.根据 spring 原生机制定义了一个接口
* 2.该接口有一个方法 afterPropertiesSet(),
* 3.afterPropertiesSet() 在 bean的 setter方法后执行,相当于原生的spring的 bean初始化方法
* 4.当一个Bean实现了这个接口后,就要实现afterPropertiesSet(),即 bean的初始化方法
*/
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}

2.为了测试,在MonsterService中实现该接口,并实现该方法,作为MonsterService的初始化方法

注:其他注解及自动装配的实现详见上一篇

package com.li.spring.component;

import com.li.spring.annotation.AutoWired;
import com.li.spring.annotation.Component;
import com.li.spring.annotation.Scope;
import com.li.spring.processor.InitializingBean; /**
* @author 李
* @version 1.0
* MonsterService 是一个 Service
* 1.如果指定了value,那么在注入spring容器时,以你指定的为准
* 2.如果没有指定value,则使用类名(首字母小写)作为默认名
*/
@Component//(value = "monsterService") //将 MonsterService注入到自己的spring容器中
@Scope(value = "prototype")
public class MonsterService implements InitializingBean {
//使用自定义注解修饰属性,表示该属性通过容器完成依赖注入
// (说明:按照名字进行组装)
@AutoWired(required = true)
private MonsterDao monsterDao; public void m1() {
monsterDao.hi();
} /**
* 1.afterPropertiesSet()就是在bean的setter方法执行完毕之后,被spring容器调用
* 2.即 bean的初始化方法
*
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("MonsterService 初始化方法被调用");
}
}

3.修改MySpringApplicationContext.java,添加逻辑:在创建后bean实例后,判断是否需要进行初始化。

容器中常用的一个方法是,根据该类是否实现了某个接口,来判断该类是否要执行某个业务逻辑。这个接口被称为标记接口。

部分代码:

//完成createBean(BeanDefinition)方法
public Object createBean(BeanDefinition beanDefinition) {
//得到Bean的class对象
Class clazz = beanDefinition.getClazz();
try {
//反射创建bean实例
Object instance = clazz.getDeclaredConstructor().newInstance();
//todo 这里要加入依赖注入的业务逻辑
//1.遍历当前要创建对象的所有属性字段
for (Field declaredField : clazz.getDeclaredFields()) {
//2.判断字段是否有AutoWired注解
if (declaredField.isAnnotationPresent(AutoWired.class)) {
//判断是否需要自动装配
AutoWired autoWiredAnnotation =
declaredField.getAnnotation(AutoWired.class);
if (autoWiredAnnotation.required()) {
//3.得到字段的名称
String name = declaredField.getName();
//4.通过getBean()方法获取要组装的对象
//如果name对应的对象时单例的,就到单例池去获取,如果是多例的,就反射创建并返回
Object bean = getBean(name);
//5.进行组装
//暴破
declaredField.setAccessible(true);
declaredField.set(instance, bean);
}
}
}
System.out.println("======已创建好实例=====" + instance); //todo 判断是否要执行bean的初始化方法
// 1.判断当前创建的 bean对象是否实现了 InitializingBean
// 2.instanceof 表示判断对象的运行类型 是不是 某个类型或某个类型的子类型
if (instance instanceof InitializingBean) {
//3.将instance转成接口类型,调用初始化方法
try {
((InitializingBean) instance).afterPropertiesSet();
} catch (Exception e) {
e.printStackTrace();
}
}
return instance;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
//如果反射对象失败
return null;
}

4.测试类

//...
public class AppMain {
public static void main(String[] args) {
MySpringApplicationContext ioc =
new MySpringApplicationContext(MySpringConfig.class);
MonsterService monsterService = (MonsterService) ioc.getBean("monsterService");
}
}

测试结果:MonsterService的初始化方法成功被调用。

5.创建 BeanPostProcessor 接口

package com.li.spring.processor;

/**
* @author 李
* @version 1.0
* 说明:
* 1.此接口参考原生Spring容器定义
* 2.该接口有两个方法 postProcessBeforeInitialization和 postProcessAfterInitialization
* 3.这两个方法会对spring容器的所有bean生效(切面编程)
*/
public interface BeanPostProcessor { /**
* postProcessBeforeInitialization方法在bean的初始化方法前 调用
*
* @param bean
* @param beanName
* @return
*/
default Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
} /**
* postProcessAfterInitialization方法在bean的初始化方法后 调用
*
* @param bean
* @param beanName
* @return
*/
default Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}

6.为了测试,自定义MyBeanPostProcessor实现上述接口。

package com.li.spring.component;

import com.li.spring.annotation.Component;
import com.li.spring.processor.BeanPostProcessor; /**
* @author 李
* @version 1.0
* 1.这是我们自己的一个后置处理器,它实现了我们自定义的 BeanPostProcessor
* 2.通过重写 BeanPostProcessor的方法实现相应业务
* 3.仍然将 MyBeanPostProcessor 看做一个Bean对象,使用Component注解,注入到spring容器中。
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("后置处理器MyBeanPostProcessor的 Before()方法被调用,bean类型="
+ bean.getClass() + ",bean的名字=" + beanName);
return bean;
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("后置处理器MyBeanPostProcessor的 After()方法被调用,bean类型="
+ bean.getClass() + ",bean的名字=" + beanName);
return bean;
}
}

7.修改 MySpringApplicationContext.java(模拟原生的ioc)

后置处理器和普通bean的创建顺序问题:

因为后置处理器要对容器中的所有bean对象都生效。因此后置处理器的bean对象的创建,要比普通的bean对象创建的时机靠前。

为了解决这个问题,我们将后置处理器对象的反射创建,提前到扫描包的时候完成(因为ioc容器初始化时,扫描工作要比反射对象的工作靠前)。

在原生的spring容器中,对后置处理器还是走的getBean,createBean方法。但如果这样做,需要在singletonObjects中加入相应业务逻辑,这里为了方便,我们简化处理了

然后将创建的后置处理器对象放到一个单独的集合来调用。

部分代码:

//该方法完成对指定包的扫描,并将Bean信息封装到BeanDefinition对象,再放入map中
public void beanDefinitionByScan(Class configClass) {
//步骤一:获取要扫描的包
//... //步骤二:得到要扫描的包下的所有资源(类.class)
//... //步骤三:获取全类名反射对象,并放入容器中
//...
//判断该类是否有特定注解
if (clazz.isAnnotationPresent(Component.class)) {
//如果该类使用了 @Component ,说明是spring bean
System.out.println("是一个spring bean=" + clazz + " 类名=" + className);
//--------------------新增代码------------------------
/*
为了方便,如果发现是一个后置处理器,将其放入到ArrayList集合中
1.在原生的spring容器中,对后置处理器还是走的getBean,createBean方法
2.如果这样做,需要在singletonObjects中加入相应业务逻辑
3.这里为了方便,我们简化处理
*/
//判断当前class有没有实现接口
//注意这里我们不能通过 instanceof 判断 class是否实现了 BeanPostProcessor
//因为clazz不是一个实例对象,而是一个Class对象,需要如下判断:
if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
BeanPostProcessor beanPostProcessor =
(BeanPostProcessor)clazz.newInstance();
//放入ArrayList集合中
beanPostProcessorList.add(beanPostProcessor);
continue;//简化处理,不再将后置处理器的bean信息放到beanDefinition对象中
}
//--------------------新增代码------------------------ //得到 BeanName-key
//... //将 Bean信息封装到 BeanDefinition对象-value
//... //将beanDefinition对象放入Map中
//...
} else {
//如果没有使用,则说明不是spring bean
System.out.println("不是一个 spring bean=" + clazz + " 类名=" + className);
}
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("===========================");
}}} //完成createBean(BeanDefinition)方法
public Object createBean(String beanName, BeanDefinition beanDefinition) {
//得到Bean的class对象
Class clazz = beanDefinition.getClazz();
try {
//反射创建bean实例
Object instance = clazz.getDeclaredConstructor().newInstance();
// 这里要加入依赖注入的业务逻辑
//...
System.out.println("======已创建好实例=====" + instance); //--------------------新增代码start------------------------
//在bean的初始化方法前调用后置处理器的before方法
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
//在后置处理器的before方法,可以对容器的bean进行处理
// 然后返回处理后的bean实例。相当于做了前置处理
Object current = beanPostProcessor.
postProcessBeforeInitialization(instance, beanName);
//如果current为null,就不对instance做处理
if (current != null) {
instance = current;
}
}
//--------------------新增代码end------------------------ //判断是否要执行bean的初始化方法
// 1.判断当前创建的 bean对象是否实现了 InitializingBean
// 2.instanceof 表示判断对象的运行类型 是不是 某个类型或某个类型的子类型
if (instance instanceof InitializingBean) {
//3.将instance转成接口类型,调用初始化方法
try {
((InitializingBean) instance).afterPropertiesSet();
} catch (Exception e) {
e.printStackTrace();
}
} //--------------------新增代码start------------------------
// 在bean的初始化方法后调用后置处理器的 after方法
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
//在后置处理器的 after方法,可以对容器的 bean进行处理
// 然后返回处理后的bean实例。相当于做了后置处理
Object current = beanPostProcessor.
postProcessAfterInitialization(instance, beanName);
if (current != null) {
instance = current;
}
}
//--------------------新增代码end------------------------ return instance;
} catch (InstantiationException e) {
//...
}
//如果反射对象失败
return null;
}

8.测试类

//...
public class AppMain {
public static void main(String[] args) {
MySpringApplicationContext ioc =
new MySpringApplicationContext(MySpringConfig.class);
MonsterService monsterService = (MonsterService) ioc.getBean("monsterService");
monsterService.m1();
}
}

测试结果:成功在所有bean对象的初始化方法前后,调用后置处理器的方法(无论bean对象有无初始化方法)

后置处理器是对容器的所有bean生效,因此相当于可以对多个对象编程,即切面编程。日志,身份,权限,事务……都可以在后置处理器进行处理。

8.实现任务阶段6

8.1分析

阶段6目标:实现自己的AOP机制

在原生的Spring中,如果我们同时配置了后置处理器和切面类,并在后置处理器的两个方法中输出bean的运行类型。你会发现,在后置处理器的before...()方法输出bean的类型时,bean对象还是原生类型;但在后置处理器的after...()方法输出时,bean对象已经被封装成了代理对象。

当然,变化的只是aop的目标方法对应的bean对象

因此,我们需要编写方法,在后置处理器的after方法中,将原生的bean对象封装成代理对象并返回。在代理对象中切入要执行的前置/返回/异常/最终通知等。

8.2代码实现

1.为了测试,创建接口和实现类,模拟aop的需求

(1)SmartAnimal 接口

package com.li.spring.component;

/**
* @author 李
* @version 1.0
*/
public interface SmartAnimal {
public float getSum(float i, float j); public float getSub(float i, float j);
}

(2)SmartDog 实现类

package com.li.spring.component;

import com.li.spring.annotation.Component;

/**
* @author 李
* @version 1.0
*/
@Component(value = "smartDog")
public class SmartDog implements SmartAnimal {
@Override
public float getSum(float i, float j) {
float res = i + j;
System.out.println("SmartDog-getSum()-res=" + res);
return res;
} @Override
public float getSub(float i, float j) {
float res = i - j;
System.out.println("SmartDog-getSub()-res=" + res);
return res;
}
}

2.模拟切面类

原生的切面类有@Aspect,@Before,@AfterReturning,@AfterThrowing,@After等注解。

这里为了简化,先将SmartAnimalAspect 当做一个“切面类”来使用,后面再分析如何做得更加灵活

package com.li.spring.component;

/**
* @author 李
* @version 1.0
* 先将SmartAnimalAspect 当做一个切面类来使用
* 后面再分析如何做得更加灵活
*/
public class SmartAnimalAspect {
public static void showBeginLog() {
System.out.println("前置通知...");
} public static void showSuccessLog() {
System.out.println("返回通知...");
}
}

3.修改配置的后置处理器 MyBeanPostProcessor

在实现后置处理器时,调用createBean()方法反射bean对象后,会调用后置处理器的两个方法。因此,我们可以在postProcessAfterInitialization()方法中判断当前bean是否要返回代理对象,并进行处理。

部分代码:

package com.li.spring.component;

import com.li.spring.annotation.Component;
import com.li.spring.processor.BeanPostProcessor; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; /**
* @author 李
* @version 1.0
* 这是我们自己的一个后置处理器,它实现了我们自定义的 BeanPostProcessor
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
//...
//...
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("后置处理器MyBeanPostProcessor的 After()方法被调用,bean类型="
+ bean.getClass() + ",bean的名字=" + beanName);
//实现aop,返回代理对象,即对bean进行包装
//先写死,后面我们可以通过注解的方式更加灵活运用
if ("smartDog".equals(beanName)) {
//使用jdk的动态代理,返回bean的代理对象
Object proxyInstance = Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(),
bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method=" + method.getName());
Object result = null;
//假设我们要进行前置,返回通知处理的方法是getSum()
//(原生的spring的通知方法是通过注解来获取的)
//这里我们后面再通过注解来做得更加灵活
if ("getSum".equals(method.getName())) {
//前置通知
SmartAnimalAspect.showBeginLog();
//目标方法
result = method.invoke(bean, args);
//返回通知
SmartAnimalAspect.showSuccessLog();
} else {
result = method.invoke(bean, args);
}
return result;
}
});
//如果bean需要返回代理对象,这里就直接return ProxyInstance
return proxyInstance;
}
//如果不需要AOP,直接返回 bean
return bean;
}
}

4.测试类

//...
public class AppMain {
public static void main(String[] args) {
MySpringApplicationContext ioc =
new MySpringApplicationContext(MySpringConfig.class);
SmartAnimal smartDog = (SmartAnimal) ioc.getBean("smartDog");
System.out.println("smartDog类型=" + smartDog.getClass());
smartDog.getSum(100, 20);
}
}

测试结果:成功通过后置处理器的 postProcessAfterInitialization 方法,对bean对象进行包装返回,同时通过invoke方法,插入前置通知和后置通知的方法。

拓展:如何做得更加灵活?

  1. 前面我们使用的硬编码,不灵活,但是aop的核心机制差不多如此。
  2. 如果要把aop做得更加灵活,需要实现一点其他的逻辑(注解+数据结构map+切入表达式,其实和aop机制关系不大了)

day13-实现Spring底层机制-03的更多相关文章

  1. Hibernate工作原理及为什么要用?. Struts工作机制?为什么要使用Struts? spring工作机制及为什么要用?

    三大框架是用来开发web应用程序中使用的.Struts:基于MVC的充当了其中的试图层和控制器Hibernate:做持久化的,对JDBC轻量级的封装,使得我们能过面向对象的操作数据库Spring: 采 ...

  2. spring工作机制及为什么要用?

    spring工作机制及为什么要用?1.spring mvc请所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求进行真正的处理工作.2.DispatcherSer ...

  3. 如何妙用Spring 数据绑定机制?

    前言 在剖析完 「Spring Boot 统一数据格式是怎么实现的? 」文章之后,一直觉得有必要说明一下 Spring's Data Binding Mechanism 「Spring 数据绑定机制」 ...

  4. Spring 事务机制详解

    原文出处: 陶邦仁 Spring事务机制主要包括声明式事务和编程式事务,此处侧重讲解声明式事务,编程式事务在实际开发中得不到广泛使用,仅供学习参考. Spring声明式事务让我们从复杂的事务处理中得到 ...

  5. spring工作机制

    Hibernate.struts,还差一个spring 就一起发出去.. spring工作机制及为什么要用? 1.springmvc请所有的请求都提交给DispatcherServlet,它会委托应用 ...

  6. [转]STL 容器一些底层机制

    1.vector 容器 vector 的数据安排以及操作方式,与 array 非常相似.两者的唯一区别在于空间的运用的灵活性.array 是静态空间,一旦配置了就不能改变,vector 是动态数组.在 ...

  7. Spring 事务机制详解(事务的隔离性和传播性)

    原文出处: 陶邦仁 Spring事务机制主要包括声明式事务和编程式事务,此处侧重讲解声明式事务,编程式事务在实际开发中得不到广泛使用,仅供学习参考. Spring声明式事务让我们从复杂的事务处理中得到 ...

  8. Spring事件机制详解

    一.前言 说来惭愧,对应Spring事件机制之前只知道实现 ApplicationListener 接口,就可以基于Spring自带的事件做一些事情(如ContextRefreshedEvent),但 ...

  9. C++ STL容器底层机制

    1.vector容器 vector的数据安排以及操作方式,与array非常相似.两者的唯一区别在于空间的运用的灵活性.array是静态空间,一旦配置了就不能改变.vector是动态空间,随着元素的加入 ...

  10. 探索C++的底层机制

    探索C++的底层机制 在看这篇文章之前,请你先要明白一点:那就是c++为我们所提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说是编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么 ...

随机推荐

  1. Microsoft Office for Mac最新版本安装教程,亲测可用!!!

    办公必备的Office工具,它为需要使用 Office 工具的用户和企业设计,让他们可以利用功能强大的 Outlook 来处理电子邮件.日历和通讯录事宜.你所熟知和信赖的 Office 经过更新后,可 ...

  2. LVS综合实验

    LVS综合实验 1.环境准备 提前准备:Mysql8.0.30安装包.Mysql安装脚本.shopxo2.3.0安装包.DNS脚本 服务器 IP地址 作用 系统版本 Mysql-master 10.0 ...

  3. 野火 STM32MP157 开发板内核和设备树的编译烧写

    一.环境 编译环境:Ubuntu 版本:18.4.6 交叉编译工具:arm-linux-gnueabihf-gcc 版本:7.4.1 开发板:STM32MP157 pro 烧写方式:STM32Cube ...

  4. pinpoint部署

    pinpoint是一个分析大型分布式系统的平台,提供解决方案来处理海量跟踪数据,主要面向基于tomcat的Java 应用. pinpoint使用HBASE储存数据. 下面介绍pinpoint部署及应用 ...

  5. Kubernetes核心技术-Controller

    Kubernetes核心技术-Controller 内容 什么是Controller Pod和Controller的关系 Deployment控制器应用场景Deployment控制器应用 yaml文件 ...

  6. Java:ArrayList的基本使用(学习笔记)

    ​ 集合和数组的对比(为什么要有集合) 分为俩点 1. 长度:数组的长度是固定的,集合的长度是可变的. 2. 存储类型: 数组:可以存储基本数据类型,引用数据类型. 集合:只能存储引用数据类型. 小t ...

  7. Huawei OJ 题解 - 31. 整数拆分 - Go 参考解答

    ## 简介- 详情:http://oj.rnd.huawei.com/problems/31/details- 难度:简单## 思路TODO## 用例TODO## 解答```gopackage mai ...

  8. 【Java并发001】使用级别:线程相关知识

    一.前言 本文介绍Java线程相关知识(不包括线程同步+线程通信,这个内容在笔者的另一篇博客中介绍过了),包括:线程生命周期.线程优先级.线程礼让.后台线程.联合线程. 二.线程生命周期 2.1 引子 ...

  9. phpword 模板文件导出word到服务器 并浏览器下载

    模板文件填充 然后生成新文件 //调用PHPwordrequire_once(ROOTPATH . "inc/vendor/autoload.php"); $phpWord = n ...

  10. 关于urllib.request解析网站不能decode

    原因 不能decode,无论以gbk还utf8都无法正常解码,这个原因是因为 网页被gzip压缩了,需要解压缩 解决办法 import urllib.request import gzip url = ...