java 代理模式

1.概述

Java中最常见的模式之一,简单来说,即在不改变目标对象源码的情况下,实现对目标对象的功能扩展。

举个栗子:码农在看P站

public class CodeFamer{
      public void watchPSite(){
        System.out.println("code famer is watching PSite");
    }  
}

如果码农在看P站之前要准备卫生纸;看P站之后要洗手。

那么应该是这样的:

public class CodeFamer{
      public void watchPSite(){
        System.out.println("prepare toilet paper");
        System.out.println("code famer is watching PSite");
        System.out.println("handwashing");
    }  
}

这样当然没问题,但是很显然无关紧要的准备纸巾/洗手这些操作影响到看片了......

也就是说改动了源码了,开闭原则(扩展开放,修改关闭)了解一下?

这个时候我们的代理模式闪亮登场,总共有三种:静态代理,jdk动态代理,cglib动态代理

2.静态代理

//统一接口
public interface Famer{
   void watchPSite();
}
//目标类
public class CodeFamer implement Famer{
    @Override
    public void watchPSite(){
        System.out.println("code famer is watching PSite");
    }  
}
//代理类
public class CodeFamerProxy implement Famer{
    private Famer codeFamer;
    public CodeFamerProxy(Famer codeFamer){
        this.codeFamer = codeFamer;
    }

    @Override
    public void watchPSite(){
        System.out.println("prepare toilet paper");
        codeFamer.watchPSite(); 
        System.out.println("handwashing");
    }
 }
//使用
public class Test{
    public static void main(String[] args){
        Famer codeFamer = new CodeFamer();
        Famer proxy = new CodeFamerProxy(codeFamer);
        poxy.watchPSite();
    }
}

如代码所示:创建一个代理类来做扩展,不修改源码,完全符合开闭原则。

也就是说给码农看片提供一个服务员,在他看片前给他递纸巾,在他看片完帮他洗手,毕竟看片才是码农的主要事项。

静态代理虽好,但也有缺点:如果接口层有变动呢?譬如多了一个方法:void code(); 那么代理类也得跟着改动,这又增加了维护的难度。

也就是说给码农提供了一个看片的服务员,但是码农不但看片,还要写代码的呀。写代码的时候这个服务员也得跟着码农,众所周知,码农写代码时是不能让别人看到的,不然复制粘贴大法岂不是不好意思用?

3.jdk动态代理

//接口
public interface Famer{
   void watchPSite();
}
//目标类
public class CodeFamer implement Famer{
    @Override
    public void watchPSite(){
        System.out.println("code famer is watching PSite");
    }  
}
//jdk动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class Dynamicproxy implements InvocationHandler {
    private final Object target;
    public Dynamicproxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("prepare toilet paper");
        method.invoke(target, args);
        System.out.println("handwashing");
        return null;
    }

    public static Object getDynamicproxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载
                target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个
                new Dynamicproxy(target)   // 代理对象对应的自定义 InvocationHandler
        );
    }
}
//使用
public class Test{
    public static void main(String[] args){
        Famer proxy = (Famer)Dynamicproxy.getDynamicproxy(new CodeFamer());
        proxy.watchPSite();
    }
}

以上代码虽然相对复杂,但是主要解决了静态代理的问题,无需为每个目标类创建一个代理类,接口有什么变动,也不会影响代理实现。只有在使用时通过反射获取目标类的方法动态做代理。

也就是说,码农不用预先请一个服务员,而是只有在看片的时候再请服务员过来做事,做完了服务员也就走了。做其他事譬如写代码的时候不用服务员那不叫服务员过来就行了。

动态代理虽好,可缺点也不少,首先是代码复杂度大大提高,其次和静态代理一样都必须要通过接口的方式实现。

4.cglib动态代理

//目标类
public class CodeFamer{
      public void watchPSite(){
        System.out.println("code famer is watching PSite");
    }  
}
//cglib动态代理类
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
    private Object target;
    public CglibProxy(Object target) {
        this.target = target;
    }
 
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("prepare toilet paper");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("handwashing");
        return result;
    }
 
    public static <T> T createProxy(T target) {
        CglibProxy cglibProxy = new CglibProxy(target);
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(cglibProxy);
        enhancer.setSuperclass(target.getClass());
        return (T) enhancer.create();
    }
}
//使用
public class Test {
    public static void main(String[] args) {
        CodeFamer proxy = CglibProxy.createProxy(new CodeFamer());
        proxy.watchPSite();
    }
}

cglib代理和jdk代理代码复杂度差不多,都需要按照规则实现功能。

直观上的不同就是jdk动态代理需要目标类实现接口,而cglib代理无需目标类实现接口。

5.jdk代理和cglib代理的区别

JDK代理使用的是反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理;

CGLIB代理使用字节码处理框架ASM,对代理对象类的class文件加载进来,通过修改字节码生成子类。

 

DK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类;

CGLIB则使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,因为是继承机制,不能代理final修饰的类。

 

JDK创建代理对象效率较高,执行效率较低;

CGLIB创建代理对象效率较低,执行效率高。

 

JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求:

 1.实现InvocationHandler接口,重写invoke()

 2.使用Proxy.newProxyInstance()产生代理对象

 3.被代理的对象必须要实现接口

CGLib 必须依赖于CGLib的类库,需要满足以下要求:

 1.实现MethodInterceptor接口,重写intercept()

 2.使用Enhancer对象.create()产生代理对象

6.SpringAOP

源码如下:

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理
2、如果目标对象实现了接口,也可以强制使用CGLIB
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

posted @ 2022-09-15 16:57  蜗壳吃虾米  阅读(23)  评论(0)    收藏  举报