代理模式

该文档是:设计模式学习...

博客连接:https://www.loveuluo.cn

日期:2020-01-01

1. 动态代理概述

代理,简单来说,就是代替处理,代替原有操作者去处理一件事。在Java中一般是2种,静态代理和动态代理,动态代理又分为CGLIB和jdk自带。

名称备注
静态代理简单,代理模式,是动态代理的理论基础。常见使用在代理模式
jdk动态代理需要有顶层接口才能使用,但是在只有顶层接口的时候也可以使用,常见是mybatis的mapper文件是代理。使用反射完成。使用了动态生成字节码技术。
cglib动态代理可以直接代理类,使用字节码技术,不能对 final类进行继承。使用了动态生成字节码技术。

image-20201231160330680

image-20201231160337150

2. 静态代理例子

/**
 * 静态代理举例
 * 特点:代理类和被代理类在编译期间,就确定下来了
 */
interface ClothesFactory{ //一个衣服加工厂
    void makeClothes();
}

//代理类
class ProxyClothFactory implements ClothesFactory{
    //这是一个接口,可以传入被代理类对象进行实例化
    private ClothesFactory clothesFactory;

    public ProxyClothFactory(ClothesFactory clothesFactory) {
        this.clothesFactory = clothesFactory;
    }

    @Override
    public void makeClothes() {
        //执行被代理对象的他自己方法之前的增强
        System.out.println("做衣服之前我先检查一下机器好坏...");
        //因为传入了被代理类的实现类,直接调用他原先自己的方法
        clothesFactory.makeClothes();
        //执行被代理对象的他自己方法之后的增强
        System.out.println("衣服做好了,卖给憨批...");
    }
}
//被代理类
class NikeClothesFactory implements ClothesFactory{
    @Override
    public void makeClothes() {
        System.out.println("我开始做Nike衣服啦!");
    }
}
//测试类
public class StaticProxyTest {
    public static void main(String[] args) {
        //创建一个被代理类
        NikeClothesFactory nikeClothesFactory=new NikeClothesFactory();
        /*创建代理类里边传入被代理类。因为里边有一个接口ClothesFactory,而被代理类和代理类都实现了这个接口,
        代理类有一个构造方法可以传入ClothesFactory的实现类,例如现在传入被代理类等于说是
        ClothesFactory clothesFactory = new NikeClothesFactory,多态形式!*/
        ProxyClothFactory proxyClothFactory=new ProxyClothFactory(nikeClothesFactory);
        proxyClothFactory.makeClothes();
    }
}

输出结果:

image-20201231162545209

3. JDK动态代理相关API和方法

API:

image-20201231164942150

invoke方法:

image-20201231165034294

4. JDK动态代理代码*

Proxy.newProxyInstance的第一个参数进一步理解:

image-20210225223213178

代码:

/**
 * 动态代理的举例
 */
interface Human{
    String getBelief();
    void eat(String food);
}

//[被代理类]
class SuperMan implements Human{
    @Override
    public String getBelief() {
        return "i believe i can fly!";
    }

    @Override
    public void eat(String food) {
        System.out.println("超人喜欢吃"+food);
    }
}

/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的[被代理类],动态的创建一个[代理类]及其对象。
问题二:当通过[代理类的对象]调用方法eat时,如何动态的去调用[被代理类]中的同名方法eat。
 */
class ProxyFactory{
    //调用此方法,返回一个[代理类的对象]。解决问题一
    public static Object getProxyInstance(Object obj){//obj:[被代理类的对象]
        //创建InvocationHandler接口的实现类对象的实现类对象
        MyInvocationHandler handler=new MyInvocationHandler();
        //把[被代理的对象]设置进去好让被代理对象原先的方法被执行
        handler.bind(obj);
        /*Proxy.newProxyInstanced方法的三个参数详解:
        第一个参数:[被代理类]的类加载器。
        第二个参数:[被代理类]实现的接口( [代理类]要去实现和[被代理类]一样的接口,和静态代理里边写的一个道理 )。
                 而因为[代理类的对象]也实现了[被代理类]实现的接口,那么它就能调用跟[被代理类]一样的方法
        第三个参数:InvocationHandler接口的实现类对象
                当例如[代理类的对象]调用了一个方法,会执行这个接口实现类里的invoke方法,并且把[代理类的对象],要调用的方法,方法的参数传过去。
        */
        Object o = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
        return o;
    }
}

class MyInvocationHandler implements InvocationHandler{
    //需要传入[被代理对象]
    private Object obj;
    //用这个方法可以把[被代理对象]绑定进来
    public void bind(Object obj) {
        this.obj = obj;
    }
    //当我们通过[代理类]的对象,调用方法eat时,就会自动的调用如下的方法:invoke()
    //将被代理类要执行的方法例如eat的功能就声明在invoke()中
    /* invoke方法的参数详解:
    proxy:这是[代理类的对象]
    method:即为[代理类对象]调用的方法,此方法也就作为了[被代理类对象]要调用的方法。
    例如此时代理类调用eat方法,那么这个method就是eat方法,那么可以通过反射让被代理类也执行eat方法
    args:方法调用时所需要的参数(例如此时eat方法需要一个参数为food,如果传入的是"汉堡",那么这个args就是汉堡)
    obj:[传入的被代理类的对象]
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //例如调用的是eat,通过反射让被代理对象执行eat方法,参数是被代理对象和方法的参数
        Object returnValue = method.invoke(obj, args);
        //returnValue就是执行method.invoke(obj, args)方法后的返回值
        return returnValue;
    }
}

//测试类
public class ProxyTest {
    public static void main(String[] args) {
        //创建[被代理对象]
        SuperMan superMan=new SuperMan();
        //这里只能写成Human,代理类和被代理类共同实现的接口,如果写SuperMan的话那它不就成了既是代理类,同时也是被代理类
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //proxyInstance:就是代理类的对象
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        proxyInstance.eat("汉堡");
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        System.out.println("*****************************");

        //调用另一个造衣服的也没问题,展示了动态性!
        NikeClothesFactory nikeClothFactory = new NikeClothesFactory();
        ClothesFactory proxyClothFactory = (ClothesFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
        proxyClothFactory.makeClothes();
    }
}

5. 展示JDK动态代理的作用

这是没有用动态代理的情况:

image-20201231183636951

使用了动态代理后:

image-20201231183724866

增强要调用的通用方法类:

public class HumanUtil {
    public static void method1(){
        System.out.println("====================通用方法一====================");

    }
    public static void method2(){
        System.out.println("====================通用方法二====================");
    }
    
}

在静态代理中增加通用方法:


//[被代理类]
class SuperMan implements Human{
    @Override
    public String getBelief() {
        return "i believe i can fly!";
    }

    @Override
    public void eat(String food) {
        System.out.println("超人喜欢吃"+food);
    }
}

/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的[被代理类],动态的创建一个[代理类]及其对象。
问题二:当通过[代理类的对象]调用方法eat时,如何动态的去调用[被代理类]中的同名方法eat。
 */
class ProxyFactory{
    //调用此方法,返回一个[代理类的对象]。解决问题一
    public static Object getProxyInstance(Object obj){//obj:[被代理类的对象]
        //创建InvocationHandler接口的实现类对象的实现类对象
        MyInvocationHandler handler=new MyInvocationHandler();
        //把[被代理的对象]设置进去好让被代理对象原先的方法被执行
        handler.bind(obj);
        /*Proxy.newProxyInstanced方法的三个参数详解:
        第一个参数:[被代理类]的类加载器。
        第二个参数:[被代理类]实现的接口( [代理类]要去实现和[被代理类]一样的接口,和静态代理里边写的一个道理 )。
                 而因为[代理类的对象]也实现了[被代理类]实现的接口,那么它就能调用跟[被代理类]一样的方法
        第三个参数:InvocationHandler接口的实现类对象
                当例如[代理类的对象]调用了一个方法,会执行这个接口实现类里的invoke方法,并且把[代理类的对象],要调用的方法,方法的参数传过去。
        */
        Object o = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
        return o;
    }
}

class MyInvocationHandler implements InvocationHandler{
    //需要传入[被代理对象]
    private Object obj;
    //用这个方法可以把[被代理对象]绑定进来
    public void bind(Object obj) {
        this.obj = obj;
    }
    //当我们通过[代理类]的对象,调用方法eat时,就会自动的调用如下的方法:invoke()
    //将被代理类要执行的方法例如eat的功能就声明在invoke()中
    /* invoke方法的参数详解:
    proxy:这是[代理类的对象]
    method:即为[代理类对象]调用的方法,此方法也就作为了[被代理类对象]要调用的方法。
    例如此时代理类调用eat方法,那么这个method就是eat方法,那么可以通过反射让被代理类也执行eat方法
    args:方法调用时所需要的参数(例如此时eat方法需要一个参数为food,如果传入的是"汉堡",那么这个args就是汉堡)
    obj:[传入的被代理类的对象]
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        HumanUtil.method1();

        //例如调用的是eat,通过反射让被代理对象执行eat方法,参数是被代理对象和方法的参数
        Object returnValue = method.invoke(obj, args);

        HumanUtil.method2();

        //returnValue就是执行method.invoke(obj, args)方法后的返回值
        return returnValue;
    }
}

//测试类
public class ProxyTest {
    public static void main(String[] args) {
        //创建[被代理对象]
        SuperMan superMan=new SuperMan();
        //这里只能写成Human,代理类和被代理类共同实现的接口,如果写SuperMan的话那它不就成了既是代理类,同时也是被代理类
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //proxyInstance:就是代理类的对象
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        proxyInstance.eat("汉堡");
        System.out.println("*****************************");

        //调用另一个造衣服的也没问题,展示了动态性!
        NikeClothesFactory nikeClothFactory = new NikeClothesFactory();
        ClothesFactory proxyClothFactory = (ClothesFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
        proxyClothFactory.makeClothes();
    }
}

输出结果:

7. CGLIB动态代理*

说明:

image-20210225225955866

image-20210225230056416

被代理类:

public class TeacherDao {
    public String teach(){
        System.out.println("老师授课中,我是cglib代理,不需要实现接口");
        return "hello";
    }
}

获取代理类的方法:

通过getProxyInstance获取到代理对象,调用它某一个方法的时候就会触发intercept这个方法, 而这个方法回去调用被增强对象的方法,而它之上之下可以做增强的逻辑!

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class ProxyFactory implements MethodInterceptor {

    //维护一个目标对象
    private Object obj;

    //构造器,传入一个被代理的对象
    public ProxyFactory(Object obj){
        this.obj=obj;
    }

    //返回一个代理对象:是obj对象的代理对象
    public Object getProxyInstance(){
        //1.创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(obj.getClass());
        //3.设置回调函数
        enhancer.setCallback(this);
        //4.创建子类对象,即代理对象
        return enhancer.create();
    }

    //重写intercept方法,会调用目标对象的方法,类似jdk动态代理的invoke方法
    /*通过getProxyInstance获取到代理对象,调用它某一个方法的时候就会触发intercept这个方法,
     而这个方法回去调用被增强对象的方法,而它之上之下可以做增强的逻辑!                   */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("Cglib代理模式开始~");
        //method是调用的方法,通过反射调用原先被增强类的方法
        //返回值是调用的方法的返回值,可能有可能没有,接收一下
        Object returnValue = method.invoke(obj, args);
        System.out.println("Cglib代理模式结束~");
        return returnValue;
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        //创建被代理对象
        TeacherDao teacherDao=new TeacherDao();
        //传入被代理对象,创建用于获取代理对象的ProxyFactory
        ProxyFactory proxyFactory=new ProxyFactory(teacherDao);
        //调用方法得到代理对象,强转为TeacherDao类型
        TeacherDao proxyInstance = (TeacherDao) proxyFactory.getProxyInstance();
        //执行方法
        String xxx = proxyInstance.teach();
        System.out.println(xxx);
    }
}

测试结果:

image-20210225233418567

8. Spring中使用的是哪种代理

先上结论,如果一个类有顶层接口,则默认使用jdk的动态代理来代理,如果直接是一个类,则使用cglib动态代理。 其次,如果没有需要代理的方法,如所有方法都没有@Transactional注解,Aop这种,则不会被代理。

问题来了,只说结论,没有依据,怎么让人信服?

@Service
public class TestServiceImpl implements TestService {
    @Transactional
    public void updateActual() {
        

    }
}

在这里,我们实现一个Service,让他实现一个顶层接口,然后我们如果在Controller里使用

@Autowired
private TestServiceImpl testServiceImpl;

注解来注入,这时候会发现,启动时报错的。

报错也很明显:

The bean 'testServiceImpl' could not be injected as a 'com.Luo.finance.service.apply.TestServiceImpl' because it is a JDK dynamic proxy that implements:
    com.Luo.finance.service.apply.TestService

改成 :@Autowired
private TestService testServiceImpl;

即可正常启动。

证明动态代理生成的代码是一个 TestService 却不是一个TestServiceImpl。使用的是jdk的动态代理。

这里去掉事务注解和 去掉接口实现 自己可以再试一下。

最后修改:2021 年 02 月 25 日 11 : 35 PM
如果觉得我的文章对你有用,请随意赞赏