代理模式简介:
什么是代理模式,代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。这样可以避免大量的代码冗余,比如我们在优化代码的时候,喜欢用该方法执行多少时间来判断是否性能上有提高,这样你每优化一个方法的时候要写两句输出时间的代码,如果使用代理模式,我们可以直接在代理类中统一给所用的方法在调用之前和之后记录时间,这样就不会像之前那样麻烦了。在项目中我们所使用的Spring AOP(面向切面编程),就是代理模式的应用,在Spring AOP 使用了JDK动态代理CGLIB动态代理来实现,对于接口来说就是使用的JDK的动态代理来实现的,而对于类的代理使用CGLIB来实现。再普通点的例子,我们生活中的中介结构,就是一种代理,租房中介,二手车中介等。加入你刚刚换到一个城市,你需要租一个房子,但是你对新环境不熟悉,因此你需要在中介公司上找房子租。他会在你租房子之前,把房子的地理位置,价格都找出来。然后你只要付钱就可以了。代理模式分为两种,一种是动态代理和静态代理。动和静是相对的,那什么是动态代理,什么又是静态代理呢?动态代理和静态代理的区分就是代理类的生成方式。静态代理是我们需要把代理类硬编码出来,动态代理这是在程序运行的过长中生成的代理类。动态代理有两种实现方式,JDK动态代理和CGLIB动态代理。JDK动态代理被代理类需要实现接口,否则无法实现,因此有一定的局限性。而CGLIB动态代理则没有这种局限性。不需要实现接口,也可以实现代理。
上手例子:
(一)静态代理
创建一个接口,被代理类和代理类都实现这个接口,代理类中持有被代理类的引用,然后在实现的接口方法中,调用代理类的方法。
1.公共接口
public interface Person {
public void giveHouse();
}
2.租客实现类
public class Tenant implements Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void giveHouse() {
System.out.println(this.name+"租房子");
}
}
3.租房中介实现类
public class RentalAgency implements Person {
private Person tenant;
public RentalAgency(Person tenant) {
this.tenant = tenant;
}
@Override
public void giveHouse() {
TestFunctionSpendTime.start = System.currentTimeMillis();
System.out.println("谈论价钱");
tenant.giveHouse();
System.out.println("交接钥匙");
TestFunctionSpendTime.end = System.currentTimeMillis();
TestFunctionSpendTime.display();
}
}
4.时间测试类
public class TestFunctionSpendTime {
public static long start;
public static long end;
public static void display() {
System.out.println("执行该方法所需要时间:" + (end - start) + "ms");
}
}
5.测试类
public class staticProxyTest {
public static void main(String[] args) {
Tenant tenant = new Tenant();
tenant.setName("王五");
RentalAgency rentalAgency = new RentalAgency(tenant);
rentalAgency.giveHouse();
}
}
静态代理类总结:静态代理可以很清晰的看懂代理过程,但是如果每一个类都要我们去写一个代理类,会有大量的代码冗余,因此在实际中很少使用。
(二)JDK动态代理
JDK动态代理是利用反射机制在运行时创建代理类的,要是先JDK的动态代理,需要创建一个调用处理器MyInvocationHandler类,需要实现InvocationHandler接口,然后通过Proxy.newProxyInstance()方法创建出代理类的实例。JDK不支持代理实现类,我们可以反编译代理类的class文件。可以看出代理类继承了Proxy然后实现被代理的接口,由于Java只支持单继承,因此已经继承了Proxy就不能再继承其他实现类了。
1.公共接口
public interface Person {
public void giveHouse();
}
2.租客实现类
public class Tenant implements Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void giveHouse() {
System.out.println(this.name+"租房子");
}
}
3.调用处理器
public class TenantHandler<T> implements InvocationHandler {
//持有的被代理类的引用
private T target;
//被代理类赋值
public TenantHandler(final T target) {
this.target = target;
}
//通过代理类去执行代理类的方法时,都先调用此方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
TestFunctionSpendTime.start = System.currentTimeMillis();
System.out.println("谈论价钱");
method.invoke(target,args);
System.out.println("交接钥匙");
TestFunctionSpendTime.end = System.currentTimeMillis();
TestFunctionSpendTime.display();
return null;
}
}
4.测试类
public class JDKDynamicProxyTest {
public static void main(String[] args) {
Tenant tenant = new Tenant();
tenant.setName("王五");
//创建调用处理器
TenantHandler<Person> tenantHandler = new TenantHandler<Person>(tenant);
//生成代理对象
Person rentalAgency = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, tenantHandler);
rentalAgency.giveHouse();
}
}
JDK动态代理总结:JDK动态代理,在程序运行时候生成代理的对象,无需要手动写代理类。但是只能代理接口,不能代理类。这样会减少大量的代码冗余。生成代理对象的过程不是很清晰。
(三)CGLIB动态代理
CGLIB动态代理的工作原理是让生成的代理类继承被代理的类,然后把被代理类的方法继承下来,然后在代理类中对方法进行强化处理,CGLIB底层借助Java字节码生成框架ASM。由于CGLIB是利用java继承这一特征来实现的,因此如果被代理的类或者方法使用final来修饰,则无法实现代理。
1.租客类
public class Tenant{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void giveHouse() {
System.out.println(this.name+"租房子");
}
}
2.方法拦截实现类
public class TenantInterceptor<T> implements MethodInterceptor {
//被代理类对象引用
private T target;
public Object getInstance(T target){
//被代理对象赋值
this.target = target;
//1.创建类加载器,用于生成被代理类
Enhancer enhancer = new Enhancer();
//2.指定被代理类的类型
enhancer.setSuperclass(this.target.getClass());
//3.设置回调
enhancer.setCallback(this);
//4.创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
TestFunctionSpendTime.start = System.currentTimeMillis();
System.out.println("谈论价钱");
methodProxy.invokeSuper(o,args);
System.out.println("交接钥匙");
TestFunctionSpendTime.end = System.currentTimeMillis();
TestFunctionSpendTime.display();
return null;
}
}
3.测试类
public class CGLIBProxyTest {
public static void main(String[] args) {
Tenant tenant = new Tenant();
tenant.setName("王五");
TenantInterceptor<Tenant> tenantInterceptor = new TenantInterceptor<Tenant>();
Tenant rentalAgency = (Tenant) tenantInterceptor.getInstance(tenant);
rentalAgency.giveHouse();
}
}
CGLIB动态代理总结:CGLIB可以直接代理具体的类,无需接口。CGLIB时第三方库,而且其底层实现需要ASM框架,因此需要导入CGLIB的JAR包和ASM的JAR包,两个框架的版本对应有一定的要求,下面我给出我测试用的两个版本,大家使用maven下载就好了。
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.ow2.asm/asm -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>5.1</version>
</dependency>
总结全文:
在我们提高自己能力的过程中,需要去看优秀框架的源码,但是框架大量使用了各种设计模式。因此学好设计模式很重要。
参考博文地址如下:
https://www.cnblogs.com/clds/p/4985893.html
https://www.jianshu.com/p/9a61af393e41?from=timeline&isappinstalled=0
https://www.jianshu.com/p/471c80a7e831
https://www.cnblogs.com/daniels/p/8242592.html