代理模式是23种设计模式中的一种,代理模式就是给一个对象提供一个代理,就像我们生活中的中介(租房、留学、相亲),由代理对象控制原目标对象的访问,所以在代理对象中就可以实现对目标对象功能的增强、扩展、甚至改写。
因为一个良好的设计不应该被轻易的修改,这正是开闭原则的体现:一个良好的设计应该对修改关闭,对扩展开放。而代理正是为了扩展类而存在,他可以控制对现有类(也就是被代理的类)的访问,通俗的说就是可以拦截对现有类方法的调用并做出响应的处理。
代理模式的实际应用场景:aop、权限控制、服务监控、缓存、日志、限流、事务、拦截过滤等。
静态代理
和动态代理
静态代理就是我们自己手写代理类;aspect静态代理(编译器生成代理类)。静态代理:(目标接口、目标接口实现、代理类)。
// 创建目标接口TargetInterface
public interface TargetInterface {
void sayHello(String name);
void sayThanks(String name);
}
// 创建目标接口的实现类
public class TargetIntegerfaceImpl implements TargetInterface{
@Override
public void sayHello(String name) {
System.out.println("sayHello: " + name);
}
@Override
public void sayThanks(String name) {
System.out.println("sayThanks: " + name);
}
}
// 创建测试类
public class proxy_static_test {
public static void main(String[] args) {
// 1. 通过创建接口的实现类, 来代理该接口
TargetInterface targetInterface = new TargetIntegerfaceImpl();
targetInterface.sayHello("张三");
targetInterface.sayThanks("李四");
}
}
// 创建静态代理对象TargetProxy
public class TargetProxy implements TargetInterface {
private TargetInterface targetInterface;
public TargetProxy(TargetInterface targetInterface) {
this.targetInterface = targetInterface;
}
@Override
public void sayHello(String name) {
// 使用代理来达到对目标方法增强
System.out.println("start..........");
// 中间调用目标接口的真正实现
targetInterface.sayHello(name);
System.out.println("end..........");
}
@Override
public void sayThanks(String name) {
// 使用代理来达到对目标方法增强
System.out.println("start..........");
// 中间调用目标接口的真正实现
targetInterface.sayThanks(name);
System.out.println("end..........");
}
}
// 创建测试类
public class proxy_static_test {
public static void main(String[] args) {
// 2. 使用静态代理的方法来调用接口中的方法
/*
创建静态代理实例TargetProxy, 传入一个该接口的实现类, 这样静态代理Proxy 对象就已经被我们创建好了, 它含有目标接口的引用,
同时也含有目标接口的具体实现类, 当我们想调用接口中的方法时, 直接去找代理对象就可以了, 这个步骤中代理对象就可以对我们的方法进行增强.
*/
TargetProxy targetProxy = new TargetProxy(new TargetIntegerfaceImpl());
targetProxy.sayHello("王五");
targetProxy.sayHello("赵六");
}
}
底层采用的是Java反射技术,获取真正的代理类,代理对象会在jvm 中进行创建,通过指定接口的ClassLoader 来创建一个指定接口的代理对象,代理类会实现InvocationHandler
接口,来拦截我们的接口请求,在invoke()
中做增强与请求转发处理:
@SuppressWarnings("unchecked")
public <T> T getProxy(Class interfaces) {
// 1. jvm 内存中生成一个class 类
// 2. 根据该class 类反射一个代理对象 $Proxy@
return (T) Proxy.newProxyInstance(interfaces.getClassLoader(), new Class<?>[] {interfaces}, this);
}
/**
* invoke() 是 InvocationHandler 接口中代理的拦截方法, 此处覆盖该方法, 让它对目标接口的方法进行拦截
*
* @param proxy 这个就是我们的代理类, 就是jdk 生成的那个以 $Proxy. 开头的代理类
* @param method interface.method() 目标接口中的方法
* @param args interface.method(args) 目标接口中的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("TargetProxy 前置增强.....");
// 调用目标接口中的方法
Object result = method.invoke(target, args);
System.out.println("TargetProxy 后置增强.....");
return result;
}
使用CGLIB实现动态代理,完全不受代理类必须实现接口的限制,CGLIB底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK8之前,CGLIB动态要比JDK的动态代理(JDK使用的是Java反射)效率要高。唯一需要注意的是,如果被代理的类被final修饰,那么它将不可被继承,即不可被代理,同样,如果被代理的类中存在final修饰的方法,那么该方法也不可被代理。因为CGLIB原理是动态生成被代理类的子类。
CGLIB原理:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,它比Java反射的JDK动态代理要快。
CGLIB底层:使用字节码处理器框架ASM,来转换字节码并生成新的类。不推荐直接使用ASM,因为他要求你必须对JVM内部结构,包括class文件的格式和属性都很熟悉。
CGLIB缺点:对于final类与final方法,CGLIB无法进行代理。
CGLIB在代理接口与代理实体类时有不同的处理代理逻辑,代理类实现MethodInterceptor
接口,来拦截我们的接口请求,在intercept()
中做增强与请求转发处理:
/**
* cglib动态代理关键代码, 获取真正的cglib 动态代理类
* 1. jvm 内存中生成一个class 类
* 2. 根据该class 类反射创建一个代理对象 $Proxy@564546548
* @param tClass
* @param
* @return
*/
public <T> T getProxy(Class<T> tClass) {
// 字节码增强工具类
Enhancer enhancer = new Enhancer();
// 实体类代理 -> 设置父类
enhancer.setSuperclass(tClass);
// 接口代理 -> 设置接口
// enhancer.setInterfaces(new Class[] {tClass});
// 设置回调类
enhancer.setCallback(this);
//创建代理类
return (T) enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib 代理增强开始......");
// 如果是代理类则直接调用目标方法
Object result = methodProxy.invokeSuper(obj, args);
// 如果是代理接口则需要像mybatis一样自己实现接口
// System.out.println("CGLIB代理接口自己实现啦啦啦:方法参数含有+");
// Arrays.stream(args).forEach(System.out::println);
System.out.println("cglib 代理增强结束......");
return result;
}
javassist与JDK代理类似,它是通过代理工厂来创建代理对象的,代理类实现MethodHandler
接口,来拦截我们的接口请求,在invoke()
中做增强与请求转发处理:
public Object getProxy(Class<?> tClass) throws InstantiationException, IllegalAccessException {
// 代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
// 设置需要创建子类的父类
proxyFactory.setSuperclass(tClass);
// 通过字节码技术动态创建子类实例
proxyFactory.writeDirectory = "D:\\software\\data\\idea\\mybatis-3-mybatis-3.5.4";
Object proxy = proxyFactory.createClass().newInstance();
// 在调用目标方法之前, Javassist会回调MethodHandler接口方法拦截, 在其中可以实现自己的代理增强方法, 类似JDK中的InvocationHandler
((ProxyObject) proxy).setHandler(this);
// 返回代理对象
return proxy;
}
@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
System.out.println("Javassist 事务开始.....");
// 调用目标类中的方法
Object result = proceed.invoke(self, args);
System.out.println("Javassist 事务结束.....");
return result;
}