代理模式
1.静态代理
a. 角色分析
- 真实角色:被代理的角色;
- 代理角色:代理真实角色,代理真实角色后,一般我们会有附属操作,用来对真实角色的增强;
- 客户端:访问代理对象的人;
b.静态代理的好处
- 可以是真实对象的操作更加简单,不用去关注公共业务;
- 在不修改目标对象的前提下,可以通过代理对象对目标对象功能扩展;
c.静态代理的缺点
- 一个真实角色就需要一个代理角色;代码量翻倍;导致开发效率下降;
- 代理角色和真实角色实现了相同的接口,代理角色通过真实角色实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理角色也需要实现此方法。增加了代码维护的复杂度;
d.静态代理代码实现
-
先创建一个公共的接口Animal,提供一个sleep方法;
/** * @author tsc * @project_name spring * @date 2021/9/13 21:53 */ public interface Animal { void sleep(); }
-
创建一个Dog类真实角色实现Animal接口,重写sleep方法;
/** * @author tsc * @project_name spring * @date 2021/9/13 21:53 */ public class Dog implements Animal { @Override public void sleep() { System.out.println("小狗狗睡觉!!!"); } }
-
创建一个Dog的代理角色DogProxy类;
/** * @author tsc * @project_name spring * @date 2021/9/13 21:54 */ public class DogProxy implements Animal { private Dog dog; public void setDog(Dog dog) { this.dog = dog; } @Override public void sleep() { System.out.println("小狗狗睡觉前执行的代码"); dog.sleep(); System.out.println("小狗狗睡觉后执行的代码"); } }
4.测试
/**
* @author tsc
* @project_name spring
* @date 2021/9/13 22:11
*/
public class App {
public static void main(String[] args) {
DogProxy dogProxy = new DogProxy();
dogProxy.setDog(new Dog());
dogProxy.sleep();
}
}
测试结果
D:\Development\jdk1.8.0_152\bin\java.exe "-javaagent:D:\Development\IntelliJ IDEA
小狗狗睡觉前执行的代码
小狗狗睡觉!!!
小狗狗睡觉后执行的代码
Process finished with exit code 0
2. 动态代理
① jdk代理(接口代理)
前提条件:代理角色不需要实现接口,但是真实角色一定要实现接口,否则不能用动态代理
所谓的jdk代理指的是借助jdk所提供的相关类来实现代理模式,其主要有两个类:java.lang.reflect InvocationHandler和java.lang.reflect Proxy。在实现代理模式时,只需要实现InvocationHandler接口即可;
-
Proxy
提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- ClassLoader loader:指定当前真实对象使用类加载器,获取加载器的方法是固定的
- Class<?>[] interfaces:真实对象实现的接口的类型,使用泛型方式确认类型(接口字节码对象的数组)
- InvocationHandler h:调度方法,执行真实角色的方法时,会触发事件处理器的方法,会把当前执行真实角色的方法作为参数传入
-
InvocationHandler
是由代理实例的调用处理程序实现的接口 。每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的
invoke
方法。Object invoke(Object proxy, Method method, Object[] args)
- Object proxy:代理角色实例;
- Method method:真实角色执行的方法对象;
- Object[] args:真实角色执行的参数列表;
a.创建一个Ihadoop接口;
/** * @author tsc * @project_name spring * @date 2021/9/13 10:57 */ public interface Ihadoop { void eating(String name); }
b.创建一个真实角色并实现Ihadoop接口;
/** * @author tsc * @project_name spring * @date 2021/9/13 10:58 */ public class Hadoop implements Ihadoop { @Override public void eating(String name) { System.out.println("大象再吃"+name); try { Thread.sleep(3000);//给点延迟方便测试 } catch (InterruptedException e) { e.printStackTrace(); } } }
c.测试
import com.gec.targect.Ihadoop; import com.gec.targect.impl.Hadoop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author tsc * @project_name spring * @date 2021/9/13 10:58 */ /** * 利用jdk动态代理实现对真实对象执行一个方法的耗时情况; * 本身Hdoop的eat()方法是没有计算耗时情况的; */ public class MainApp { public static void main(String[] args) { Ihadoop hadoopProxy = (Ihadoop) Proxy.newProxyInstance(Hadoop.class.getClassLoader()//获取类加载器 ,new Class[]{Ihadoop.class}//获取真实对象实现的接口的类型 , new InvocationHandler() {//匿名内部类实现调度方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); Object res = method.invoke(new Hadoop(), args); long endTime = System.currentTimeMillis(); long time = endTime - startTime; System.out.println("耗时" + time); return res; } } ); hadoopProxy.eating("香蕉"); } }
lambda表达式等价写法
import com.gec.targect.Ihadoop; import com.gec.targect.impl.Hadoop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author tsc * @project_name spring * @date 2021/9/13 10:58 */ /** * 利用jdk动态代理实现对真实对象执行一个方法的耗时情况; * 本身Hdoop的eat()方法是没有计算耗时情况的; */ public class MainApp { public static void main(String[] args) { //匿名内部类实现调度方法 Ihadoop hadoopProxy = (Ihadoop) Proxy.newProxyInstance(Hadoop.class.getClassLoader()//获取类加载器 ,new Class[]{Ihadoop.class}//获取真实对象实现的接口的类型 , (proxy, method, args1) -> { long startTime = System.currentTimeMillis(); Object res = method.invoke(new Hadoop(), args1); long endTime = System.currentTimeMillis(); long time = endTime - startTime; System.out.println("耗时" + time); return res; } ); hadoopProxy.eating("香蕉"); } }
jdk动态代理后的结果
D:\Development\jdk1.8.0_152\bin\java.exe "-javaagent:D:\Development\IntelliJ IDEA 大象再吃香蕉 耗时3003 Process finished with exit code 0
输出的耗时时间就是对真实角色的eat()方法的增强;本身真实角色Hadoop只是普通的吃方法没有计算耗时功能;
② Cglib代理(子类代理)
a.前提条件
1.需要引入cglib的jar文件;
2.代理的类不能为final,否则报错;
3.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
b.Cglib概述
Cglib代理是功能最为强大的一种代理方式,因为其不仅解决了静态代理需要创建多个代理类的问题,还解决了jdk代理需要被代理对象(真实角色)实现某个接口的问题。,根据Cglib实现原理,由于其是通过创建子类字节码的形式来实现代理的,如果被代理类的方法被声明final类型,那么Cglib代理是无法正常工作的,因为final类型方法不能被重写;它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
c.代码实现Cglib代理
a.创建一个真实角色(被代理对象)Hadoop1;
/**
* @author tsc
* @project_name spring
* @date 2021/9/13 11:38
*/
public class Hadoop1 {
public static void eating(String name) {
System.out.println("大象再吃"+name);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
b.编写cglib动态代理并测试
import com.gec.cglib.targect.Hadoop1;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author tsc
* @project_name spring
* @date 2021/9/13 11:39
*/
/**
* 利用jdk动态代理实现对真实对象执行一个方法的耗时情况;
* 本身Hdoop1的eat()方法是没有计算耗时情况的;
*/
public class MainApp {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();//创建一个Cglib的执行对象
enhancer.setSuperclass(Hadoop1.class);//设置真实角色的字节码对象
enhancer.setCallback(new MethodInterceptor() {//设置方法拦截器对象
//Object o 真实角色的子类(代理角色)
//Method method 代理角色的方法对象
// Object[] objects 共用的方法参数对象
//MethodProxy methodProxy 真实对象的方法对象
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long startTime = System.currentTimeMillis();
//方法一 method.invoke 实际上是调用代理对象的方法,此时我们需要传入一个Hadoop1对象
//Object res = method.invoke(new Hadoop1(), objects);
//方法二 methodProxy.invokeSuper 实际上调用的是传入对象的父类的方法 ,此时我们传入代理对象(因为代理对象就是真实对象的子类)这种效率高且节省内存(推荐这一种)
Object res = methodProxy.invokeSuper(o, objects);
long endTime = System.currentTimeMillis();
long time = endTime - startTime;
System.out.println("耗时" + time);
return res;
}
});
Hadoop1 o = (Hadoop1) enhancer.create();
o.eating("苹果");
}
}
结果展示
D:\Development\jdk1.8.0_152\bin\java.exe "-javaagent:D:\Development\IntelliJ IDEA
大象再吃苹果
耗时3010
Process finished with exit code 0
动态代理优缺点:
- 优点:灵活易用,不用手动创建代理类。
- 缺点:如果频繁创建代理类,可能会让metaspace空间不足,从而触发频繁FullGC,因此如果我们需要自己用到动态代理时,一般将创建出来的代理对象进行缓存复用。