实现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. 驱动开发:内核监控Register注册表回调

    在笔者前一篇文章<驱动开发:内核枚举Registry注册表回调>中实现了对注册表的枚举,本章将实现对注册表的监控,不同于32位系统在64位系统中,微软为我们提供了两个针对注册表的专用内核监 ...

  2. 长文梳理muduo网络库核心代码、剖析优秀编程细节

    前言 muduo库是陈硕个人开发的tcp网络编程库,支持Reactor模型,推荐大家阅读陈硕写的<Linux多线程服务端编程:使用muduo C++网络库>.本人前段时间出于个人学习.找工 ...

  3. JS逆向实战3——AESCBC 模式解密

    爬取某省公共资源交易中心 通过抓包数据可知 这个data是我们所需要的数据,但是已经通过加密隐藏起来了 分析 首先这是个json文件,我们可以用请求参数一个一个搜 但是由于我们已经知道了这是个json ...

  4. CH58X/CH57X/V208的Broadcaster(广播者)例程讲解

    在对ble进行应用的时候,每个用户的需求可能不尽相同.这里着重介绍从机Broadcaster例程,只广播不连接. 使用该例程时可以在手机使用APP上对Broadcaster进行调试. 安卓端在应用市场 ...

  5. elasticsearch聚合之bucket terms聚合

    目录 1. 背景 2. 前置条件 2.1 创建索引 2.2 准备数据 3. 各种聚合 3.1 统计人数最多的2个省 3.1.1 dsl 3.1.2 运行结果 3.2 统计人数最少的2个省 3.2.1 ...

  6. GAC简述

    GAC简介 GAC全称是Global Assembly Cache作用是可以存放一些有很多程序都要用到的公共Assembly,例如System.Data.System.Windows.Forms等等. ...

  7. Perl引用

    引用就是C语言中的指针,perl引用是一个标量类型可以指向变量.数组.哈希表(也叫关联数组)甚至子程序,可以应用在程序的任何地方. 在变量前面加一个\就得到了这个变量的一个引用 #!usr/bin/p ...

  8. Xtrabackup使用帮助

    目录 1.安装工具 2.下载后上传到需要备份的服务器 全备 1.安装完成后我们进行数据库备份执行以下命令 2.查看备份的数据 3.进入数据库,删除一个测试库 4.删除school库 5.备份数据目录 ...

  9. 本地GoLand编辑与调试远端服务器上的代码

    Goland是专为Go开发人员构建的跨平台IDE,功能非常强大,拥有强大的代码洞察力,帮助所有Go开发人员即时错误检测和修复建议,快速和安全的重构,一步撤销,智能代码完成,死代码检测和文档提示,让您创 ...

  10. Web安全Day1 - SQL注入、漏洞类型

    Web安全Day1 - SQL注入.漏洞类型 1. SQL注入 1.1 漏洞简介 1.2 漏洞原理 1.3 漏洞危害 2. SQL漏洞类型 2.1 区分数字和字符串 2.2 内联SQL注入 2.3 报 ...