ByxAOP是一个基于JDK动态代理的简易AOP框架,具有以下功能特性:
- 对目标对象的特定方法进行拦截和增强
- 支持灵活的拦截规则和自定义拦截规则
- 动态实现接口和批量实现接口方法
- 灵活的对象代理机制
使用示例
首先来通过一个简单例子快速了解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接口是怎么实现的吗?)。关于这部分内容,请看: