一、AOP分析

问题1:AOP是什么?

  Aspect Oriented Programming 面向切面编程,在不改变类的代码的情况下,对类方法进行功能增强。

问题2:我们需要做什么?

在我们的框架中要向使用用户提供AOP功能,让他们可以通过AOP技术实现对类方法进行功能增强。

从"Aspect Oriented Programming 面向切面编程,在不改变类的代码的情况下,对类方法进行功能增强"这句话我们能得到下面的这些信息:

二、AOP概念学习

我们先来看一下下面的这张图

说明:

程序运行时会调用很多方法,调用的很多方法就叫做Join points(连接点,可以被选择来进行增强的方法点),在方法的前或者后选择一个地方来切入,切入的的地方就叫做Pointcut(切入点,选择增强的方法),然后把要增强的功能(Advice)加入到切入点所在的位置。Advice和Pointcut组成一个切面(Aspect)

AOP的几个概念:

Advice、Pointcut、Weaving的特点:

Advice(功能增强):

1)用户性:由用户提供增强功能的逻辑代码

2)变化的:不同的增强需求,会有不同的逻辑

3)可选时机:可选择在方法前、后、异常时进行功能增强

4)多重的:同一个切入点上可以有多重增强

Pointcut(切入点):

1)用户性:由用户来指定

2)变化的:用户可灵活指定

3)多点性:用户可以选择在多个点上进行功能增强

Weaving(织入):

1)无侵入性,因为不改变原类的代码

2)我们在框架中实现

三、切面实现

我们将要分析Advice、Pointcut、Aspect这三个东西

1. Advice设计

Advice是由用户来提供,我们来使用,它是多变得。

问题1:我们如何能识别用户提供的东西?用户在我们写好框架以后使用我们的框架。

问题2:如何让我们的代码隔绝用户提供的多变?

解决方法:

我们定义一套标准接口,用户通过实现接口来提供它们不同的逻辑。

为了应对变化,这里使用到了设计原则:面向接口编程 

Advice的特点:可选时机,可选择在方法前、后、异常时进行功能增强

  1)有的Advice是在方法执行前进行增强——前置增强

  2)有的Advice是在方法执行后进行增强——后置增强

  3)有的Advice会在方执行前后都进行增强——环绕增强

  4)有的Advice则只是在方法执行抛出异常时进行增强——异常处理增强

问题1:我们需要做什么?

  定义标准接口方法,让用户可以实现它,提供各种增强。

问题2:这四种增强所需的参数一样吗?

  下面我们来一个一个的分析

1.1 前置增强分析

前置增强:在方法执行前进行增强

问题1:它可能需要什么参数?

  目的是对方法进行增强,应该需要的是方法相关的信息。

问题2:运行时,方法有哪些信息?

  方法本身 Method

  方法属于哪个类 Object

  方法的参数 Object  [ ]  

  方法的返回值

  ...........

问题3:前置增强可能需要什么参数?

  方法本身 Method

  方法属于哪个类 Object

  方法的参数 Object  [ ] 

问题3:前置增强的返回值是什么?

  在方法执行前进行增强,不需要返回值

1.2 后置增强分析

后置增强:在方法执行后进行增强

问题1:后置增强可能需要什么参数?

  方法本身 Method

  方法属于哪个类 Object

  方法的参数 Object  [ ]  

  方法的返回值

问题2:后置增强的返回值是什么?

  在方法执行后进行增强,不需要返回值

1.3 环绕增强分析

环绕增强:方法执行前后进行增强(包裹方法进行增强)

问题1:它可能需要什么参数?

  方法本身 Method

  方法属于哪个类 Object

  方法的参数 Object  [ ]  

问题2:环绕增强的返回值是什么?

  方法被它包裹,也即方法将由它来执行,它需要返回方法的返回值。Object

1.4 异常处理增强分析

异常处理增强:捕获方法执行时的异常,进行增强处理。

问题1:它可能需要什么参数?

   异常信息

问题2:进行异常处理增强需要包裹方法吗?

  需要,把执行代码用try包起来,捕获到哪个异常就在哪里进行增强

问题3:那它可否在环绕中实现?

  可以

1.5 经过前面的分析,我们一共需要定义三个方法,因为1.3和1.4可以都用环绕实现

问题1:是把这三个方法定义在一个接口中,还是分别定义在三个接口中?

  分三个接口,这样可以通过类型来区分不同的增强(Advice)

类图如下:

对应的代码实现:

前置增强:

package com.study.spring.aop.advice;

import java.lang.reflect.Method;

/**
*
* @Description: 前置增强接口
* @author leeSmall
* @date 2018年12月1日
*
*/
public interface MethodBeforeAdvice extends Advice { /**
* 实现该方法进行前置增强
*
* @param method 被增强的方法
*
* @param args 被增强的方法的参数
*
* @param target 被增强的目标对象(被增强的方法所在的类)
*
* @throws Throwable 异常
*/
void before(Method method, Object[] args, Object target) throws Throwable;
}

后置增强:

package com.study.spring.aop.advice;

import java.lang.reflect.Method;

/**
*
* @Description: 后置增强接口
* @author leeSmall
* @date 2018年12月1日
*
*/
public interface AfterReturningAdvice extends Advice {
/**
* 实现该方法,提供后置增强
*
* @param returnValue 被增强的方法的返回值
*
* @param method 被增强的方法
*
* @param args 被增强的方法的参数
*
* @param target 被增强的目标对象(被增强的方法所在的类)
*
* @throws Throwable 异常
*/
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}

方法进行环绕(前置、后置)增强、异常处理增强:

package com.study.spring.aop.advice;

import java.lang.reflect.Method;

/**
*
* @Description: 对方法进行环绕(前置、后置)增强、异常处理增强接口
* @author leeSmall
* @date 2018年12月1日
*
*/
public interface MethodInterceptor extends Advice {
/**
* 对方法进行环绕(前置、后置)增强、异常处理增强,方法实现中需调用目标方法。
*
* @param method 被增强的方法
*
* @param args 被增强的方法的参数
*
* @param target 被增强的目标对象(被增强的方法所在的类)
*
* @return Object 被增强的方法的返回值
* @throws Throwable
*/
Object invoke(Method method, Object[] args, Object target) throws Throwable;
}

2. Pointcut设计

2.1 Pointcut分析

Pointcut的特点:

  1)用户性:由用户来指定

  2)变化的:用户可灵活指定

  3)多点性:用户可以选择在多个点上进行增强

我们需要做什么?

  为用户提供一个东西,让他们可以灵活的指定多个方法点,而且我们又能懂

切入点是由用户来指定在哪些方法点上进行增强,那么这个哪些方法如何来表示,能满足上面的点?

分析:

  1)指定哪些方法,是不是一个描述信息

  2)如何来指定一个方法——某类的某个方法

  3)方法重载怎么办——加上参数类型

  4)有没有感觉其实就是一个完整的方法签名

    com.study.design.mode.samples.proxy.Girl.dating(float length)

    com.study.design.mode.samples.proxy.Girl.dating(long time)

  5)如何做到多点性,灵活性?在一个描述中指定一类类的某些方法?

    a)某个包下的某个类的某个方法

    b)某个包下的所有类中的所有方法

    c)某个包下的所有类中的do开头的方法

    d)某个包下的以service结尾的类中的do开头的方法

    e)某个包下的及其子包下的以service结尾的类中的do开头的方法

    总结:我们需要一个能描述上面a-e这些信息的表达式

  6)要表达哪些信息?

    包名、类名、方法名(参数类型)

  7)每部分的要求是怎样的?

    包名:有父子特点,要能模糊匹配

    类名:要能模糊匹配

    方法:要能模糊匹配

    参数类型:参数可以有多个

  8)这个表达式将被我们用来决定是否需要对某个类的某个方法进行功能增强,这个决定过程应该是怎样的?

    匹配类、匹配方法

  9)一个表达式不好实现,分成多个表达式进行组合是否更容易些?

    可以这么考虑

  10)我们掌握的表达式有哪些?

    正则表达式

    Ant Path表达式

    AspectJ的Pointcut表达式——execution(* com.study.design.mode.samples.proxy.Girl.*(..))

    总结:正则表达式是可以的,AspectJ本就是切面编程的组件,也是可以的

2.2 AspectJ的Pointcut表达式语法学习

AspectJ是什么?
  AspectJ是java里面切面编程的库,能够帮助我们完成预编译时的代码增强,和eclispe配合使用可以生成相关的字节码。我们在AOP里面只使用了他的表达式解析匹配相关的API

AspectJ的Pointcut表达式是用来表示应该在哪个类的哪个方法进行切入进行方法增强的。

语法如下:

示例:

* com.study.design.mode.samples.proxy.Girl.*(..)

2.3 Pointcut设计

下面开始对Pointcut进行接口、类设计了

问题1:切点应有什么属性?

  切点定义表达式

问题2:切点应对外提供什么行为(方法)?

问题3:切点将被我们用来做什么?

  对类、方法进行匹配

  切点应该提供匹配类、匹配方法的行为

问题4:如果在我们设计的框架中要能灵活扩展的切点的实现方式,我们该如何设计?

  这又是一个支持可多变的问题,像通知一样,我们来定义一套标准接口,定义好基本行为,面向接口编程,屏蔽掉具体的实现

  无论哪种实现,都实现匹配类、匹配方法的接口

Pointcut标准接口的类图:

Pointcut代码

package com.study.spring.aop.pointcut;

import java.lang.reflect.Method;

/**
*
* @Description: Pointcut标准接口
* @author leeSmall
* @date 2018年12月2日
*
*/
public interface Pointcut { //匹配类
boolean matchsClass(Class<?> targetClass); //匹配方法
boolean matchsMethod(Method method, Class<?> targetClass);
}

下面我们来实现AspectJ表达式的Pointcut

AspectJExpressionPointcut的实现

可以使用AspectJ的API把这两个match方法实现

实现步骤:

1)引入AspectJ的jar

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>

2)掌握AspectJ的API的使用,我们只使用它的切点表达式解析匹配部分

a)入口:org.aspectj.weaver.tools.PointcutParser 获得切点解析器

PointcutParser pp = PointcutParser
.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();

b)解析表达式得到org.aspectj.weaver.tools.PointcutExpression

PointcutExpression pe = pp.parsePointcutExpression("execution(* com.study.spring.samples.*.set*(..))");

c)用PointcutExpression匹配类,匹配有时候不准,后面可以通过匹配方法来精确匹配

pe.couldMatchJoinPointsInType(ABean.class)

d)用PointcutExpression匹配方法,可以实现精确匹配

//拿到匹配类的Class
Class<?> cl = ABean.class; //通过方法名拿到Method
Method aMethod = cl.getMethod("doSomthing", null); //使用PointcutExpression匹配方法拿到匹配对象ShadowMatch
ShadowMatch sm = pe.matchesMethodExecution(aMethod); //从匹配对象ShadowMatch里面查看是否匹配
System.out.println(sm.alwaysMatches());

AspectJExpressionPointcut的具体代码:

package com.study.spring.aop.pointcut;

import java.lang.reflect.Method;

import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.ShadowMatch; /**
*
* @Description: AspectJ表达式的Pointcut
* @author leeSmall
* @date 2018年12月2日
*
*/
public class AspectJExpressionPointcut implements Pointcut { //获得切点解析器
private static PointcutParser pp = PointcutParser
.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution(); //表达式
private String expression; //Pointcut表达式对象
private PointcutExpression pe; public AspectJExpressionPointcut(String expression) {
super();
this.expression = expression;
//解析表达式得到org.aspectj.weaver.tools.PointcutExpression
pe = pp.parsePointcutExpression(expression);
} //匹配类 用PointcutExpression匹配类,匹配有时候不准,后面可以通过匹配方法来精确匹配
@Override
public boolean matchsClass(Class<?> targetClass) {
return pe.couldMatchJoinPointsInType(targetClass);
} //匹配方法 用PointcutExpression匹配方法,可以实现精确匹配
@Override
public boolean matchsMethod(Method method, Class<?> targetClass) {
ShadowMatch sm = pe.matchesMethodExecution(method);
return sm.alwaysMatches();
} public String getExpression() {
return expression;
} }

到这里,功能增强(Advice)和Pointcut我们都实现了,下面来看看用户如何使用我们提供的东西了

说明:

从上面的Advice和Pointcut的类图我们可以知道,用户要使用我们提供的Advice和Pointcut,只需要实现自己的一个Advice,如MyBeforeAdvice,并把实现的Advice配置成bean,然后传入一个表达式到AspectJExpressionPointcut里面就可以了。

配置的实现的Advice的bean的名字(adviceBeanName)和表达式(expression)组成一个切面

上面的使用还是不太好,这个时候我们需要为上面的使用抽象出一个接口,使得用户的使用更加简单!!!请继续看下面的内容

2.4 Aspect(切面)设计

为用户提供更简单的外观,Advisor(通知者)组合Advice和Pointcut

Advisor(通知者)代码实现:

package com.study.spring.aop.advisor;

/**
*
* @Description: Advisor(通知者)接口
* @author leeSmall
* @date 2018年12月2日
*
*/
public interface Advisor { String getAdviceBeanName(); String getExpression();
}

基于切入点的通知者实现代码:

package com.study.spring.aop.advisor;

import com.dn.spring.aop.pointcut.Pointcut;

/**
*
* @Description:基于切入点的通知者实现
* @author leeSmall
* @date 2018年12月2日
*
*/
public interface PointcutAdvisor extends Advisor { Pointcut getPointcut();
}

AspectJPointcutAdvisor(基于AspectJ切入点的通知者实现)代码实现:

package com.study.spring.aop.advisor;

import com.dn.spring.aop.pointcut.AspectJExpressionPointcut;
import com.dn.spring.aop.pointcut.Pointcut; /**
*
* @Description: 基于AspectJ切入点的通知者实现 用户配的一个切面,包含Advice(功能增强)和Pointcut(切入点)
* @author leeSmall
* @date 2018年12月2日
*
*/
public class AspectJPointcutAdvisor implements PointcutAdvisor { //用户配置的advice的bean的名字
private String adviceBeanName; //切入点表达式
private String expression; //AspectJ表达式切入点对象
private AspectJExpressionPointcut pointcut; public AspectJPointcutAdvisor(String adviceBeanName, String expression) {
super();
this.adviceBeanName = adviceBeanName;
this.expression = expression;
this.pointcut = new AspectJExpressionPointcut(this.expression);
} @Override
public Pointcut getPointcut() {
return this.pointcut;
} @Override
public String getAdviceBeanName() {
return this.adviceBeanName;
} @Override
public String getExpression() {
return this.expression;
}
}

 扩展不同的Advisor实现:

还可把AspectJPointcutAdvisor和RegExpressionPointcutAdvisor的公共部分提取出来减少冗余代码:

四、织入实现

1. Weaving织入分析

织入要完成什么?

  将用户提供的增强功能加到指定的方法上。这一部分是我们要实现的

思考以下问题:

问题1:在什么时候做织入?

  创建bean实例的时候,在bean初始化完成后,再对其进行增强

问题2:如何确定bean要增强?

  对bean类及其方法挨个匹配用户指定的切面,如果有切面匹配就是要增强的

问题3:如何织入

  代理  

2. Weaving织入设计

整理一下AOP的使用流程,帮助我们更好地去设计织入

问题1:用户到哪里去注册切面?

  BeanFactory?

问题2:判断匹配、织入的逻辑写在哪里?

  写在BeanFactory中?

我们现在是不是要在Bean创建的过程中加入一项处理?后续可能在Bean创建过程中还会加入更多别的处理,如果直接在BeanFactory中实现会有什么不好?

  BeanFactory的代码会越来越多

  不易扩展

那么该怎么来设计呢?

回顾一下Bean产生的过程中都经历了什么

在Bean产生的过程中,会有很多的处理逻辑加入到过程的不同阶段,比如bean初始化前、bean初始化后等等

我们如何来设计能让我们的BeanFactory一次写好后,后面就不改代码,就可以灵活扩展呢?

  在各个节点加入扩展点、加入注册机制

什么是扩展点,什么是注册机制?

  这里就需要用到前面学习的观察者模式(监听模式)了,BeanFactory就是主题(保证写好一次后就不在改变),6个扩展点就是观察者,主题面向观察者编程,BeanFactory(主题)里面可以添加、删除、通知6个扩展点(观察者

观察者模式类图:

说明:

主题Subject面向观察者接口Observer编程,主题里面可以添加、删除和通知观察者Observer;
注意每个观察者都有一个回调方法update,如果有变化就会在主题的notifyObservers()方法里面调用update方法,把最新的变化给到观察者

变化之处:观察者会变,观察者的数量会变。

不变:主题的代码要不受观察者变化的影响。

观察者模式定义:

定义了对象之间一对多的依赖关系,当一端对象改变状态时,它的所有依赖者都会收到通知并自动更新(被调用更新方法)。也称为:监听模式、发布订阅模式。提供一种对象之间松耦合的设计方式。

使用观察者模式来加入我们的AOP织入

代码实现:

AOP增强处理接口:

package com.study.spring.beans;

/**
*
* @Description: AOP增强处理接口
* @author leeSamll
* @date 2018年12月2日
*
*/
public interface BeanPostProcessor { //bean初始化前增强
default Object postProcessBeforeInitialization(Object bean, String beanName) throws Throwable {
return bean;
} //bean初始化后增强
default Object postProcessAfterInitialization(Object bean, String beanName) throws Throwable {
return bean;
}
}

Advisor注册接口:

package com.study.spring.aop.advisor;

import java.util.List;

/**
*
* @Description: Advisor注册接口
* @author leeSamll
* @date 2018年12月2日
*
*/
public interface AdvisorRegistry { //注册Advisor
public void registAdvisor(Advisor ad); //获取Advisor
public List<Advisor> getAdvisors();
}

AOP增强处理的观察者实现:

package com.study.spring.aop;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set; import org.apache.commons.collections4.CollectionUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils; import com.dn.spring.aop.advisor.Advisor;
import com.dn.spring.aop.advisor.AdvisorRegistry;
import com.dn.spring.aop.advisor.PointcutAdvisor;
import com.dn.spring.aop.pointcut.Pointcut;
import com.dn.spring.beans.BeanFactory;
import com.dn.spring.beans.BeanFactoryAware;
import com.dn.spring.beans.BeanPostProcessor; /**
*
* @Description: AOP增强处理的观察者实现
* @author leeSamll
* @date 2018年12月2日
*
*/
public class AdvisorAutoProxyCreator implements AdvisorRegistry, BeanPostProcessor, BeanFactoryAware {
private List<Advisor> advisors; private BeanFactory beanFactory; public AdvisorAutoProxyCreator() {
this.advisors = new ArrayList<>();
} public void registAdvisor(Advisor ad) {
this.advisors.add(ad);
} public List<Advisor> getAdvisors() {
return advisors;
} @Override
public void setBeanFactory(BeanFactory bf) {
this.beanFactory = bf;
} //后置增强
public Object postProcessAfterInitialization(Object bean, String beanName) throws Throwable { // 在此判断bean是否需要进行切面增强
List<Advisor> matchAdvisors = getMatchedAdvisors(bean, beanName);
// 如需要就进行增强,再返回增强的对象。
if (CollectionUtils.isNotEmpty(matchAdvisors)) {
bean = this.createProxy(bean, beanName, matchAdvisors);
}
return bean;
} //在此判断bean是否需要进行切面增强
private List<Advisor> getMatchedAdvisors(Object bean, String beanName) {
if (CollectionUtils.isEmpty(advisors)) {
return null;
} // 得到类、类的所有方法
Class<?> beanClass = bean.getClass();
List<Method> allMethods = this.getAllMethodForClass(beanClass); // 存放匹配的Advisor的list
List<Advisor> matchAdvisors = new ArrayList<>();
// 遍历Advisor来找匹配的
for (Advisor ad : this.advisors) {
if (ad instanceof PointcutAdvisor) {
if (isPointcutMatchBean((PointcutAdvisor) ad, beanClass, allMethods)) {
matchAdvisors.add(ad);
}
}
} return matchAdvisors;
} //获取类的所有方法,包括继承的父类和实现的接口里面的方法
private List<Method> getAllMethodForClass(Class<?> beanClass) {
List<Method> allMethods = new LinkedList<>();
//获取beanClass的所有接口
Set<Class<?>> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(beanClass));
classes.add(beanClass); //遍历所有的类和接口反射获取到所有的方法
for (Class<?> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method m : methods) {
allMethods.add(m);
}
} return allMethods;
} //判断类及类的方法是否和切面匹配
private boolean isPointcutMatchBean(PointcutAdvisor pa, Class<?> beanClass, List<Method> methods) {
Pointcut p = pa.getPointcut(); // 首先判断类是否匹配
if (!p.matchsClass(beanClass)) {
return false;
} // 再判断是否有方法匹配
for (Method method : methods) {
if (p.matchsMethod(method, beanClass)) {
return true;
}
}
return false;
} //创建代理对象增强
private Object createProxy(Object bean, String beanName, List<Advisor> matchAdvisors) throws Throwable {
// 通过AopProxyFactory工厂去完成选择、和创建代理对象的工作。
return AopProxyFactory.getDefaultAopProxyFactory().createAopProxy(bean, beanName, matchAdvisors, beanFactory)
.getProxy();
} }

Advisor(通知者)接口:

package com.study.spring.aop.advisor;

/**
*
* @Description: Advisor(通知者)接口
* @author leeSmall
* @date 2018年12月2日
*
*/
public interface Advisor { //获取配置的Advice的bean的名字
String getAdviceBeanName(); //获取切入点表达式
String getExpression();
}

在IOC容器(bean工厂)接口里面增加注册AOP织入(注册AOP增强处理的观察者实现)的方法:

package com.study.spring.beans;

/**
*
* @Description: IOC容器(bean工厂)接口:负责创建bean实例
* @author leeSmall
* @date 2018年11月29日
*
*/
public interface BeanFactory {
/**
* 获取bean
*
* @param name bean的名字
*
* @return bean 实例
* @throws Exception
*/
Object getBean(String name) throws Throwable; //注册AOP织入(注册AOP增强处理的观察者实现)
void registerBeanPostProcessor(BeanPostProcessor bpp);
}

3. Weaving织入实现

3.1 判断bean是否需要织入增强

问题1:如何判断bean实例是否要增强?

 1)通过反射获取bean类及所有方法

  java.lang.Class.getMethods() : Method[ ]  获取所有修饰符为public的方法,包括实现的接口和继承的父类里面的所有public修饰的方法

  java.lang.Class.getMethod(String, Class<?>...)  : Method 获取一个指定的public修饰符修饰的方法,包括实现的接口里面的public修饰的方法

  java.lang.Class.getDeclaredMethods() : Method[ ]  获取类里面的所有方法,包括public, protected, default (package) access, and private 这些修饰符修饰的方法,但是不能获取从父类继承来的方法

  总结:上面的三种方式都不能保证获取到所有的方法,如果要获取所有的方法就得递归调用,找到所有的类再去获取对应的方法,这一点Spring已经有实现好了的了,我们直接拿来用就行了。

     Spring获取类的所有方法的API如下:

//获取类的所有方法,包括继承的父类和实现的接口里面的方法
private List<Method> getAllMethodForClass(Class<?> beanClass) {
List<Method> allMethods = new LinkedList<>();
//获取beanClass的所有接口
Set<Class<?>> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(beanClass));
classes.add(beanClass); //遍历所有的类和接口反射获取到所有的方法
for (Class<?> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method m : methods) {
allMethods.add(m);
}
} return allMethods;
}

说明:

获取类的所有接口:org.springframework.util.ClassUtils.getAllInterfacesForClassAsSet(Class<?>)

获取类的所有方法:org.springframework.util.ReflectionUtils.getAllDeclaredMethods(Class<?>)

 2)遍历Advisor(通知者),取Advisor中的Pointcut(切入点)来匹配类、匹配方法

判断bean实例是否要增强的代码实现:

    //在此判断bean是否需要进行切面增强
private List<Advisor> getMatchedAdvisors(Object bean, String beanName) {
if (CollectionUtils.isEmpty(advisors)) {
return null;
} // 得到类、类的所有方法
Class<?> beanClass = bean.getClass();
List<Method> allMethods = this.getAllMethodForClass(beanClass); // 存放匹配的Advisor的list
List<Advisor> matchAdvisors = new ArrayList<>();
// 遍历Advisor来找匹配的
for (Advisor ad : this.advisors) {
if (ad instanceof PointcutAdvisor) {
if (isPointcutMatchBean((PointcutAdvisor) ad, beanClass, allMethods)) {
matchAdvisors.add(ad);
}
}
} return matchAdvisors;
} //获取类的所有方法,包括继承的父类和实现的接口里面的方法
private List<Method> getAllMethodForClass(Class<?> beanClass) {
List<Method> allMethods = new LinkedList<>();
//获取beanClass的所有接口
Set<Class<?>> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(beanClass));
classes.add(beanClass); //遍历所有的类和接口反射获取到所有的方法
for (Class<?> clazz : classes) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method m : methods) {
allMethods.add(m);
}
} return allMethods;
} //判断类及类的方法是否和切面匹配
private boolean isPointcutMatchBean(PointcutAdvisor pa, Class<?> beanClass, List<Method> methods) {
Pointcut p = pa.getPointcut(); // 首先判断类是否匹配
if (!p.matchsClass(beanClass)) {
return false;
} // 再判断是否有方法匹配
for (Method method : methods) {
if (p.matchsMethod(method, beanClass)) {
return true;
}
}
return false;
}

3.2 代理增强

问题2:代理增强的逻辑是怎么样的?

回忆一下JDK动态代理:

在运行时,对接口创建代理对象

生成代理类$Proxy0的方法:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

参数说明:

ClassLoader loader:类加载器

Class<?>[] interfaces:需要被代理的目标对象实现的接口,可以传入多个

InvocationHandler h:功能增强的接口

功能增强的接口:

public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

参数说明:

Object proxy:被代理的目标对象(接口)

Method method:要调用的目标对象的方法

Object[] args:要调用的目标对象的方法的参数

回忆一下cglib动态代理:

在运行期为类、接口生成动态代理对象。 以达到不改动原类代码而实现功能增强的目的

说明:

实现思想和前面的JDK动态代理一样,只是使用了不同的API。

代理类由Enhancer生成,代理类实现被代理的类或者接口,特定的功能增强的实现MyMethodInterceptor实现MethodInterceptor接口,特定的功能增强实现MyMethodInterceptor里面持有被代理的类或者接口target

下面就把生成代理对象的部分和功能增强实现的部分分别实现

无论是JDK动态代理还是cglib动态代理都是生成代理对象,因此可以对这两种代理进行抽象,先看下面的类图

要生成代理对象,完成织入增强,JDK动态代理这里需要一些什么数据?

  要实现的接口——要实现抽象生成代理对象的接口AopProxy和JDK动态代理生成代理对象的接口InvocationHandler

  目标对象——需要增强的Bean

  匹配的Advisors——Advice和Pointcut组成的切面去匹配被增强的Bean及Bean里面的方法

  BeanFactory ?

要生成代理对象,完成织入增强,cglib动态代理这里需要一些什么数据?

  要继承的类 ?

  要实现的接口——要实现抽象生成代理对象的接口AopProxy和cglib动态代理生成代理对象的接口MethodInterceptor

  目标对象——需要增强的Bean

  匹配的Advisors——Advice和Pointcut组成的切面去匹配被增强的Bean及Bean里面的方法

  BeanFactory ?

  构造参数类型 ?

  构造参数 ?

下面看具体的代码实现:

JDK动态代理和cglib动态代理抽象出公共部分的接口去获取代理对象:

package com.study.spring.aop;

/**
*
* @Description: JDK动态代理和cglib动态代理抽象出公共部分的接口去获取代理对象
* @author leeSmall
* @date 2018年12月2日
*
*/
public interface AopProxy { //获取代理对象
Object getProxy(); //通过类加载器获取代理对象
Object getProxy(ClassLoader classLoader); }

JDK动态代理实现:

package com.study.spring.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import com.dn.spring.aop.advisor.Advisor;
import com.dn.spring.beans.BeanFactory; /**
*
* @Description: JDK动态AOP代理实现
* @author leeSmall
* @date 2018年12月2日
*
*/
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class); private String beanName;
private Object target;
private List<Advisor> matchAdvisors; private BeanFactory beanFactory; public JdkDynamicAopProxy(String beanName, Object target, List<Advisor> matchAdvisors, BeanFactory beanFactory) {
super();
this.beanName = beanName;
this.target = target;
this.matchAdvisors = matchAdvisors;
this.beanFactory = beanFactory;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return AopProxyUtils.applyAdvices(target, method, args, matchAdvisors, proxy, beanFactory);
} @Override
public Object getProxy() {
return this.getProxy(target.getClass().getClassLoader());
} @Override
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("为" + target + "创建代理。");
}
return Proxy.newProxyInstance(classLoader, target.getClass().getInterfaces(), this);
} }

cglib动态代理实现:

package com.study.spring.aop;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import com.dn.spring.aop.advisor.Advisor;
import com.dn.spring.beans.BeanDefinition;
import com.dn.spring.beans.BeanFactory;
import com.dn.spring.beans.DefaultBeanFactory; import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; /**
*
* @Description: cglib动态AOP代理实现
* @author leeSmall
* @date 2018年12月2日
*
*/
public class CglibDynamicAopProxy implements AopProxy, MethodInterceptor {
private static final Log logger = LogFactory.getLog(CglibDynamicAopProxy.class);
private static Enhancer enhancer = new Enhancer(); private String beanName;
private Object target; private List<Advisor> matchAdvisors; private BeanFactory beanFactory; public CglibDynamicAopProxy(String beanName, Object target, List<Advisor> matchAdvisors, BeanFactory beanFactory) {
super();
this.beanName = beanName;
this.target = target;
this.matchAdvisors = matchAdvisors;
this.beanFactory = beanFactory;
} @Override
public Object getProxy() {
return this.getProxy(target.getClass().getClassLoader());
} @Override
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("为" + target + "创建cglib代理。");
}
Class<?> superClass = this.target.getClass();
enhancer.setSuperclass(superClass);
enhancer.setInterfaces(this.getClass().getInterfaces());
enhancer.setCallback(this);
Constructor<?> constructor = null;
try {
constructor = superClass.getConstructor(new Class<?>[] {});
} catch (NoSuchMethodException | SecurityException e) { }
if (constructor != null) {
return enhancer.create();
}
//没有无参构造函数时,从BeanDefinition里面获取构造参数的类型和值进行增强
else {
BeanDefinition bd = ((DefaultBeanFactory) beanFactory).getBeanDefinition(beanName);
return enhancer.create(bd.getConstructor().getParameterTypes(), bd.getConstructorArgumentRealValues());
}
} @Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
return AopProxyUtils.applyAdvices(target, method, args, matchAdvisors, proxy, beanFactory);
} }

3.3 实现重要的增强逻辑

逻辑如下:

增强逻辑代码应该写在JDK动态代理的invoke方法和cglib动态代理的intercept方法里面

由于都是增强的代理,逻辑是一样的就提取成一个公共的方法com.study.spring.aop.AopProxyUtils.applyAdvices(Object, Method, Object[], List<Advisor>, Object, BeanFactory)

JdkDynamicAopProxy代码:

    @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return AopProxyUtils.applyAdvices(target, method, args, matchAdvisors, proxy, beanFactory);
}

CglibDynamicAopProxy代码:

    @Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
return AopProxyUtils.applyAdvices(target, method, args, matchAdvisors, proxy, beanFactory);
}

增强逻辑实现工具类AopProxyUtils代码:

package com.dn.spring.aop;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List; import org.springframework.util.CollectionUtils; import com.dn.spring.aop.advisor.Advisor;
import com.dn.spring.aop.advisor.PointcutAdvisor;
import com.dn.spring.beans.BeanFactory; /**
*
* @Description: 增强逻辑实现工具类
* @author leeSmall
* @date 2018年12月3日
*
*/
public class AopProxyUtils { /**
* 对方法应用advices增强
*
* @param target
* @param method
* @param args
* @param matchAdvisors
* @param proxy
* @param beanFactory
* @return
* @throws Throwable
*/
public static Object applyAdvices(Object target, Method method, Object[] args, List<Advisor> matchAdvisors,
Object proxy, BeanFactory beanFactory) throws Throwable {
// 这里要做什么?
// 1、获取要对当前方法进行增强的advice
List<Object> advices = AopProxyUtils.getShouldApplyAdvices(target.getClass(), method, matchAdvisors,
beanFactory);
// 2、如有增强的advice,就责任链式增强执行
if (CollectionUtils.isEmpty(advices)) {
//没有Advice就直接调用invoke方法
return method.invoke(target, args);
} else {
// 有Advice就责任链式执行增强
AopAdviceChainInvocation chain = new AopAdviceChainInvocation(proxy, target, method, args, advices);
return chain.invoke();
}
} /**
* 获取与方法匹配的切面的advices
*
* @param beanClass
* @param method
* @param matchAdvisors
* @param beanFactory
* @return
* @throws Exception
*/
public static List<Object> getShouldApplyAdvices(Class<?> beanClass, Method method, List<Advisor> matchAdvisors,
BeanFactory beanFactory) throws Throwable {
if (CollectionUtils.isEmpty(matchAdvisors)) {
return null;
}
List<Object> advices = new ArrayList<>();
for (Advisor ad : matchAdvisors) {
if (ad instanceof PointcutAdvisor) {
//如果当前方法和切入点匹配就是要加入增强功能的方法
if (((PointcutAdvisor) ad).getPointcut().matchsMethod(method, beanClass)) {
advices.add(beanFactory.getBean(ad.getAdviceBeanName()));
}
}
} return advices;
} }

3.4 实现代理对象的创建

先实现JDK动态代理对象的方式的:

需要的参数:

  要实现的接口 ——AopProxy和InvocationHandler

    //创建代理对象
@Override
public Object getProxy() {
return this.getProxy(target.getClass().getClassLoader());
} //创建代理对象
@Override
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("为" + target + "创建代理。");
}
return Proxy.newProxyInstance(classLoader, target.getClass().getInterfaces(), this);
}

实现cglib动态代理方式的:

需要的参数:

  继承的参数

  实现的接口

  Callback

  构造参数类型

  构造参数

问题:构造参数类型、构造参数从哪来?

  创建对象实例时会有

传递构造参数类型和构造参数:

如何来传递创建bean实例时获得的数据到初始化后的AOP中呢?

  在BeanDefinition中用ThreadLocal保存创建bean实例时获得的数据(构造参数类型、构造参数)

GenericBeanDefinition代码:

    //没有无参构造函数时,传递构造参数的类型和值到cglib动态代理里面去获取有参构造函数进行增强
private ThreadLocal<Object[]> realConstructorArgumentValues = new ThreadLocal<>(); @Override
public Object[] getConstructorArgumentRealValues() {
return realConstructorArgumentValues.get();
} @Override
public void setConstructorArgumentRealValues(Object[] values) {
realConstructorArgumentValues.set(values);
}

DefaultBeanFactory代码:

// 构造方法来构造对象
private Object createInstanceByConstructor(BeanDefinition bd) throws Throwable {
try {
Object[] args = this.getConstructorArgumentValues(bd);
if (args == null) {
return bd.getBeanClass().newInstance();
} else {
bd.setConstructorArgumentRealValues(args);
// 决定构造方法
Constructor<?> constructor = this.determineConstructor(bd, args);
// 缓存构造函数由determineConstructor 中移到了这里,无论原型否都缓存,因为后面AOP需要用
bd.setConstructor(constructor);
return constructor.newInstance(args);
}
} catch (SecurityException e1) {
logger.error("创建bean的实例异常,beanDefinition:" + bd, e1);
throw e1;
}
}

CglibDynamicAopProxy代码:

    //创建代理对象
@Override
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("为" + target + "创建cglib代理。");
}
Class<?> superClass = this.target.getClass();
enhancer.setSuperclass(superClass);
enhancer.setInterfaces(this.getClass().getInterfaces());
enhancer.setCallback(this);
Constructor<?> constructor = null;
try {
constructor = superClass.getConstructor(new Class<?>[] {});
} catch (NoSuchMethodException | SecurityException e) { }
if (constructor != null) {
return enhancer.create();
}
//没有无参构造函数时,从BeanDefinition里面获取构造参数的类型和值进行增强
else {
BeanDefinition bd = ((DefaultBeanFactory) beanFactory).getBeanDefinition(beanName);
return enhancer.create(bd.getConstructor().getParameterTypes(), bd.getConstructorArgumentRealValues());
}
}

2.5 AdvisorAutoProxyCreator中怎么使用动态代理AopProxy

把选择使用哪个动态代理的逻辑交给工厂去判断

AopProxyFactory代码:

package com.study.spring.aop;

import java.util.List;

import com.dn.spring.aop.advisor.Advisor;
import com.dn.spring.beans.BeanFactory; /**
*
* @Description: 工厂AopProxyFactory负责选择使用哪个动态代理
* @author leeSamll
* @date 2018年12月3日
*
*/
public interface AopProxyFactory { AopProxy createAopProxy(Object bean, String beanName, List<Advisor> matchAdvisors, BeanFactory beanFactory)
throws Throwable; /**
* 获得默认的AopProxyFactory实例
*
* @return AopProxyFactory
*/
static AopProxyFactory getDefaultAopProxyFactory() {
return new DefaultAopProxyFactory();
}
}

DefaultAopProxyFactory代码:

package com.study.spring.aop;

import java.util.List;

import com.dn.spring.aop.advisor.Advisor;
import com.dn.spring.beans.BeanFactory; /**
*
* @Description: 工厂AopProxyFactory的默认实现 负责选择使用哪个动态代理
* @author leeSamll
* @date 2018年12月3日
*
*/
public class DefaultAopProxyFactory implements AopProxyFactory { @Override
public AopProxy createAopProxy(Object bean, String beanName, List<Advisor> matchAdvisors, BeanFactory beanFactory)
throws Throwable {
// 是该用jdk动态代理还是cglib?
if (shouldUseJDKDynamicProxy(bean, beanName)) {
return new JdkDynamicAopProxy(beanName, bean, matchAdvisors, beanFactory);
} else {
return new CglibDynamicAopProxy(beanName, bean, matchAdvisors, beanFactory);
}
} //默认使用cglib
private boolean shouldUseJDKDynamicProxy(Object bean, String beanName) {
// 如何判断?
// 这样可以吗:有实现接口就用JDK,没有就用cglib?
return false;
} }

AdvisorAutoProxyCreator代码:

    //创建代理对象增强
private Object createProxy(Object bean, String beanName, List<Advisor> matchAdvisors) throws Throwable {
// 通过AopProxyFactory工厂去完成选择、和创建代理对象的工作。
return AopProxyFactory.getDefaultAopProxyFactory().createAopProxy(bean, beanName, matchAdvisors, beanFactory)
.getProxy();
}

完整源码获取地址:

https://github.com/leeSmall/FrameSourceCodeStudy/tree/master/spring-v3

框架源码系列三:手写Spring AOP(AOP分析、AOP概念学习、切面实现、织入实现)的更多相关文章

  1. 框架源码系列四:手写Spring-配置(为什么要提供配置的方法、选择什么样的配置方式、配置方式的工作过程是怎样的、分步骤一个一个的去分析和设计)

    一.为什么要提供配置的方法 经过前面的手写Spring IOC.手写Spring DI.手写Spring AOP,我们知道要创建一个bean对象,需要用户先定义好bean,然后注册到bean工厂才能创 ...

  2. Spring源码系列(三)--spring-aop的基础组件、架构和使用

    简介 前面已经讲完 spring-bean( 详见Spring ),这篇博客开始攻克 Spring 的另一个重要模块--spring-aop. spring-aop 可以实现动态代理(底层是使用 JD ...

  3. 剑指Spring源码(三)俯瞰Spring的Bean的生命周期(大众版)

    距离上一次写Spring源码解析,已经过去了快要好几个月了,主要原因还是Spring的源码解析类文章太难写了,不像我先前写的什么CAS源码,AQS源码,LinkedBlockingQueue等等,这些 ...

  4. 框架源码系列二:手写Spring-IOC和Spring-DI(IOC分析、IOC设计实现、DI分析、DI实现)

    一.IOC分析 1. IOC是什么? IOC:Inversion of Control控制反转,也称依赖倒置(反转) 问题:如何理解控制反转? 反转:依赖对象的获得被反转了.由自己创建,反转为从IOC ...

  5. 框架源码系列十一:事务管理(Spring事务管理的特点、事务概念学习、Spring事务使用学习、Spring事务管理API学习、Spring事务源码学习)

    一.Spring事务管理的特点 Spring框架为事务管理提供一套统一的抽象,带来的好处有:1. 跨不同事务API的统一的编程模型,无论你使用的是jdbc.jta.jpa.hibernate.2. 支 ...

  6. 框架源码系列九:依赖注入DI、三种Bean配置方式的注册和实例化过程

    一.依赖注入DI 学习目标1)搞清楚构造参数依赖注入的过程及类2)搞清楚注解方式的属性依赖注入在哪里完成的.学习思路1)思考我们手写时是如何做的2)读 spring 源码对比看它的实现3)Spring ...

  7. 框架源码系列六:Spring源码学习之Spring IOC源码学习

    Spring 源码学习过程: 一.搞明白IOC能做什么,是怎么做的  1. 搞明白IOC能做什么? IOC是用为用户创建.管理实例对象的.用户需要实例对象时只需要向IOC容器获取就行了,不用自己去创建 ...

  8. 框架源码系列十:Spring AOP(AOP的核心概念回顾、Spring中AOP的用法、Spring AOP 源码学习)

    一.AOP的核心概念回顾 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#a ...

  9. 框架源码系列七:Spring源码学习之BeanDefinition源码学习(BeanDefinition、Annotation 方式配置的BeanDefinition的解析)

    一.BeanDefinition 1. bean定义都定义了什么? 2.BeanDefinition的继承体系  父类: AttributeAccessor: 可以在xml的bean定义里面加上DTD ...

随机推荐

  1. 说一下Servlet里面得request和response

    当一个servlet被调用的时候,我们一般继承带协议的httpServlet,大方向上是下图这样 在这里面request和response起了什么作用呢? 来细究一下. request:1.封装了客户 ...

  2. phpExcel导入大数据量情况下内存溢出解决方案

    PHPExcel版本:1.7.6+ 在不进行特殊设置的情况下,phpExcel将读取的单元格信息保存在内存中,我们可以通过 PHPExcel_Settings::setCacheStorageMeth ...

  3. Java 多线程 重入锁

    作为关键字synchronized的替代品(或者说是增强版),重入锁是synchronized的功能扩展.在JDK 1.5的早期版本中,重入锁的性能远远好于synchronized,但从JDK 1.6 ...

  4. UE4入门(一)软件安装教程

    基本的安装流程:下载并安装EpicInstaller=>登陆EpicLauncher并在UNREAL ENGINE标签中下载并安装引擎=>打开引擎创建新项目并测试 一 .下载并安装Epic ...

  5. CSS_选择符

    2016-10-28 <CSS入门经典>第五章 以下提示注意事项: 1.如何选择使用id选择符还是class选择符:当确信id选择符在页面的唯一性时,就可以使用id选择符. 2.通用选择符 ...

  6. Golang LicenseServer授权服务器的设计 与 RSA 密钥对的应用

    //TODO 待写文章 目录: 1.为什么要写授权服务器  LicenseServer 2.授权服务器的设计思路 3.授权服务器所使用到的加密技术 1.为什么要写授权服务器 为了防止别人拿到二进制后, ...

  7. DBS:TestSys

    ylbtech-DBS:TestSys 1.返回顶部 1. -- ============================================= -- 测试系统 -- 2018-4-12 ...

  8. vue项目启动时将localhost替换成指定ip地址

    1.node启动vue项目时地址一般都是http://localhost:8080 2.config->index.js 中的host:‘localhost’换成host:‘你的本机ip’就可以 ...

  9. ionic 锁定方向 禁止横屏 orientation

    安装插件 cordova-plugin-screen-orientation ionic cordova plugin add cordova-plugin-screen-orientation 添加 ...

  10. masstree Seastar

    masstree  Seastar 线程锁竞争和切换的开销几乎为0,代码也不用考虑多线程竞争,逻辑大大减化:此外Niagara是一个全异步执行引擎,采用了基于future,promise和contin ...