文章目录
- 目标
- 设计
- 项目结构
- 一、代理方法的案例
- 二、代理方法案例拆解
- 实现
- 1、切点表达式——Pointcut
- 2、类匹配器——ClassFilter
- 3、方法匹配器——MethodMatcher
- 4、实现切点表达式类——AspectJExpressionPointcut
- 4.1、匹配验证
- 5、包装切面信息——AdvisedSupport
- 5.1、被代理的目标对象——TargetSource
- 6、反射方法调用的包装类——ReflectiveMethodInvocation
- 7、 代理抽象——AopProxy
- 7.1、JDK代理实现——JdkDynamicAopProxy
- 7.2、Cglib代理实现——Cglib2AopProxy
- 测试
- 1、事先准备
- 2、自定义拦截方法
- 3、单元测试
- 总结
目标
在软件行业,AOP 意为:面向切面编程,通过预编译的方式和运行期间动态代理实现程序功能功能的统一维护。
在 Spring 框架中是一个非常重要的内容,使用 AOP 可以对业务逻辑的各个部分进行隔离,从而使各模块间的业务逻辑耦合度降低,提高代码的可复用性,同时也能提高开发效率。
关于 AOP 的核心技术实现主要是动态代理的使用,就像你可以给一个(IUserService )接口的实现类,使用代理的方式替换掉这个实现类,使用代理类来处理你需要的逻辑。比如
public interface IUserService {
String queryUserInfo();
String register(String userName);
}
用代理类处理自定义的逻辑
@Test
public void test_proxy_class() {
IUserService userService = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{IUserService.class},
(proxy, method, args) -> "你被代理了!");
String result = userService.queryUserInfo();
System.out.println("测试结果:" + result);
}
本章的目标是给方法做代理,而不是代理类。所以需要思考如何给符合规则的方法做代理?如果能代理所有类的方法,则可以做一个方法拦截器,给所有被代理的方法添加自定义处理,如打印日志、监控异常等。
设计
1、如何给符合规则的方法做代理
2、做完代理方法的案例后,把类的职责拆分出来
而这两个功能点的实现,都是以切面的思想进行设计和开发
简单的说就是,只处理一些需要被拦截的方法。在拦截方法后,执行你对方法的扩展操作。
那么我们就需要先来实现一个可以代理方法的 Proxy,其实代理方法主要是使用到方法拦截器类处理方法的调用 MethodInterceptor#invoke,而不是直接使用 invoke 方法中的入参 Method method 进行 method.invoke(targetObj, args) 这块是整个使用时的差异。
除了以上的核心功能实现,还需要使用到 org.aspectj.weaver.tools.PointcutParser 处理拦截表达式 “execution(* springframework.test.bean.IUserService.*(…))”,有了方法代理和处理拦截,我们就可以完成设计出一个 AOP 的雏形了。
项目结构
主要分为几块内容
1、AopProxy 是代理的抽象对象,它的实现主要是基于 JDK 的代理和 Cglib 代理
2、方法匹配、类匹配、切点表达式的实现(AspectJExpressionPointcut 的核心功能主要依赖于 aspectj 组件并处理 Pointcut、ClassFilter,、MethodMatcher 接口实现,专门用于处理类和方法的匹配过滤操作。)
3、切面的包装类、反射方法调用的包装类、被代理的目标对象等
一、代理方法的案例
在实现 AOP 的核心功能之前,我们先做一个代理方法的案例,通过这样一个可以概括代理方法的核心全貌,可以让更好的理解,后续拆解各个方法,设计成解耦功能的 AOP 实现过程。
测试需要的代码
public interface IUserService {
String queryUserInfo();
String register(String userName);
}
public class UserService implements IUserService {
public String queryUserInfo() {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "ljc,100001,上海";
}
public String register(String userName) {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "注册用户:" + userName + " success!";
}
}
单元测试
/**
* @desc: 代理方法的核心实现
**/
@Test
public void test_proxy_method() {
// 目标对象(可以替换成任何的目标对象)
Object targetObj = new UserService();
// AOP 代理
IUserService proxy = (IUserService) java.lang.reflect.Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
targetObj.getClass().getInterfaces(),
new InvocationHandler() {
// 方法匹配器
MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* springframework.test.bean.IUserService.*(..))");
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (methodMatcher.matches(method, targetObj.getClass())) {
// 方法拦截器
MethodInterceptor methodInterceptor = invocation -> {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
System.out.println("监控 - Begin By AOP");
System.out.println("方法名称:" + invocation.getMethod().getName());
System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("监控 - End\r\n");
}
};
// 反射调用
return methodInterceptor.invoke(new ReflectiveMethodInvocation(targetObj, method, args));
}
return method.invoke(targetObj, args);
}
});
String result = proxy.queryUserInfo();
System.out.println("测试结果:" + result);
}
首先整个案例的目标是给一个 UserService 当成目标对象,对类中的所有方法进行拦截添加监控信息打印处理。
从案例中你可以看到
有代理的实现 Proxy.newProxyInstance,
有方法的匹配 MethodMatcher,
有方法拦截器 MethodInterceptor ,
有反射的调用 invoke(Object proxy, Method method, Object[] args),
也有用户自己拦截方法后的操作。
这样一看其实和我们使用的 AOP 就非常类似了,只不过你在使用 AOP 的时候是框架已经提供更好的功能,这里是把所有的核心过程给你展示出来了。
测试结果
监控 - Begin By AOP
方法名称:queryUserInfo
方法耗时:87ms
监控 - End
测试结果:ljc,100001,上海
Process finished with exit code 0
从测试结果可以看到我们已经对 UserService#queryUserInfo 方法进行了拦截监控操作,其实后面我们实现的 AOP 就是现在体现出的结果,只不过我们需要把这部分测试的案例解耦为更具有扩展性的各个模块实现
二、代理方法案例拆解
上述代码执行顺序
1、创建被代理目标对象
2、创建代理对象,并实现InvocationHandler#invoke(用户自定义处理业务逻辑)
3、创建方法匹配器,如果方法名匹配,则进行发射调用,执行自定义的业务代码执行
4、方法不匹配,则执行被代理的方法调用
接下来需要做的事情,对上述代码进行解耦
1、我们需要把代理对象拆解出来,因为它可以是 JDK 的实现也可以是 Cglib 的处理。
2、方法匹配器操作其实已经是一个单独的实现类了,不过我们还需要把传入的目标对象、方法匹配、拦截方法,都进行统一的包装,方便外部调用时进行一个入参透传。
3、最后其实是 ReflectiveMethodInvocation(反射方法的调用) 的使用,它目前已经是实现 MethodInvocation 接口的一个包装后的类,参数信息包括:调用的对象、调用的方法、调用的入参。
实现
1、切点表达式——Pointcut
/**
* @desc 切点表达式接口定义
*/
public interface Pointcut {
/**
* 返回这个切入点的类过滤器。
*/
ClassFilter getClassFilter();
/**
* 返回这个切入点的方法匹配器
*/
MethodMatcher getMethodMatcher();
}
切入点接口,定义用于获取 ClassFilter、MethodMatcher 的两个类,这两个接口获取都是切点表达式提供的内容。
2、类匹配器——ClassFilter
/**
* @desc 定义类匹配类
*/
public interface ClassFilter {
/**
* Should the pointcut apply to the given interface or target class?
* @param clazz the candidate target class
* @return whether the advice should apply to the given target class
*/
boolean matches(Class<?> clazz);
}
定义类匹配类,用于切点找到给定的接口和目标类。
3、方法匹配器——MethodMatcher
/**
* @desc 方法匹配器
*/
public interface MethodMatcher {
/**
* 方法匹配
*/
boolean matches(Method method, Class<?> targetClass);
}
方法匹配,找到表达式范围内匹配下的目标类和方法。在上文的案例中有所体现:methodMatcher.matches(method, targetObj.getClass())
4、实现切点表达式类——AspectJExpressionPointcut
/**
* @desc 实现切点表达式类
*/
public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();
static {
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
}
private final PointcutExpression pointcutExpression;
public AspectJExpressionPointcut(String expression) {
PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
pointcutExpression = pointcutParser.parsePointcutExpression(expression);
}
@Override
public boolean matches(Class<?> clazz) {
return pointcutExpression.couldMatchJoinPointsInType(clazz);
}
@Override
public boolean matches(Method method, Class<?> targetClass) {
return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
}
@Override
public ClassFilter getClassFilter() {
return this;
}
@Override
public MethodMatcher getMethodMatcher() {
return this;
}
}
这个类主要实现了切点表达式实现了 Pointcut、ClassFilter、MethodMatcher,三个接口定义方法,同时这个类主要是对 aspectj 包提供的表达式校验方法使用。
匹配 matches:pointcutExpression.couldMatchJoinPointsInType(clazz) ——类匹配
pointcutExpression.matchesMethodExecution(method).alwaysMatches()——方法匹配
这部分内容可以单独测试验证。
4.1、匹配验证
@Test
public void test_aop() throws NoSuchMethodException {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* springframework.test.bean.UserService.*(..))");
Class<UserService> clazz = UserService.class;
Method method = clazz.getDeclaredMethod("queryUserInfo");
System.out.println(pointcut.matches(clazz));
System.out.println(pointcut.matches(method, clazz));
// 返回结果:true、true
}
这里单独提供出来一个匹配方法的验证测试,可以看看你拦截的方法与对应的对象是否匹配。
5、包装切面信息——AdvisedSupport
/**
* @desc 包装切面通知信息
*/
public class AdvisedSupport {
// 被代理的目标对象
private TargetSource targetSource;
// 方法拦截器
private MethodInterceptor methodInterceptor;
// 方法匹配器(检查目标方法是否符合通知条件)
private MethodMatcher methodMatcher;
// get/set方法
}
AdvisedSupport,主要是用于把被代理的对象、拦截的方法、方法匹配器的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。这和你的业务开发中包装入参是一个道理
TargetSource,是一个目标对象,在目标对象类中提供 Object 入参属性,以及获取目标类 TargetClass 信息。
MethodInterceptor,是一个具体拦截方法实现类,由用户自己实现 MethodInterceptor#invoke 方法,做具体的处理。像我们本文的案例中是做方法监控处理
MethodMatcher,是一个匹配方法的操作,这个对象由 AspectJExpressionPointcut 提供服务
5.1、被代理的目标对象——TargetSource
/**
* @desc: 被代理的目标对象
**/
public class TargetSource {
private final Object target;
public TargetSource(Object target) {
this.target = target;
}
public Class<?>[] getTargetClass(){
return this.target.getClass().getInterfaces();
}
public Object getTarget(){
return this.target;
}
}
被代理的对象的包装类,主要提供参数Object的target,以及获取被代理的接口和获取被代理对象
6、反射方法调用的包装类——ReflectiveMethodInvocation
public class ReflectiveMethodInvocation implements MethodInvocation {
// 目标对象
protected final Object target;
// 方法
protected final Method method;
// 入参
protected final Object[] arguments;
public ReflectiveMethodInvocation(Object target, Method method, Object[] arguments) {
this.target = target;
this.method = method;
this.arguments = arguments;
}
@Override
public Method getMethod() {
return method;
}
@Override
public Object[] getArguments() {
return arguments;
}
@Override
public Object proceed() throws Throwable {
return method.invoke(target, arguments);
}
@Override
public Object getThis() {
return target;
}
@Override
public AccessibleObject getStaticPart() {
return method;
}
}
提供了入参对象:目标对象、方法、入参。
在实现jdk和cglib的时候,调用invoke方法时,会传入
7、 代理抽象——AopProxy
public interface AopProxy {
Object getProxy();
}
定义一个标准接口,用于获取代理类。因为具体实现代理的方式可以有 JDK 方式,也可以是 Cglib 方式,所以定义接口会更加方便管理实现类。
7.1、JDK代理实现——JdkDynamicAopProxy
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
private final AdvisedSupport advised;
public JdkDynamicAopProxy(AdvisedSupport advised) {
this.advised = advised;
}
@Override
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),advised.getTargetSource().getTargetClass(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法名匹配判断,相同则,获取到方法拦截器,进行反射调用
if(advised.getMethodMatcher().matches(method,advised.getTargetSource().getTarget().getClass())){
MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
// 反射调用
return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(),method,args));
}
// 正常调用被代理方法
return method.invoke(advised.getTargetSource().getTarget(),args);
}
}
基于 JDK 实现的代理类,需要实现接口 AopProxy、InvocationHandler,这样就可以把代理对象 getProxy 和反射调用方法 invoke 分开处理了。
getProxy 方法中的是代理一个对象的操作,需要提供入参 ClassLoader、AdvisedSupport、和当前这个类 this,因为这个类提供了 invoke 方法。
invoke 方法中主要处理匹配的方法后,使用用户自己提供的方法拦截实现,做反射调用 methodInterceptor.invoke 。
这里还有一个 ReflectiveMethodInvocation,它就是一个入参的包装信息,提供了入参对象:目标对象、方法、入参
7.2、Cglib代理实现——Cglib2AopProxy
public class Cglib2AopProxy implements AopProxy{
private final AdvisedSupport advised;
public Cglib2AopProxy(AdvisedSupport advised) {
this.advised = advised;
}
// 创建代理对象
@Override
public Object getProxy() {
Enhancer enhancer = new Enhancer();
// 被代理的类
enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass());
// 被代理的类实现的接口类
enhancer.setInterfaces(advised.getTargetSource().getTargetClass());
// 扩展需要拦截的处理
enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
return enhancer.create();
}
// 动态拦截器
private static class DynamicAdvisedInterceptor implements MethodInterceptor{
private final AdvisedSupport advised;
public DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
}
// 动态拦截实现
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
CglibMethodInvocation cglibMethodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(),method,objects,methodProxy);
// 方法匹配则进行拦截处理
if(advised.getMethodMatcher().matches(method,advised.getTargetSource().getTarget().getClass())){
return advised.getMethodInterceptor().invoke(cglibMethodInvocation);
}
return cglibMethodInvocation.proceed();
}
}
// cglib方法调用
private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
private final MethodProxy methodProxy;
public CglibMethodInvocation(Object target, Method method, Object[] arguments, MethodProxy methodProxy) {
super(target, method, arguments);
this.methodProxy = methodProxy;
}
@Override
public Object proceed() throws Throwable {
return this.methodProxy.invoke(this.target,this.arguments);
}
}
}
getProxy的实现:基于 Cglib 使用 Enhancer 代理的类可以在运行期间为接口使用底层 ASM 字节码增强技术,处理对象的代理对象生成,因此被代理类不需要实现任何接口。
关于扩展进去的用户拦截方法,主要是在 Enhancer#setCallback 中处理,用户自己的新增的拦截处理。这里可以看到 DynamicAdvisedInterceptor#intercept 匹配方法后做了相应的反射操作
测试
1、事先准备
public interface IUserService {
String queryUserInfo();
String register(String userName);
}
public class UserService implements IUserService {
public String queryUserInfo() {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "ljc,100001,上海";
}
public String register(String userName) {
try {
Thread.sleep(new Random(1).nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "注册用户:" + userName + " success!";
}
}
在 UserService 中提供了2个不同方法,后面我们的测试过程,会给这个两个方法添加我们的拦截处理,打印方法执行耗时。
2、自定义拦截方法
public class UserServiceInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
System.out.println("监控 - Begin By AOP");
System.out.println("方法名称:" + invocation.getMethod());
System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("监控 - End\r\n");
}
}
}
用户自定义的拦截方法需要实现 MethodInterceptor 接口的 invoke 方法,使用方式与 Spring AOP 非常相似,也是包装 invocation.proceed() 放行,并在 finally 中添加监控信息。
3、单元测试
@Test
public void test_dynamic() {
// 目标对象
IUserService userService = new UserService();
// 组装代理信息
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTargetSource(new TargetSource(userService));
advisedSupport.setMethodInterceptor(new UserServiceInterceptor());
advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* springframework.test.bean.IUserService.*(..))"));
// 代理对象(JdkDynamicAopProxy)
IUserService proxyJdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy();
// jdk测试调用
System.out.println("测试结果:" + proxyJdk.queryUserInfo());
System.out.println();
// 代理对象(Cglib2AopProxy)
IUserService proxyCglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy();
System.out.println("测试结果:" + proxyCglib.register("yaya"));
}
整个案例测试了 AOP 在于 Spring 结合前的核心代码,包括什么是目标对象、怎么组装代理信息、如何调用代理对象。
AdvisedSupport,包装了目标对象、用户自己实现的拦截方法以及方法匹配表达式。
之后就是分别调用 JdkDynamicAopProxy、Cglib2AopProxy,两个不同方式实现的代理类,看看是否可以成功拦截方法
测试结果
监控 - Begin By AOP
方法名称:public abstract java.lang.String springframework.test.bean.IUserService.queryUserInfo()
方法耗时:87ms
监控 - End
测试结果:ljc,100001,上海
监控 - Begin By AOP
方法名称:public java.lang.String springframework.test.bean.UserService.register(java.lang.String)
方法耗时:95ms
监控 - End
测试结果:注册用户:yaya success!
如 AOP 功能定义一样,我们可以通过这样的代理方式、方法匹配和拦截后,在对应的目标方法下,做了拦截操作进行监控信息打印。
总结
到这里AOP的切面实现已经完成了,只不过还没有和spring结合。
也可以看到JDK的代理实现,需要实现AopProxy, InvocationHandler,实现AopProxy#getProxy和InvocationHandler#invoke,jdk的代理生成主要通过Proxy.newProxyInstance。
而cglib的代理实现,只需要实现AopProxy#getProxy,getProxy通过Enhancer(ASM字节码增强技术动态)生成对象,
用户的拦截器扩展,则是通过setCallback,设置需要执行的自定义方法拦截器
内部类DynamicAdvisedInterceptor(动态拦截器),通过实现MethodInterceptor#intercept(方法拦截器),来进行判断进行匹配
内部类CglibMethodInvocation(cglib方法执行),继承ReflectiveMethodInvocation(反射方法调用包装类),调用执行
切面实战逻辑代码其实并不多,主要是进行了解耦,进行了各种类的拆分