项目背景
由于项目中会需要进行入参日志打印、处理结果打印、权限控制等业务逻辑处理,而这些业务逻辑功能又是多变的,如果都将这些业务逻辑放在主要逻辑之中,那么随着时间的增加,代码会越来越杂。<br />为了降低代码的耦合度,提高代码逻辑辨识。可以使用代理模式去抽离业务逻辑,将业务逻辑独立出来。<br />但是业务逻辑有时候也不会只有一种,例如某接口即需要入参打印,又需要权限处理,那么这类情况最好将不同的业务逻辑也进行拆分。
构建cglib代理
1. 目标方法
假设目标方法长这样:
public class ToolService {
public void run(String[] args) {
System.out.println("执行目标对象方法;args="+ Arrays.toString(args));
try {
Thread.sleep(100); // 模拟调用时长
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2. 子类代理回调的拦截器
如果使用cglib代理,那么需要先引用cglib的依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
那么创建一回调拦截器,实现 MethodInterceptor接口,用于处理子类代理调用方法时回调的逻辑,也即真正代理的逻辑。
public class ToolProxyCallback implements MethodInterceptor {
// 目标对象
private Object target;
public ToolProxyCallback(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 入参日志打印
System.out.println("准备执行目标方法;参数信息为="+args);
// 权限拦截
if(args.length <= 2) { // 假设这里的权限是参数不能小于2个
return null;
}
// 通过反射执行目标方法
Object resultObj = method.invoke(target,args);
// 处理接口打印
System.out.println("目标方法请求结果为;"+resultObj);
return resultObj;
}
}
3. 创建子类代理的工厂
还需要一个工厂来创建子类代理的实例。
public class ToolProxyFactory {
/**
* 获取子类代理实例
* @param target 目标对象
* @param callback 回调接口
* @return
*/
public static Object getProxyInstance(Object target, Callback callback) {
// 工具类
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(target.getClass());
// 设置回调函数。
enhancer.setCallback(callback);
return enhancer.create(); // 创建子类代理对象
}
}
4. 客户端执行调用
那么调用目标方法就变为这样。
@Test
public void testProjectProxy() {
ToolService service = new ToolService();
Callback toolProxyCallback = new ToolProxyCallback(service);
ToolService serviceProxy = (ToolService)ToolProxyFactory.getProxyInstance(service,toolProxyCallback);
/**
* 使用 ToolProxyFactory 构建出 ToolCglibService的代理子类,通过代理子类去调用接口方法
*/
String[] args = new String[]{"-s","test","-n","zhangsan"};
serviceProxy.run(args);
}
这样就可以实现将业务逻辑与核心逻辑抽离开,通过cglib提供的接口实例化出目标对象的子类实例,然后用这个子类实例调用父类的方法,也即我们需要代理的目标方法。
优化一:将代理的职责拆分
但是~这样就ok了嘛?
有木有发现 回调拦截器这个方法 ToolProxyCallback#intercept,存在职责不单一的情况。假设这里的权限拦截有着很复杂的逻辑,又或是这个权限拦截在其他接口需要用到的,那么是不是就需要把这个逻辑给抽离出来呢。
业务小的话是没关系的,但是如果考虑到后续前置/后置逻辑会不断累积,那么就需要将回调拦截器中的业务逻辑按照职责拆分单独出来了。
针对代理拦截的逻辑可以知道,有前置逻辑和后置逻辑需要处理。那么抽象为父类
public abstract class AbstractInterceptor {
/**
* 前置逻辑
* @param target 目标对象
* @param method 目标方法
* @param args 参数
* @throws Exception
*/
public abstract void before(Object target, Method method,Object[] args) throws Exception;
/***
* 后置逻辑
* @param target 目标对象
* @param method 目标方法
* @param result 处理结果
* @throws Exception
*/
public abstract void after(Object target,Method method,Object result) throws Exception;
}
自定义注解Order,用于拦截器调用的先后顺序;
/**
* order 值越小 表示优先级越高
* -1 为默认值,可表示系统拦截
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Order {
int value() default -1;
}
定义LogInterceptor,继承AbstractInterceptor,用于关键日志打印;
@Order(1)
public class LogInterceptor extends AbstractInterceptor {
private long startTime;
private long endTime;
private static Logger logger = Logger.getLogger(LogInterceptor.class.getName());
@Override
public void before(Object target, Method method,Object[] args) {
startTime = System.currentTimeMillis();
logger.info("代理对象方法执行。参数args="+args);
}
@Override
public void after(Object target,Method method,Object result) {
endTime = System.currentTimeMillis();
logger.info("方法执行结束。耗时:" + (endTime - startTime) + "ms");
}
}
定义AuthInterceptor拦截器,用于接口权限拦截;
@Order(2)
public class AuthInterceptor extends AbstractInterceptor {
@Override
public void before(Object target, Method method, Object[] args) throws Exception {
System.out.println("run start..."+ this.getClass());
}
@Override
public void after(Object target, Method method, Object result) throws Exception {
System.out.println("run end..."+this.getClass());
}
}
修改下前面的回调拦截器的方法,新增interceptorList列表,用于存储拦截器,注意这里是先后顺序存储,先后调用。
public class ToolProxyCallback implements MethodInterceptor {
private Object target;
public ToolProxyCallback(Object target) {
this.target = target;
}
private static List<AbstractInterceptor> interceptorList = new LinkedList<>();
// 注册拦截器
public static void registerInterceptor(AbstractInterceptor interceptor) {
interceptorList.add(interceptor);
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 执行拦截器的前置方法
for(AbstractInterceptor interceptor : interceptorList) {
interceptor.before(target,method,args);
}
System.out.println("准备执行目标方法;interceptorList.size="+interceptorList.size());
Object resultObj = method.invoke(target,args);
// 执行拦截器的后置方法
for(AbstractInterceptor interceptor : interceptorList) {
interceptor.after(target,method,resultObj);
}
return resultObj;
}
}
添加拦截器,那么调用ToolProxyCallback#registerInterceptor 方法,将拦截器注册到interceptorList即可。
优化二:自动化注册拦截器
后续如果想要新增拦截器,那么只需要继承AbstractInterceptor就行。但是每次添加一个拦截器都要主动注册一下,还要判断下先后顺序,那就有点麻烦。
新增拦截器注解,通过扫描包,将有该注解的拦截器都注册进来,就可以实现自动化注册了。
/**
* 有该注解表示为自定义拦截器
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ToolAspect {
}
通过扫描包,将拦截器统统注册进来。
public class AnnotationScanner {
private static final String packageName = "agent.project";
/**
* 自动注册工具拦截器
*/
public static void registerToolInterceptorList() {
List<Class> list = ClassUtil.getAllClassByAnnotation(ToolAspect.class,packageName);
// 从小到大排序
Collections.sort(list, new Comparator<Class>() {
@Override
public int compare(Class clazz1, Class clazz2) {
Order order1 = (Order)clazz1.getAnnotation(Order.class);
Order order2 = (Order)clazz2.getAnnotation(Order.class);
return order1.value() - order2.value();
}
});
for(Class clazz : list) {
AbstractInterceptor interceptor = null;
try {
interceptor = (AbstractInterceptor)clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// 注册拦截器
ToolProxyCallback.registerInterceptor(interceptor);
}
}
}
还记得前面的代理工厂吗 ToolProxyFactory。由于我们每次获取子类代理实例的时候都会使用这个类,那么将自动注册的调用放在这里就合适了。
public class ToolProxyFactory {
static {
AnnotationScanner.registerToolInterceptorList(); // 注册拦截器
}
/**
* 获取子类代理实例
* @param target 目标对象
* @param callback 回调接口
* @return
*/
public static Object getProxyInstance(Object target, Callback callback) {
// 工具类
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(target.getClass());
// 设置回调函数。
enhancer.setCallback(callback);
return enhancer.create(); // 创建子类代理对象
}
}
通过上述的优化一、二,实现了将代理职责拆分,同时自动化注册拦截器。在这基础上,客户端调用的方法不变,依旧通过ToolProxyFactory#getProxyInstance方法获取到子类代理,然后执行目标方法。
再扩展优化思考
针对现有的实现架构,留下几个Q:
- 自动化注册拦截器会为所有目标方法实现了所有拦截器,但并不是每个目标方法所需要的拦截器都是相同的,那么就需要为目标方法实现特定的拦截器;
- 获取子类代理实例的逻辑略为繁琐,需要认识的类有点多,能不能实现自动代理;(通过注解或者其它方式)
- 能不能搞成即可使用cglib实现,又可自动切换为接口代理实现;(根据判断目标方法有没目标接口?)
- 对比下Spring Aop有何区别与相同之处;