ByxAOP是一个基于JDK动态代理的简易AOP框架,具有以下功能特性:
- 对目标对象的特定方法进行拦截和增强
- 支持灵活的拦截规则和自定义拦截规则
- 动态实现接口和批量实现接口方法
- 灵活的对象代理机制
项目地址:github 码云
使用示例
首先来通过一个简单例子快速了解ByxAOP。
假设我们有一个UserDao接口:
public interface UserDao { int listAll(); int listById(int id); void deleteByName(String name); }
UserDao接口有一个UserDaoImpl实现类:
public class UserDaoImpl implements UserDao { @Override public int listAll() { System.out.println("正在执行listAll方法"); return 123; } @Override public int listById(int id) { System.out.println("正在执行listById方法:id = " + id); return 456; } @Override public void deleteByName(String name) { System.out.println("正在执行deleteByName方法:name = " + name); } }
现在我们来完成一个需求:拦截UserDaoImpl中所有以list开头的方法,即listAll和listById方法,拦截过程中打印出目标方法的一些相关信息。使用ByxAOP来完成这个需求,只需要三个步骤:
第一步:创建一个方法拦截器(MethodInterceptor):
MethodInterceptor interceptor = (signature, targetMethod, params) -> { System.out.println("开始拦截" + signature.getName() + "方法"); System.out.println("原始参数:" + Arrays.toString(params)); Object ret = targetMethod.invoke(params); System.out.println("原始返回值:" + ret); System.out.println("结束拦截" + signature.getName() + "方法"); return ret; };
MethodInterceptor是ByxAOP中用来表示方法拦截器的核心接口,signature是目标方法签名,targetMethod用于调用目标方法,params是传递给目标方法的参数。在interceptor中,首先打印了拦截开始信息、目标方法的方法名和原始参数,然后调用了目标方法并获取返回值,最后打印了返回值和拦截结束信息。
关于MethodInterceptor接口及其相关方法和参数的详细介绍,请看下文。
第二步:创建一个方法匹配器(MethodMatcher):
MethodMatcher matcher = withPattern("list(.*)").andReturnType(int.class);
MethodMatcher的构建使用了一种被称为“流畅接口”的API设计风格,通过链式调用以及特定的命名来达到很好的可读性。withPattern匹配方法名满足指定正则表达式的方法,andReturnType则在上一个匹配器的基础上增加了返回值类型的条件。整个matcher表示匹配所有方法名以list开头且返回值为int类型的方法。
MethodMatcher接口有很多预定义的实现类,通过组合这些类可以非常灵活地配置拦截规则,请看下文的详细介绍。
第三步:创建AOP代理对象:
UserDao userDao = proxy(new UserDaoImpl(), interceptor.when(matcher));
proxy方法是AOP类中的一个静态方法,用于创建AOP代理对象。第一个参数传入目标对象(被增强的对象),第二个参数传入方法拦截器。对于目标对象中的每一个方法调用,都会被该方法拦截器拦截。不过请注意,这里我们用interceptor.when(matcher)指定了拦截条件。由于match匹配以list开头且返回值为int类型的方法,所以只有满足这个条件的方法才会被intercept拦截。
到这里,代理对象userDao就被创建出来了,现在让我们来调用一下userDao中的各个方法:
userDao.listAll(); System.out.println(); userDao.listById(1001); System.out.println(); userDao.deleteByName("XiaoMing");
控制台输出如下:
开始拦截listAll方法 原始参数:null 正在执行listAll方法 原始返回值:123 结束拦截listAll方法 开始拦截listById方法 原始参数:[1001] 正在执行listById方法:id = 1001 原始返回值:456 结束拦截listById方法 正在执行deleteByName方法:name = XiaoMing
从输出结果可以看到,UserDaoImpl中的listAll方法和listById方法都被增强了,相应的信息也打印出来了,而deleteByName方法没有被增强。
下面将介绍ByxAOP的设计。
MethodInterceptor接口
首先来介绍一下MethodInterceptor接口,它是ByxAOP中的核心接口之一。
MethodInterceptor即方法拦截器,是对方法拦截过程的封装,它的定义如下:
public interface MethodInterceptor { Object intercept(MethodSignature signature, Invokable targetMethod, Object[] params); }
当目标方法被拦截时,intercept方法将会被调用,同时传入一些目标方法的相关信息。intercept方法的返回值将作为代理方法的返回值。
signature是目标方法签名,用于在拦截时获取目标方法的签名信息。
signature是MethodSignature接口的实现类,MethodSignature接口包含下列方法:
方法 说明 getName 获取方法名 getReturnType 获取返回值类型 getParameterTypes 获取参数类型 getAnnotation 获取方法的指定注解 getAnnotation 获取方法上的指定注解 getAnnotations 获取方法上的所有注解 hasAnnotation 方法是否被某个注解标注 getParameterAnnotations 获取方法参数上的注解 isPublic 是否为public方法 isPrivate 是否为private方法 isProtected 是否为protected方法 targetMethod是目标方法调用器,用于在拦截时调用目标方法,向目标方法传递参数,以及获取目标方法的返回值
targetMethod是Invokable接口的实现类,Invokable接口的定义如下:public interface Invokable { Object invoke(Object... params); }
params是传递给目标方法的原始参数,即我们调用代理对象的方法时传递的参数
用户通过实现MethodInterceptor接口来定制方法拦截过程。在实现类的intercept方法中,用户可以对目标方法的参数进行增强,也可以对目标方法的返回值进行增强,还可以对目标方法抛出的异常进行处理,以及其它任何能想象到的拦截和增强操作。
还记得proxy方法吗?
public static <T> T proxy(Object target, MethodInterceptor interceptor);
在调用AOP类中的proxy方法创建代理对象时,需要传入一个MethodInterceptor的实现类。当我们调用代理对象的某个方法时,这个方法调用将会被代理到我们传入的MethodInterceptor的intercept方法,同时proxy方法内部会把目标方法的相关信息转换成signature、targetMethod和params这三个参数。
注意,对于代理对象中的每个方法,都会被我们传入的方法拦截器拦截。那么,如何让方法拦截器只拦截某些特定的方法呢?
我们可以在方法拦截器的实现类中通过判断signature信息来选择执行不同操作,就像下面这样:
public class MyInterceptor implements MethodInterceptor { @Override public Object intercept(MethodSignature signature, Invokable targetMethod, Object[] params) { if (signature.getName().startsWith("list") && signature.getReturnType() == int.class) { // 拦截以list开头且返回值类型为int的方法 } // 对于其它方法,则直接调用目标方法,并返回目标方法的返回值 return targetMethod.invoke(params); } }
不过,在绝大多数情况下,我们并不需要通过手动判断signature来拦截特定的方法,因为ByxAOP提供了很多预定义的MethodMatcher来表达方法匹配规则,只需要调用MethodInterceptor的when方法,并将某个MethodMatcher传递进去,就能得到只拦截特定方法的拦截器。
MethodMatcher接口
MethodMatcher是ByxAOP中的另一个核心接口,它封装了方法匹配规则,其定义如下:
public interface MethodMatcher { boolean match(MethodSignature signature); }
signature是方法签名。MethodMatcher的实现类通过判断方法签名是否满足某个条件来决定是否匹配该方法,如果匹配则返回true,否则返回false。
下面是所有预定义的MethodMatcher实现类,它们都是通过MethodMatcher的静态工厂方法来获取:
工厂方法 | 说明 |
---|---|
all | 匹配所有方法 |
withName | 匹配指定名称的方法 |
withPattern | 匹配方法名具有特定模式的方法 |
withReturnType | 匹配具有特定返回值的方法 |
withParameterTypes | 匹配具有指定参数类型的方法 |
existInType | 匹配存在于另一个类型中的方法 |
hasAnnotation | 匹配被指定注解标注的方法 |
and | 匹配同时满足两个匹配条件的方法 |
or | 匹配至少满足两个匹配条件其中之一的方法 |
not | 匹配不满足指定匹配结果的方法 |
通过组合这些自带的MethodMatcher,基本可以表达任意的方法匹配规则。
下面这个matcher用于匹配所有方法名以list开头且返回值为int类型的方法:
MethodMatcher matcher = withPattern("list(.*)").andReturnType(int.class);
下面这个matcher用于匹配所有带有Validate注解且参数类型为String和Integer的方法:
MethodMatcher matcher = hasAnnotation(Validate.class).andParameterTypes(String.class, Integer.class);
当然,如果用户有自己的特殊需求,也可以自定义MethodMatcher的实现类。
多重AOP代理
将MethodInterceptor与MethodMatcher配合使用,还可以完成更复杂的需求。
还是上面UserDaoImpl的例子,假设我们既想拦截所有以list开头的方法,又想拦截delete和insert方法。拦截list方法时需要打印出查询日志,而拦截delete和list方法时需要进行事务管理,这个该如何实现呢?
MethodInterceptor interceptor1 = (signature, targetMethod, params) -> { // 打印日志的拦截器实现... }; MethodInterceptor interceptor2 = (signature, targetMethod, params) -> { // 事务管理的拦截器实现... }; // 匹配所有以list开头的方法 MethodMatcher matcher1 = withPattern("list(.*)"); // 匹配delete方法和insert方法 MethodMatcher matcher2 = withName("delete").or(withName("insert")); // 创建代理对象 UserDao userDao = proxy(new UserDaoImpl(), interceptor1.when(matcher1) .then(interceptor2.when(matcher2)));
在上面的方法中,使用then将两个MethodInterceptor连接起来,从而实现了多重AOP代理。
不仅仅是AOP
事实上,ByxAOP不仅仅是一个AOP框架。在AOP类中还有一个implement方法:
public static <T> T implement(Class<T> type, MethodInterceptor interceptor);
implement方法用于动态实现接口,这个方法配合MethodInterceptor中的delegateTo方法,可以实现很多炫酷的效果(你知道MyBatis中的Mapper接口是怎么实现的吗?)。关于这部分内容,请看:
- 动态实现接口
- 批量实现接口方法
- 优雅地创建适配器