(一)代理模式

1 简介

定义:内部含有对真实对象的引用,并由代理对象进行操作控制的运作模式。

目的:对目标方法进行增强。

优势:让增强的动作和目标动作分开,实现解耦,从而保证对原生代码无侵入的目的。

2 详解

代理模式包括静态代理和动态代理两种类型。

2.1 静态代理

其中,静态代理主要是针对某一个类做代理,如下所示:

// 接口
public interface IUserDao {
    void save();
    void find();
}
 
//目标对象
class UserDao implements IUserDao{
    @Override
    public void save() {
        System.out.println("模拟:保存用户!");
    }
    @Override
    public void find() {
        System.out.println("模拟:查询用户");
    }
}
 
/**
  * 静态代理
  * 特点:
  * 2. 目标对象必须要实现接口
  * 2. 代理对象,要实现与目标对象一样的接口
 */
class UserDaoProxy implements IUserDao{
 
    private IUserDao target = new UserDao();
 
    @Override
    public void save() {
        System.out.println("代理操作: 开启事务...");
        target.save();   
        System.out.println("代理操作:提交事务...");
    }
 
    @Override
    public void find() {
        target.find();
    }
}

2.2 动态代理

由于静态代理中的代理类是针对某一个类去做代理的,如果系统中有1000个Interface,那么需要创建1000个代理类,因此静态代理的可复用性不强,而采用动态代理就能很好的解决以上问题。其中,动态代理分为JDK的动态代理和cglib动态代理:

JDK动态代理:利用拦截器加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理。

其中,JDK动态代理使用的前提是目标对象生成了接口,如果目标对象没有实现接口,则必须使用cglib库。JDK的动态代理的使用方式如下所示:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface ISayHelloWorld {
    void say();
}

class ManSayHelloWorld implements ISayHelloWorld {
    @Override
    public void say() {
        System.out.println("Hello world!");
    }
}

/**
 * 这里创建一个拦截器,代理对象的所有方法调用都会做拦截器的invoke方法,从而实现了对原生方法的过滤和增强
 * */
class AOPHandle implements InvocationHandler {

    /**
     * 代理对象
     * */
    private Object obj;

    public AOPHandle(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 调用前处理
        System.out.println("前置代理");
        // 调用代理对象的方法并返回ret
        Object ret = method.invoke(obj, args);
        // 调用结束后处理
        System.out.println("后置代理");
        // 返回代理对象函数调用的结果
        return ret;
    }
}

public class Test {
    public static void main(String[] args) {
        AOPHandle handle = new AOPHandle(new ManSayHelloWorld());
        ISayHelloWorld ic = (ISayHelloWorld) Proxy.newProxyInstance(ManSayHelloWorld.class.getClassLoader(), new Class[]{ISayHelloWorld.class}, handle);
        ic.say();
    }
}

cglib动态代理的使用方式如下所示:

依赖:
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

使用:
class Example{
    public void test() {
        System.out.println("hello,world!");
    }
}

/**
 * 方法拦截器
 * CGLib 代理的目标对象不需要实现任何接口,它是通过动态继承目标对象 实现的动态代理
 */
class MethodInvocation implements MethodInterceptor {
    public Object getInstance(Class<?> clazz){
        Enhancer enhancer = new Enhancer();
        //告诉cglib,生成的子类需要继承哪个类
        enhancer.setSuperclass(clazz);
        //设置回调
        enhancer.setCallback(this);
        //生成源代码
        //编译成class文件
        //加载到JVM中,并返回被代理对象
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("------invoke before---------");
        Object obj = methodProxy.invokeSuper(o, objects);
        System.out.println("------invoke end---------");
        return obj;
    }
}

public class Test {
    public static void main(String[] args) {
        MethodInvocation webApp = new MethodInvocation();
        Example boss = (Example) webApp.getInstance(Example.class);
        boss.test();
    }
}

对比两种动态代理方式:

(1)JDK动态代理是实现了被代理对象的接口,CGLib是继承了被代理对象;

(2)JDK和CGLIb都是在运行期间生成字节码,JDK是直接写Class字节码,CGLib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类的效率比JDK动态代理方式低;

(3)JDK调用代理方法,是通过反射机制调用的,CGLIb是通过FastClass机制直接调用方法,CGLib执行效率更高。 

 

但以上两种代理方式还需要我们写相应代码比较麻烦,因此在Spring和SpringBoot等框架中引入了AOP(Aspect Oriented Programming,面向切面编程,采用动态代理实现,当代理类存在接口时使用JDK动态代理,当代理类不存在接口时使用cglib动态代理),只需要我们简单配置,即可实现运行时动态地将代码切入到类的指定方法、指定位置上。

 

posted @ 2020-05-04 14:17  FCity  阅读(162)  评论(0编辑  收藏  举报