代理模式

Lou.Chen
大约 8 分钟

一、静态代理

  • 代理类和目标对象的类都是在编译期间确定下来的叫静态代理。
  • 静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。
  • 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.

1、代理模式和装饰模式的区别

①目标对象的行为抽象

吃饭行为:

public interface IEat {
    void eat();
}

读书行为:

public interface IStudy {
    void readBook();
}

②创建目标对象
public class Child implements IEat,IStudy{
    @Override
    public void eat() {
        System.out.println("The child is eating...");
    }

    @Override
    public void readBook() {
        System.out.println("I am reading");
    }
}
③创建代理对象
1)父母代理(帮孩子做饭)
public class Parent implements IEat {
    private Child child;

    public Parent(Child child) {
        this.child = child;
    }

    @Override
    public void eat() {
         //由代理类调用实际对象中的方法。代理类可以在其中加入其他操作
        System.out.println("parents cook..");
        child.eat();
        System.out.println("parents wash dishes ");
    }
}
2)学校代理(教孩子识字,读书)
public class School implements IStudy{

    private Child child;

    public School(Child child) {
        this.child = child;
    }

    @Override
    public void readBook() {
        System.out.println("school teach words");
        child.readBook();
        System.out.println("school teach write");
    }
}
④创建目标对象的扩展类(装饰)

孩子长大了,能够自己学会认字,自己做饭,则需要对功能扩展。但是此时不需要任何代理,自己本身去实现扩展的功能即可,本质还是一个孩子。

public class ChildWrapper implements IEat,IStudy {

    private Child child;

    public ChildWrapper(Child child) {
        this.child = child;
    }

    @Override
    public void eat() {
        System.out.println("self cook...");
        child.eat();
        System.out.println("self wash dishes...");
    }

    @Override
    public void readBook() {
        System.out.println("self start read book...");
        child.readBook();
        System.out.println("self finish reading....");
    }
}
⑤结果
1)代理模式
//1、代理模式
//1、创建目标对象
Child child=new Child();
//2、创建代理类并传入实际操作的目标对象  (父类帮孩子做饭,孩子本身不会完成这件事,所以需要父母代理类)
Parent parent = new Parent(child);
//同理孩子本身不会完成读书这件事,所以需要学校代理类
School school = new School(child);
//3、调用代理方法
//由代理对象调用,并可以加入自己的额外的功能
parent.eat();
school.readBook();

父母的代理结果:

parents cook..
The child is eating...
parents wash dishes 

学校的代理结果:

school teach words
I am reading
school teach write
2)装饰模式
//2、装饰模式(扩展)
//创建其目标对象
Child child1=new Child();
//对目标对象的扩展功能,本质还是目标对象
ChildWrapper childWrapper = new ChildWrapper(child);
childWrapper.eat();
childWrapper.readBook();
self cook...
The child is eating...
self wash dishes...
    
self start read book...
I am reading
self finish reading....

孩子有吃饭和学习俩件任务,父母作为代理类之一,只能指导吃饭;学校作为代理类之一,只能指导学习。 对于某些独立自主的孩子(装饰类),它可能学习更加主动,吃完饭会主动收拾碗筷,但这些本来就是它原有功能的加强,它的本质仍然是孩子,依然可以享受父母、学校的代理帮助。

⑥结论

代理,偏重因自己无法完成或自己无需关心,需要他人干涉事件流程,更多的是对对象的控制。

装饰,偏重对原对象功能的扩展,扩展后的对象仍是是对象本身。

代理模式:注重对对象某一功能的流程把控和辅助。它可以控制对象做某些事,重心是为了借用对象的功能完成某一流程,而非对象功能如何。 装饰模式:注重对对象功能的扩展,它不关心外界如何调用,只注重对对象功能的加强,装饰后还是对象本身。

对于代理类,如何调用对象的某一功能是思考重点,而不需要兼顾对象的所有功能; 对于装饰类,如何扩展对象的某一功能是思考重点,同时也需要兼顾对象的其它功能,因为再怎么装饰,本质也是对象本身,要担负起对象应有的职责。

https://www.jianshu.com/p/c06a686dae39open in new window

二、JDK动态代理

  • 代理对象不需要实现目标对象的接口。

  • 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

  • 代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理

  • 动态代理也叫做:JDK代理,接口代理

Proxy中的newProxyInstance方法

    /**
     * 获取代理对象
     * @param loader 获取目标对象的类加载器。指定当前目标对象使用类加载器,获取加载器的方法是固定的
     * @param interfaces 目标对象实现的接口的类型,使用泛型方式确认类型
     * @param h 回调方法处理器。事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
     * @return 返回代理对象
     */
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h){}

回调处理器InvocationHandler中的invoke方法

public interface InvocationHandler {
           /**
            * 回调方法处理器
            * @param proxy 代指当前代理对象 由jdk调用,我们无需操作
            * @param method 当前要执行目标对象的方法
            * @param args 方法的实际参数
            * @return 返回方法的结果
            * @throws Throwable
            */
    		public Object invoke(Object proxy, Method method, Object[] args)
        	throws Throwable;
}

1、目标对象抽象接口

public interface IUserDao {
    void save(String id);
    String getUser();
}

2、目标对象类

public class UserDao implements IUserDao {
    @Override
    public void save(String id) {
        System.out.println("存储学生学号:"+id);
    }

    @Override
    public String getUser() {
        return "我是张三";
    }
}

3、代理工厂(获取代理对象)

public class ProxyFactory {
    //被代理对象(目标对象)
    private Object object;

    //传入目标对象
    public ProxyFactory(Object object) {
        this.object = object;
    }
    //获取代理对象
    public Object getProxyInstance() {
        //利用反射获取代理对象
       return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new InvocationHandler() {
           //回调方法。相当于代理对象中对目标对象的方法调用
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //回调方法之前执行的代码
                System.out.println("transcation start...");
                
                //回调当前目标对象执行的方法
                //object 当前目标对象
                //args 当前目标方法执行时传入的参数
                //有值则返回,无则返回null
                Object invokeValue = method.invoke(object, args);
                
                //回调方法之后执行的代码
                System.out.println("-----"+invokeValue);
                System.out.println("transcation ending...");
                
                //返回方法的执行结果
                return invokeValue;
            }
        });
    }
}

4、测试

public class Main {
    public static void main(String[] args) {
        //创建代理工厂并传入目标对象
        ProxyFactory proxyFactory = new ProxyFactory(new UserDao());
        //获取代理对象(注意这里的对象必须为接口对象)
        IUserDao proxyInstance = (IUserDao) proxyFactory.getProxyInstance();
        //调用代理对象中的方法
        proxyInstance.save("1001");
        //若代理对象中的方法有返回值则返回此值。
        String user = proxyInstance.getUser();
        //我是张三
        System.out.println(user);
    }
}
transcation start...
存储学生学号:1001
-----null
transcation ending...
    
transcation start...
-----我是张三
transcation ending...
我是张三

三、Cglib代理

上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
  • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

Cglib子类代理实现方法: 1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

2.引入功能包后,就可以在内存中动态构建子类 3.代理的类不能为final,否则报错 4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

在Spring的AOP编程中:

  • 如果加入容器的目标对象有实现接口,用JDK代理
  • 如果目标对象没有实现接口,用Cglib代理

1、目标对象类

public class UserDao {
    public void save(String id) {
        System.out.println("存储学生学号:"+id);
    }
    public String getUser() {
        return "我是张三";
    }
}

2、代理工厂(基于目标对象的子类代理)

public class ProxyFactory implements MethodInterceptor {
    //目标对象
    private Object object;

    //传入目标对象
    public ProxyFactory(Object object) {
        this.object = object;
    }

    //获取代理对象
    public Object getProxyInstance() {
        //1、工具类
        Enhancer enhancer=new Enhancer();
        //2、设置父类
        enhancer.setSuperclass(object.getClass());
        //3、设置回调函数(当前代理工厂)
        enhancer.setCallback(this);
        //4、创建子类(子类代理)
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("开始事务...");
        //回调的方法
        Object invokeValue = method.invoke(object, args);
        System.out.println("----"+invokeValue);
        System.out.println("事务结束...");
        return invokeValue;
    }
}

3、测试

public class Main {
    public static void main(String[] args) {
        //创建代理工厂并传入目标对象
        ProxyFactory proxyFactory = new ProxyFactory(new UserDao());
        //获取代理对象(基于目标对象的子类代理)
        UserDao userDao = (UserDao) proxyFactory.getProxyInstance();
        userDao.save("019321");
        String user = userDao.getUser();
        System.out.println(user);
    }
}

参考:https://www.cnblogs.com/cenyu/p/6289209.htmlopen in new window