前言

上篇 实现了 判断一个类的方式是符合配置的 pointcut 表达式、根据一个 Bean 的名称和方法名,获取 Method 对象、实现了 BeforeAdvice、AfterReturningAdvice 以及 AfterThrowingAdvice并按照指定次序调用 等功能,这篇再来看看剩下的 代理对象如何生成根据 XML 配置文件生成 BeanDefintion以及如何将生成的代理对象放入到容器中 等功能,话不多说,下面进入主题。

代理对象生成

代理对象的生成策略和 Spring 框架一致,当被代理类实现了接口时采用 JDK 动态代理的方式生成代理对象,被代理对象未实现接口时使用 CGLIB 来生成代理对象,为了简单起见这里不支持手动指定生成代理对象的策略,JDK 动态代理的实现这里不在介绍,感兴趣可以自己实现一下,这里主要讨论 CGLIB 的生成方式。

基于面向接口编程的思想,这里的生成代理对象需要定义一个统一的接口,不管是 CGLIB 生成方式还是JDK 动态代理生成方式都要实现该接口。生成代理对象是根据一些配置去生成的,同样,这里生成代理的配置也可以抽取一个统一的接口,在实现类中定义拦截器(也就是 Advice)以及实现的接口等,CGLIB 的基本使用可以到官网自行查找。代理对象生成的整体的类图如下:

其中代理创建的工厂接口 AopProxyFactory 如下,提供了不指定 ClassLoader(使用默认的 ClassLoader)和指定 ClassLoader 两种方式创建代理对象,源码如下:

/**
* @author mghio
* @since 2021-06-13
*/
public interface AopProxyFactory { Object getProxy(); Object getProxy(ClassLoader classLoader); }

使用 CGLIB 创建代理的工厂接口实现类如下所示:

/**
* @author mghio
* @since 2021-06-13
*/
public class CglibProxyFactory implements AopProxyFactory { /*
* Constants for CGLIB callback array indices
*/
private static final int AOP_PROXY = 0; protected final Advised advised; public CglibProxyFactory(Advised config) {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvices().size() == 0) {
throw new AopConfigException("No advisors and no TargetSource specified");
} this.advised = config;
} @Override
public Object getProxy() {
return getProxy(null);
} @Override
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating CGLIB proxy: target class is " + this.advised.getTargetClass());
} try {
Class<?> rootClass = this.advised.getTargetClass(); // Configure CGLIB Enhancer...
Enhancer enhancer = new Enhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
}
enhancer.setSuperclass(rootClass);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); // BySpringCGLIB
enhancer.setInterceptDuringConstruction(false); Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int i = 0; i < types.length; i++) {
types[i] = callbacks[i].getClass();
}
enhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised));
enhancer.setCallbackTypes(types);
enhancer.setCallbacks(callbacks); // Generate the proxy class and create a proxy instance.
return enhancer.create();
}
catch (CodeGenerationException | IllegalArgumentException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of class [" +
this.advised.getTargetClass() + "]: " +
"Common causes of this problem include using a final class or a non-visible class",
ex);
} catch (Exception ex) {
// TargetSource.getTarget() failed
throw new AopConfigException("Unexpected AOP exception", ex);
}
} // omit other methods ... }

整体来看还是比较简单的,主要是 CGLIB 第三方字节码生成库的基本用法,当然,前提是你已经了解了 CGLIB 的基本使用。AOP 的相关配置接口 Advised 相对来说就比较简单了,主要是一些相关属性的增、删、改等操作,主要部分代码如下:

/**
* @author mghio
* @since 2021-06-13
*/
public interface Advised { Class<?> getTargetClass(); boolean isInterfaceProxied(Class<?> intf); List<Advice> getAdvices(); void addAdvice(Advice advice); List<Advice> getAdvices(Method method); void addInterface(Class<?> clazz); // omit other methods ... }

实现类也比较简单,代码如下:

/**
* @author mghio
* @since 2021-06-13
*/
public class AdvisedSupport implements Advised { private boolean proxyTargetClass = false;
private Object targetObject = null;
private final List<Advice> advices = new ArrayList<>();
private final List<Class<?>> interfaces = new ArrayList<>(); public AdvisedSupport() {
} @Override
public Class<?> getTargetClass() {
return this.targetObject.getClass();
} @Override
public boolean isInterfaceProxied(Class<?> intf) {
return interfaces.contains(intf);
} @Override
public List<Advice> getAdvices() {
return this.advices;
} @Override
public void addAdvice(Advice advice) {
this.advices.add(advice);
} @Override
public List<Advice> getAdvices(Method method) {
List<Advice> result = new ArrayList<>();
for (Advice advice : this.getAdvices()) {
Pointcut pc = advice.getPointcut();
if (pc.getMethodMatcher().matches(method)) {
result.add(advice);
}
}
return result;
} @Override
public void addInterface(Class<?> clazz) {
this.interfaces.add(clazz);
} // omit other methods ... }

到这里,代理对象使用 CGLIB 生成的方式就已经实现了,核心代码其实比较简单,主要是需要多考虑考虑代码后期的扩展性。

创建 BeanDefinition

我们先来看看一般 AOP 在 XML 配置文件中是如何定义的,一个包含 BeforeAdvice、AfterReturningAdvice以及AfterThrowingAdvice 的 XML 配置文件如下:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.e3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/beans/spring-context.xsd"> <context:scann-package base-package="cn.mghio.service.version5,cn.mghio.dao.version5" /> <bean id="tx" class="cn.mghio.tx.TransactionManager"/> <aop:config>
<aop:aspect ref="tx">
<aop:pointcut id="placeOrder" expression="execution(* cn.mghio.service.version5.*.placeOrder(..))"/>
<aop:before pointcut-ref="placeOrder" method="start"/>
<aop:after-returning pointcut-ref="placeOrder" method="commit"/>
<aop:after-throwing pointcut-ref="placeOrder" method="rollback"/>
</aop:aspect>
</aop:config>
</beans>

有了之前解析 XML 的 Bean 定义的经验后,很显然这里我们需要一个数据结构去表示这个 AOP 配置,如果你阅读过 上篇 的话,类 AspectJExpressionPointcut 表示的是 <aop:pointcut id="placeOrder" expression="execution(* cn.mghio.service.version5.*.placeOrder(..))"/>,另外几个 Advice 配置分别对应 AspectJBeforeAdvice、AspectJAfterReturningAdvice以及 AspectJAfterThrowingAdvice 等几个类。

这里只要解析 XML 配置文件,然后使用对应的 Advice 的构造器创建对应的对象即可,解析 XML 使用的是 dom4j,主要部分代码如下所示:

/**
* @author mghio
* @since 2021-06-13
*/
public class ConfigBeanDefinitionParser { private static final String ASPECT = "aspect";
private static final String EXPRESSION = "expression";
private static final String ID = "id";
private static final String REF = "ref";
private static final String BEFORE = "before";
private static final String AFTER = "after";
private static final String AFTER_RETURNING_ELEMENT = "after-returning";
private static final String AFTER_THROWING_ELEMENT = "after-throwing";
private static final String AROUND = "around";
private static final String POINTCUT = "pointcut";
private static final String POINTCUT_REF = "pointcut-ref";
private static final String ASPECT_NAME_PROPERTY = "aspectName"; public void parse(Element element, BeanDefinitionRegistry registry) {
List<Element> childElements = element.elements();
for (Element el : childElements) {
String localName = el.getName();
if (ASPECT.equals(localName)) {
parseAspect(el, registry);
}
}
} private void parseAspect(Element aspectElement, BeanDefinitionRegistry registry) {
String aspectName = aspectElement.attributeValue(REF); List<BeanDefinition> beanDefinitions = new ArrayList<>();
List<RuntimeBeanReference> beanReferences = new ArrayList<>(); // parse advice
List<Element> elements = aspectElement.elements();
boolean adviceFoundAlready = false;
for (Element element : elements) {
if (isAdviceNode(element)) {
if (!adviceFoundAlready) {
adviceFoundAlready = true;
if (!StringUtils.hasText(aspectName)) {
return;
}
beanReferences.add(new RuntimeBeanReference(aspectName));
}
GenericBeanDefinition advisorDefinition = parseAdvice(aspectName, element, registry,
beanDefinitions, beanReferences);
beanDefinitions.add(advisorDefinition);
}
} // parse pointcut
List<Element> pointcuts = aspectElement.elements(POINTCUT);
for (Element pointcut : pointcuts) {
parsePointcut(pointcut, registry);
}
} private void parsePointcut(Element pointcutElement, BeanDefinitionRegistry registry) {
String id = pointcutElement.attributeValue(ID);
String expression = pointcutElement.attributeValue(EXPRESSION); GenericBeanDefinition pointcutDefinition = createPointcutDefinition(expression);
if (StringUtils.hasText(id)) {
registry.registerBeanDefinition(id, pointcutDefinition);
} else {
BeanDefinitionReaderUtils.registerWithGeneratedName(pointcutDefinition, registry);
}
} private GenericBeanDefinition parseAdvice(String aspectName, Element adviceElement,
BeanDefinitionRegistry registry, List<BeanDefinition> beanDefinitions,
List<RuntimeBeanReference> beanReferences) { GenericBeanDefinition methodDefinition = new GenericBeanDefinition(MethodLocatingFactory.class);
methodDefinition.getPropertyValues().add(new PropertyValue("targetBeanName", aspectName));
methodDefinition.getPropertyValues().add(new PropertyValue("methodName",
adviceElement.attributeValue("method")));
methodDefinition.setSynthetic(true); // create instance definition factory
GenericBeanDefinition aspectFactoryDef = new GenericBeanDefinition(AopInstanceFactory.class);
aspectFactoryDef.getPropertyValues().add(new PropertyValue("aspectBeanName", aspectName));
aspectFactoryDef.setSynthetic(true); // register the pointcut
GenericBeanDefinition adviceDef = createAdviceDefinition(adviceElement, aspectName,
methodDefinition, aspectFactoryDef, beanDefinitions, beanReferences);
adviceDef.setSynthetic(true); // register the final advisor
BeanDefinitionReaderUtils.registerWithGeneratedName(adviceDef, registry); return adviceDef;
} // omit other methods ... }

创建 BeanDefinition 已经完成了,现在可根据 XML 配置文件解析出对应的 BeanDefintion 了,下面只需要在合适的时机将这些 BeanDefinition 放到容器中就完成了全部流程了。

如何放到容器中

该如何把解析出来的 BeanDefintion 放到容器当中去呢?我们知道在 Spring 框架当中提供了很多的“钩子函数”,可以从这里入手,Bean 的生命周期如下:

选择在 Bean 实例化完成之后 BeanPostProcessor 的 postProcessAfterInitialization() 方法创建代理对象,AOP 使用的是 AspectJ,将创建代理对象的类命名为 AspectJAutoProxyCreator,实现 BeanPostProcessor 接口,处理代理对象的创建,AspectJAutoProxyCreator 类的核心源码如下:

/**
* @author mghio
* @since 2021-06-13
*/
public class AspectJAutoProxyCreator implements BeanPostProcessor { private ConfigurableBeanFactory beanFactory; @Override
public Object beforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
} @Override
public Object afterInitialization(Object bean, String beanName) throws BeansException {
// 如果这个 bean 本身就是 Advice 及其子类,则不生成动态代理
if (isInfrastructureClass(bean.getClass())) {
return bean;
} List<Advice> advices = getCandidateAdvices(bean);
if (advices.isEmpty()) {
return bean;
} return createProxy(advices, bean);
} protected Object createProxy(List<Advice> advices, Object bean) {
Advised config = new AdvisedSupport();
for (Advice advice : advices) {
config.addAdvice(advice);
} Set<Class> targetInterfaces = ClassUtils.getAllInterfacesForClassAsSet(bean.getClass());
for (Class targetInterface : targetInterfaces) {
config.addInterface(targetInterface);
}
config.setTargetObject(bean); AopProxyFactory proxyFactory = null;
if (config.getProxiedInterfaces().length == 0) {
// CGLIB 代理
proxyFactory = new CglibProxyFactory(config);
} else {
// TODO(mghio): JDK dynamic proxy ... } return proxyFactory.getProxy();
} public void setBeanFactory(ConfigurableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
} private List<Advice> getCandidateAdvices(Object bean) {
List<Object> advices = this.beanFactory.getBeansByType(Advice.class);
List<Advice> result = new ArrayList<>();
for (Object advice : advices) {
Pointcut pointcut = ((Advice) advice).getPointcut();
if (canApply(pointcut, bean.getClass())) {
result.add((Advice) advice);
}
}
return result;
} private boolean canApply(Pointcut pointcut, Class<?> targetClass) {
MethodMatcher methodMatcher = pointcut.getMethodMatcher();
Set<Class> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
classes.add(targetClass);
for (Class<?> clazz : classes) {
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
if (methodMatcher.matches(m)) {
return true;
}
}
}
return false;
} private boolean isInfrastructureClass(Class<?> beanClass) {
return Advice.class.isAssignableFrom(beanClass);
}
}

最后别忘了,这里的 BeanPostProcessor 接口是我们新加的,需要到之前定义的 DefaultFactoryBean 中加上对 BeanPostProcessor 的处理逻辑,主要修改如下:

public class DefaultBeanFactory extends AbstractBeanFactory implements BeanDefinitionRegistry {

    @Override
public Object createBean(BeanDefinition bd) throws BeanCreationException {
// 1. instantiate bean
Object bean = instantiateBean(bd);
// 2. populate bean
populateBean(bd, bean);
// 3. initialize bean
bean = initializeBean(bd, bean);
return bean;
} protected Object initializeBean(BeanDefinition bd, Object bean) { ... // 非合成类型则创建代理
if (!bd.isSynthetic()) {
return applyBeanPostProcessorAfterInitialization(bean, bd.getId());
}
return bean;
} private Object applyBeanPostProcessorAfterInitialization(Object existingBean, String beanName) {
Object result = existingBean;
for (BeanPostProcessor postProcessor : getBeanPostProcessors()) {
result = postProcessor.afterInitialization(result, beanName);
if (result == null) {
return null;
}
}
return result;
} // omit other field and methods ... }

最后运行事先测试用例,正常通过符合预期。

总结

本文主要介绍了 AOP 代理对象生成、解析 XML 配置文件并创建对应的 BeanDefinition 以及最后注入到容器中,只是介绍了大体实现思路,具体代码实现已上传 mghio-spring,感兴趣的朋友可以参考,到这里,AOP 实现部分已经全部介绍完毕。

如何实现一个简易版的 Spring - 如何实现 AOP(终结篇)的更多相关文章

  1. 如何实现一个简易版的 Spring - 如何实现 AOP(上)

    前言 本文是「如何实现一个简易版的 Spring 系列」的第五篇,在之前介绍了 Spring 中的核心技术之一 IoC,从这篇开始我们再来看看 Spring 的另一个重要的技术--AOP.用过 Spr ...

  2. 如何实现一个简易版的 Spring - 如何实现 AOP(中)

    前言 在上篇 如何实现 AOP(上) 介绍了 AOP 技术出现的原因和一些重要的概念,在我们自己实现之前有必要先了解一下 AOP 底层到底是如何运作的,所以这篇再来看看 AOP 实现所依赖的一些核心基 ...

  3. 如何实现一个简易版的 Spring - 如何实现 AOP(下)

    前言 前面两篇 如何实现 AOP(上).如何实现 AOP(中) 做了一些 AOP 的核心基础知识简要介绍,本文进入到了实战环节了,去实现一个基于 XML 配置的简易版 AOP,虽然是简易版的但是麻雀虽 ...

  4. 如何实现一个简易版的 Spring - 如何实现 Constructor 注入

    前言 本文是「如何实现一个简易版的 Spring」系列的第二篇,在 第一篇 介绍了如何实现一个基于 XML 的简单 Setter 注入,这篇来看看要如何去实现一个简单的 Constructor 注入功 ...

  5. 如何实现一个简易版的 Spring - 如何实现 @Component 注解

    前言 前面两篇文章(如何实现一个简易版的 Spring - 如何实现 Setter 注入.如何实现一个简易版的 Spring - 如何实现 Constructor 注入)介绍的都是基于 XML 配置文 ...

  6. 如何实现一个简易版的 Spring - 如何实现 @Autowired 注解

    前言 本文是 如何实现一个简易版的 Spring 系列第四篇,在 上篇 介绍了 @Component 注解的实现,这篇再来看看在使用 Spring 框架开发中常用的 @Autowired 注入要如何实 ...

  7. 如何实现一个简易版的 Spring - 如何实现 Setter 注入

    前言 之前在 上篇 提到过会实现一个简易版的 IoC 和 AOP,今天它终于来了...相信对于使用 Java 开发语言的朋友们都使用过或者听说过 Spring 这个开发框架,绝大部分的企业级开发中都离 ...

  8. 手动实现一个简易版SpringMvc

    版权声明:本篇博客大部分代码引用于公众号:java团长,我只是在作者基础上稍微修改一些内容,内容仅供学习与参考 前言:目前mvc框架经过大浪淘沙,由最初的struts1到struts2,到目前的主流框 ...

  9. .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”

    FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...

随机推荐

  1. UVA11464偶数矩阵

    题意:       给你一个n*n的01矩阵,你的你的任务是吧尽量少的0变成1,使得每个元素的上下左右之和均为偶数(如果有的话),比如 0 0 0         0 1 0 1 0 0  ---&g ...

  2. UVA11054Gergovia的酒交易

    题意:       有n个村庄,每个村庄要么买酒要么买酒,负数是买酒,整数是买酒,题目保证所有的数字想加和为0,保证有解,然后每一个村庄往相邻的村庄运k坛酒的花费是k,问满足所有的村庄的最小花费是多少 ...

  3. 发生系统错误 1275.此驱动程序被阻止加载 寒江孤钓<<windows 内核安全编程>> 学习笔记

    安装书中第一章成功安装first服务之后,在cmd窗口使用命令行 "net start first" 时, 出现 "发生系统错误 1275.此驱动程序被阻止加载" ...

  4. SQL注入平台第一关,注入?id=1'不报错的问题

    第一关需要在地址栏输入id参数测试是否有注入点 我这里输入 http://localhost/sqli-labs-master/Less-1/?id=1 下一步将id参数改为?id=1' http:/ ...

  5. Asp.NetCore Web开发之创建项目

    ​这一节,开始讲一下如何创建一个Asp.netCore Web项目,有两种常用的方式,一种是通过.NetCore SDK使用命令创建,另一种如果你使用的VisualStudio,可以直接根据引导创建. ...

  6. FROM-4-TO-6!!!!!!!!! - OO第二单元总结

    电梯的这三次作业是对并发编程的一次管窥,感觉收获还是蛮多的.在设计上有好的地方也有不足,这里简单回顾总结一下 设计总述 电梯这个问题由于比较贴近真实生活,所以需求还是很好理解的.总的来说,我的数据处理 ...

  7. VS·.Net WCF多项目调试方法

    阅文时长 | 0.12分钟 字数统计 | 252.8字符 主要内容 | 1.引言&背景 2.声明与参考资料 『VS·.Net WCF多项目调试方法』 编写人 | SCscHero 编写时间 | ...

  8. Elastic Stack(ElasticSearch 、 Kibana 和 Logstash) 实现日志的自动采集、搜索和分析

    Elastic Stack 包括 Elasticsearch.Kibana.Beats 和 Logstash(也称为 ELK Stack).能够安全可靠地获取任何来源.任何格式的数据,然后实时地对数据 ...

  9. [刷题] PTA 查验身份证

    题目: 7-63 查验身份证 (15 分)  一个合法的身份证号码由17位地区.日期编号和顺序编号加1位校验码组成.校验码的计算规则如下: 首先对前17位数字加权求和,权重分配为:{7,9,10,5, ...

  10. 【Git】git clone报错 git fatal: Unable to find remote helper for 'https'

    [参考资料] https://stackoverflow.com/questions/8329485/unable-to-find-remote-helper-for-https-during-git ...