文章目录
1 代理
代理是一种非侵入式更新软件的手段,可以通过代理为原本要执行的方法添加更多的逻辑和功能,且不会对原先的代码产生影响
考虑如下一个场景:
在一个学生信息管理网站中,教师登录和学生登录以及管理人员登录后,需要显式不同的页面,但是进入网站的第一步都是需要先点击“登录”,在后台的登录方法中,虽然可以在登录方法执行前,进行一系列的逻辑判断,但是这会导致软件的逻辑变得复杂,维护性变差,这时通过代理,可以在不影响源码的情况下,对软件的功能进行更新迭代
所谓代理,就是不直接执行方法,而是交给某个对象在预期的时间去执行该方法,最大的好处就是在执行该方法前后,可以增加一些代码来为该方法增加更多的功能,且不影响源码
2 JDKProxy
代理是一个对象,可以通过代理来执行被代理的类所含的方法
JDKProxy是JDK提供的一种代理,它基于接口,本质是Interface类型的实例,即就是被代理的类中的抽象方法所在的接口
2.1 获取JDKProxy
/**
* @author 雫
* @date 2021/2/18 - 18:02
* @function 产生JDK代理
*/
public class AboutJDKProxy {
public AboutJDKProxy() {}
/**
* @Author 雫
* @Description 通过对象实例生成JDK代理
* @Date 2021/2/18 18:08
* @Param [object]
* @return T
**/
@SuppressWarnings("all")
public static <T> T getJDKProxy(Object object) {
Class<?> klass = object.getClass();
ClassLoader classLoader = klass.getClassLoader();
Class<?> [] interfaces = klass.getInterfaces();
return (T) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前,添加前置拦截");
Object result = method.invoke(object, args);
System.out.println("方法执行后,添加后置拦截");
return result;
}
});
}
/**
* @Author 雫
* @Description 重载方法,通过Class对象生成JDK代理
* @Date 2021/2/18 18:08
* @Param [klass]
* @return T
**/
public static <T> T getJDKProxy(Class<?> klass) throws IllegalAccessException, InstantiationException {
return getJDKProxy(klass.newInstance());
}
}
获得一个JDK代理需要三个参数:
1,classLoader 被代理类的类加载器
2,interfaces 被代理类所实现的所有接口
3,new InvocationHandler() 本质是一个接口,它可以监听到方法何时被调用
通过在内部类增加代码,可以进行"前置拦截"和"后置拦截"来增加更多功能
也可以什么都不做,直接执行代码,类似于窗口编程中的监听器
2.2 JDKProxy测试
测试需要的接口和实现该接口的类:
为JDKProxy添加简单的拦截:
获取JDKProxy:
/**
* @author 雫
* @date 2021/2/8 - 15:59
* @function 代理测试
*/
public class ProxyTest {
public static void main(String[] args) {
//根据静态方法获得被代理的类的JDK代理,这个代理的类型是将要被执行的方法所在的接口类型
IMyProxy jdkProxy = JDKProxy.getJDKProxy(new MyImplement());
/*这里就是通过JDK代理执行了被代理的类所实现的接口中的方法,在该方法被指向前和执行后都可以添加一些代码来执行
* 通过生成JDK代理时所需要实现的InvocationHandler(),就可以在执行该方法前后完成 "过滤/筛选/拦截/完善"*/
jdkProxy.doFirstThing("abcd");
jdkProxy.doSecondThing(10, "abcd");
}
}
测试结果:
可以看到:通过代理,一个接口直接执行了接口的实现类中的方法
通过这样的方式,可以给方法增加额外的参数以便方法执行前识别拦截,执行完方法后对结果进行补充进行后置拦截
3 CGLibProxy
CGLib代理是是被代理类的子类,即把被代理的类作为代理的父类,这样子类(代理)就能调用父类(被代理类)中的方法
3.1 获取CGLib代理
需要jar包
/**
* @author 雫
* @date 2021/2/18 - 18:30
* @function 产生CGLib代理
*/
public class AboutCGLibProxy {
public AboutCGLibProxy() {}
/**
* @Author 雫
* @Description 根据对象生成CGLib代理
* @Date 2021/2/18 18:35
* @Param [object]
* @return T
**/
@SuppressWarnings("all")
public static <T> T getCGLibProxy(Object object) {
Class<?> klass = object.getClass();
/*这里的enhancer就是代理,将被代理的类作为了代理的父类
* 从而让代理间接调用父类中的方法*/
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(klass);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("添加前置拦截");
Object result = method.invoke(object, objects);
System.out.println("添加后置拦截");
return result;
}
});
return (T) enhancer.create();
}
/**
* @Author 雫
* @Description 重载方法,根据Class对象生成CGLib代理
* @Date 2021/2/18 18:36
* @Param [klass]
* @return T
**/
public static <T> T getCGLibProxy(Class<?> klass) throws IllegalAccessException, InstantiationException {
return getCGLibProxy(klass.newInstance());
}
}
3.2 CGLib代理测试
测试需要的被代理类:
测试结果:
4 添加拦截器
现在虽然可以通过代理来反射执行方法,但是却没有实用意义,因为前置拦截 和后置拦截 过于简单,实际应用中更多的是调用配置好的方法,而不是简单的输出,下面在方法执行前后执行配置的方法
以CGLib代理为例,CGLib代理可以执行被代理类中的方法,每个方法都需要不同的前置拦截方法和后置拦截方法,这些方法应该是配置好的,因此建立一个HashMap,键取被代理类的方法名,值取该方法对应的前置拦截方法和后置拦截方法具体实现类
前置拦截和后置拦截需要的接口:
前置拦截和后置拦截接口的适配器:
对于每个方法都需要一对前置拦截方法和后置拦截方法,让所有实现类继承InterceptAdapter,并作为值存储在HashMap中,键则采用方法名
通过MethodFactory可以获取被代理类的所有方法作为键,该方法对应的拦截方法所在类作为值,在使用前通过setMethodIntercept方法将具体的实现类作为值替换掉空适配器
代理执行方法前进行判断,根据方法名从MethodFactory中取出对应的前置拦截和后置拦截方法并执行,从而让代理工作功能更强