代理模式概述

思维导图
动态代理.png

  1. 什么是代理模式:无法访问目标对象,通过代理对象进行访问,而且增强式的访问。适合进行业务的扩展。

    代理模式是指,为其他对象提供一种代理,以控制对这个(目标)对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户和目标对象之间起到中介的作用。 使用代理对象,是为了在不修改目标对象的基础上,增强主业务逻辑。

例如:A类想要访问B类,但他不能直接访问,必须通过Proxy代理来来访问

代理模式 - 图2

  1. 功能:

    1. 增强功能
    2. 限制目标对象的访问
  2. 分类:

  • 静态代理
  • 动态代理(JDK动态代理、CGLIB动态代理)

    静态代理

    特点:

    优点: 静态代理要求目标对象和代理对象实现同一个业务接口。代理对象中的核心功能是由目标对象来完成,代理对象负责增强功能。 缺点: 当业务发生变化时,要进行大量代码的改动,实现复杂; 能够灵活的进行目标对象的切换,却无法进行功能的灵活处理; 代理类是以.java的文件形式存在,在调用前就已存在,所以比较死板;

实现步骤:

  1. 定义业务接口

    1. public interface Service {
    2. void sing();
    3. }
  2. 定义目标对象类实现接口

    1. //实现类1
    2. public class ChangChang implements Service{
    3. @Override
    4. public void sing() {
    5. System.out.println("我是邓长长....我正在唱歌");
    6. }
    7. }
    8. //实现类2
    9. public class DuanDuan implements Service {
    10. @Override
    11. public void sing() {
    12. System.out.println("我是邓短短....我正在唱歌");
    13. }
    14. }
  3. 定义代理类实现接口

    1. public class Agent implements Service {
    2. @Override
    3. public void sing() {
    4. System.out.println("正在预约时间....");
    5. System.out.println("正在预约地点....");
    6. //根据目标对象来完成业务
    7. Service chang = new ChangChang();
    8. chang.sing();
    9. System.out.println("正在结算....");
    10. }
    11. }

    代理功能改造(采用面向接口编程)

    面向接口编程的要点:

    A.类中的成员变量设计为接口 B.方法的参数设计为接口 C.方法的返回值设计为接口 D.调用时接口指向实现类

  1. public class Agent implements Service {
  2. //类中的成员变量设计为接口
  3. private Service target;//需要代理的接口
  4. /**
  5. * 传入目标对象,方法的参数设计为接口
  6. * 通过构造方法,动态定义接口实现类,根据参数来代理指定对象
  7. * @param target
  8. */
  9. public Agent(Service target){
  10. this.target = target;
  11. }
  12. @Override
  13. public void sing() {
  14. System.out.println("正在预约时间....");
  15. System.out.println("正在预约地点....");
  16. //面向接口编程:调用时,接口指向实现类
  17. target.sing();
  18. System.out.println("正在结算....");
  19. }
  20. @Override
  21. public String show(int age) {
  22. return null;
  23. }
  24. }

测试:
image.png

静态代理的缺陷

  1. 代理复杂,难于管理

代理类和目标类实现了相同的接口,每个代理都需要实现目标类的方法,这样就出现了大量的代码重复。如果接口增加一个方法,除了所有目标类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

  1. 代理类依赖目标类,代理类过多

代理类只服务于一种类型的目标类,如果要服务多个类型。势必要为每一种目标类都进行代理,静态代理在程序规模稍大时就无法胜任了,代理类数量过多

动态代理

动态代理是指代理类对象在程序运行时由 JVM 根据反射机制动态生成的。动态代理不需要定义代理类的.java 源文件。动态代理其实就是 jdk 运行期间,动态创建 class 字节码并加载到 JVM。动态代理的实现方式常用的有两种:使用 JDK 动态代理和 CGLIB 动态代理。

JDK 动态代理

JDK动态代理是基于 Java 的反射机制实现的。使用JDK中接口和类实现代理对象的动态创建。 代理对象在程序运行的过程中动态在内存构建;可以灵活的进行业务功能的切换。

特点:

  • 目标对象必须实现业务接口
  • 代理对象不必实现业务接口
  • JDK动态代理的对象在程序运行前不存在,在程序运行时动态的在内存中构建
  • JDK动态代理灵活的进行业务功能的切换
  • 非接口中的方法不能被代理

在使用JDK动态代理,需要用到两个类一个接口

Proxy 类

通过JDK的java.lang.reflect.Proxy类实现动态代理,会使用其静态方法newProxyInstance(),依据目标对象、业务接口及调用处理器三者,自动生成一个动态代理对象。
public static newProxyInstance ( ClassLoader loader, Class<?>[] interfaces,
InvocationHandler handler)
方法参数介绍:

loader:目标类的类加载器,通过目标对象的反射可获取 interfaces:目标类实现的接口数组,通过目标对象的反射可获取 handler:调用处理器。

Method 类

java.lang.reflect.Method
invoke()方法的第二个参数为 Method 类对象,该类有一个方法也叫 invoke(),可以调用目标方法。这两个 invoke()方法,虽然同名,但无关。
用来进行目标对象的方法的反射调用.
方法参数介绍:

public Object invoke ( Object obj, Object… args) obj:表示目标对象 args:表示目标方法参数,就是其上一层invoke 方法的第三个参数

该方法的作用是:调用执行 obj 对象所属类的方法,这个方法由其调用者 Method 对象确定。在代码中,一般的写法为
method.invoke(target, args);
其中,method 为上一层 invoke 方法的第二个参数。这样,即可调用了目标类的目标方法。

InvocationHandler接口

java.lang.reflect.InvocationHandler;
InvocationHandler 接口叫做调用处理器,负责完成调用目标方法,并增强功能。通过代理对象执行目标接口中的方法,会把方法的调用分派给调用处理器(InvocationHandler)的实现类,执行实现类中的 invoke()方法,我们需要把功能代理写在 invoke()方法中。此接口中只有一个方法。
public Object invoke ( Object proxy, Method method, Object[] args)
方法参数介绍:

proxy:代表生成的代理对象 method:代表目标方法 args:代表目标方法的参数 这三个参数都是 jdk 运行时赋值的,无需程序员给出。

实现步骤

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

  1. 定义业务接口

    1. public interface Service {
    2. void sing();
    3. String show(int age);
    4. }
  2. 定义业务实现类

    1. public class ChangChang implements Service{
    2. @Override
    3. public void sing() {
    4. System.out.println("我是邓长长....我正在唱歌");
    5. }
    6. @Override
    7. public String show(int age) {
    8. return "我今年"+age+"岁";
    9. }
    10. }
  3. 定义代理类工厂

    1. public class ProxyFactory {
    2. //类中的成员变量设计为接口,目标对象
    3. Service target;
    4. //传入目标对象
    5. public ProxyFactory(Service target){
    6. this.target = target;
    7. }
    8. //返回动态代理对象
    9. public Object getAgent(){
    10. return Proxy.newProxyInstance(
    11. //ClassLoader loader,类加载器,完成目标对象的加载
    12. target.getClass().getClassLoader(),
    13. //Class<?>[] interfaces,目标对象实现的所有接口
    14. target.getClass().getInterfaces(),
    15. //InvocationHandler h 实现代理功能的接口 ,采用匿名内部实现
    16. new InvocationHandler() {
    17. /**
    18. *
    19. * @param proxy 生成的代理对象
    20. * @param method 目标方法
    21. * @param args 目标方法的参数
    22. * @return
    23. * @throws Throwable
    24. */
    25. @Override
    26. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    27. //代理功能
    28. System.out.println("预订时间........");
    29. //代理功能
    30. System.out.println("预订场地........");
    31. //主业务功能实现
    32. Object obj = method.invoke(target, args);
    33. //代理功能
    34. System.out.println("结算费用........");
    35. return obj;//目标方法的返回值
    36. }
    37. }
    38. );
    39. }
    40. }
  4. 测试 ```java //测试方法 @Test public void getAgent() {

    1. //创建需要被代理的对象,目标对象
    2. ChangChang changChang = new ChangChang();
    3. //创建动态代理工厂类
    4. ProxyFactory factory = new ProxyFactory(changChang);
    5. Service agent = (Service) factory.getAgent();
    6. agent.sing();

    }

//运行结构 / 预订时间…….. 预订场地…….. 我是邓长长….我正在唱歌 结算费用……../ ``` 注意:

Service agent = (Service) factory.getAgent(); 在强转时只能强转为接口,不能是实现类 因为: System.out.println(agent.getClass());//class com.sun.proxy.$Proxy4
System.out.println(new ChangChang());//com.ityg.service.impl.ChangChang@15327b79

image.png

CGLIB动态代理

他人笔记
Spring AOP (三) CGLIB 动态代理