代理模式概念

  • 什么是代理模式

代理模式是指为其他对象提供一种代理,达到控制这个对象的访问;简单理解,现在有一个目标对象,客户端需要用到这个目标对象,但是客户端不能直接调用目标对象,这个时候,定义一个代理对象,客户端通过代理对象,访问到目标对象;实际上代理对象和目标对象之间起的是中介作用。

  • 举例说明代理模式的概念

比如现在张三想要买李四的房子,但是张三没有李四的联系方式,不能直接找李四买房子,这个时候,张三就可以找房屋中介,由中介去联系张三,在这个例子中,可以把李四看作是目标对象,房屋中介是代理对象;

静态代理

  • 静态代理概念

由上面的概念可以得出,在代理模式中,需要有目标对象和代理对象,静态代理的关键在于,需要定义顶层接口,并且目标对象和代理对象都需要实现该接口,以此来实现静态代理模式。

  • 实现静态代理事例

1)定义一个顶层接口

public interface StaticProxy {

    //定义一个抽象方法
    public void insertData();
}

2)定义一个目标类对象,目标对象需要实现上面的接口StaticProxy

public class Target implements StaticProxy{

    @Override
    public void insertData() {
        System.out.println("目标对象正在执行新增数据的方法");
    }
}

3)定义一个代理对象,根据静态代理的需求,代理对象需要和目标对象实现同一个接口

public class Agent implements StaticProxy{

    //声明一个目标对象
    private Target target;
    //使用构造方法初始化目标对象
    public Agent(Target target){
        this.target = target;
    }

    @Override
    public void insertData() {
        System.out.println("代理对象,在对新增数据方法进行局部扩展");
        //在这里调用目标对象中的方法
        target.insertData();
    }
}

4)测试静态代理方式

public class Test {
    public static void main(String[] args) {
        //创建目标对象
        Target target = new Target();

        //创建代理对象
        Agent agent = new Agent(target);
        
        //通过代理对象调用新增数据的方法insertData
        agent.insertData();
    }
}

5)测试结果

image-20210608213618845

  • 静态代理说明

代码设计:在目标对象中的insertData方法,只是输出了“目标对象正在执行新增的方法”这句话,然后在通过代理对象后,因为都实现了StaticProxy接口,因此代理对象也需要重写insertData方法,只不过这里的insertData比起目标对象中的方法,做了扩展,之后再由传入的目标对象调用insertData方法

测试方法:在测试方法的时候,需要将目标对象传递给代理对象,然后通过代理对象调用insertData方法。

测试效果:既调用了代理对象扩展后的方法,也执行了目标对象中的代码

  • 静态代理作用

静态代理作用就是代理模式的作用,在不修改原目标中代码的情况下,对目标对象的已有方法进行扩展,而且还能不影响原目标对象的作用。

  • 静态代理的缺点

从上述代码可以看出,静态代理要求目标对象和代理对象都需要实现同一个接口,那么对于没有实现接口的类,不能代理;再者,当原目标较为复杂,代理对象就难以操作。

JDK封装的代理模式

  • JDK的代理模式概念

在Java的动态代理机制中,有两个重要的接口和类,接口是InvoactionHandler,类是Proxy;jdk的动态代理模式中,代理类都必须实现InvocationHandler这个接口,并且么个代理类的实例都关联到一个Handler,通过代理对象调用一个方法的时候,这个方法的调用会转发由InvocationHandler这个接口的invoke方法来调用。

  • 代码演示动态代理

1)定义一个顶层接口DataMapper

public interface DataMapper {
    //定义抽象方法新增数据
    public void insertData();
}

2)定义目标对象,需要实现接口DataMapper

public class Target implements DataMapper{

    @Override
    public void insertData() {
        System.out.println("目标对象新增数据");
    }
}

3)定义代理对象,需要实现InvocationHandler接口

public class Agent implements InvocationHandler {
    //声明接口
    private DataMapper dataMapper;
    //返回代理对象
    public Object getInstance(DataMapper dataMapper) throws Exception{
        this.dataMapper = dataMapper;
        Class<?> clazz = dataMapper.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

    /**
     * 执行invoke方法
     * @param proxy 代理对象
     * @param method 目标对象方法
     * @param args 目标对象方法的参数列表
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //调用前置增强
        before();
        //这是执行目标对象
        Object obj = method.invoke(this.dataMapper,args);
        //嗲用后置增强
        after();
        return obj;
    }
    //自定义
    public void before(){
        System.out.println("代理对象进行前置增强,比如新增数据之前先开启事务");
    }
    //自定义
    public void after(){
        System.out.println("代理对象进行后置增强,比如新增数据之后,提交事务");
    }
}

4)测试类

public class Test {
    public static void main(String[] args) throws Exception{
        //创建目标对象
        DataMapper dataMapper = (DataMapper) new Agent().getInstance(new Target());
        dataMapper.insertData();
    }
}
  • 测试结果

image-20210608224025839

  • jdk动态代理说明

上面的事例中,DataMappr是顶层接口;Target是目标类;Agent类是代理类;Test作为测试类,可以从结果集中可以看出,创建的是代理对象Agent,将目标对象传递给代理对象,通过代理对象调用insertData方法,除了将目标对象中代码执行了,也将前置增强和后置增强执行了。

CGLIB动态代理

  • CGLIB动态代理概念

CGLIB动态代理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术,拦截所有的父类方法的调用,

  • cglib动态代理事例

1)定义一个目标类,

public class Target{
    public void insertData() {
        System.out.println("目标对象新增方法");
    }
}

2)定义一个代理类,需要实现cglib的接口MethodInterceptor

public class Agent implements MethodInterceptor {
    //获取代理对象
    public Object getInstance(Class clazz) throws Exception{
        //创建字节码增强器,用来对被代理的类扩展
        Enhancer enhancer = new Enhancer();
        //通知cglib,生成的子类需要继承那个父类
        enhancer.setSuperclass(clazz);
        //设置回调
        enhancer.setCallback(this);
        //创建对象
        Object obj = enhancer.create();
        return obj;
    }

    /**
     *
     * @param obj 生成的子类对象
     * @param method 被拦截的方法
     * @param args 被拦截方法的参数
     * @param proxy 触发父类方法的对象
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        before();
        //这里是调用invokeSuper()方法,不能调用invoke方法
        Object object = proxy.invokeSuper(obj, args);
        after();
        return object;
    }
    //前置增强方法
    public void before(){
        System.out.println("前置增强方法");
    }
    //后置增强方法
    public void after(){
        System.out.println("后置增强方法");
    }
}

3)测试类

public class Test {
    public static void main(String[] args) throws Exception {
        Target target = (Target) new Agent().getInstance(Target.class);
        target.insertData();
    }
}
  • 测试结果

image-20210608234812736

代理模式总结

  • 三种代理模式的区别
1)静态代理:静态代理要求目标对象和代理对象都实现同一个接口,并且如果目标对象的上层接口代码较为复杂,则代理对象也会变得复杂
2)jdk动态代理:在静态代理的基础上,jdk的实现只需要要求目标对象必须有实现的接口即可,如果是普通类,jdk则不能完成代理
3)cglib动态代理:比起jdk动态代理,cglib代理可以代理任何类,对目标类没有特殊的要求,运用较为灵活

以上是三种代理模式的简单实现,至于原理和源码分析,本篇文章暂不做更多内容,后续再继续了解! 关注公众号:源码说