Java 设计模式:代理模式

代理模式(Proxy Pattern)

概述

所属:结构性模式,提供了对目标对象另外的访问方式。

适用时机:控制对象的访问,或增加访问对象的功能。

优点:

  • 隐藏了目标对象,使用者根本就不会感知到目标对象的存在。
  • 不改变目标对象结构。

实现

Java 代理模式实现可分为两类:静态代理,动态代理。

静态代理

特点:代理关系在编译期确定。

要求:目标对象继承某个目标接口。

实现方式:代理对象实现目标接口的所有方法代理对象内含目标对象实例,实现方法时可调用目标对象的方法并在自己实现方法的过程中添加相应的操作,以达到增强目标对象的方法的目的。

弊端:静态代理虽然实现了对目标对象的功能扩展,但由于它要实现目标对象的所有方法,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。具有重复性脆弱性

示例

目标接口 interface ITarget,与其实现 class Target:

public interface ITarget {
    void call();
}

public class Target implements ITarget {

    @Override
    public void call() {
        System.out.println("I'm real target.");
    }

}

代理类:

public class Proxy implements ITarget{

    private Target target;
    
    AProxy() {
        this.target = new Target();
    }

    @Override
    public void call() {
        // 如果需要扩展方法,在这里实现。
        aTaget.call();
        System.out.println("I'm proxy A.");
    }
    
    // 你也可以添加其它接口
    public void extraMethod() {
        System.out.println("I'm extra method.");
    }
    
}

我们使用时,即使用对应的代理类:

public class ProxyPatternDemo {

    public static void main(String[] args) {
		Proxy proxy = new Proxy();
        
        // 调用代理方法
        proxy.call();
        // 调用额外方法
        proxy.extraMethod();

    }
}

动态代理

特点:代理关系在运行时确定。

JVM 根据传进来的业务实现类对象及方法名,在运行时生成代理类的字节码,即 class 文件,随后被字节码引擎执行,然后通过该代理类对象进行方法调用。

动态代理的实现方式有很多,这里主要讲述 JDK 动态代理与 CGLib 动态代理。

JDK 动态代理

核心 API:java.lang.reflect.Proxy、java.lang.reflect.InvocationHandler

特点/实现原理:

  • 利用反射机制
  • 基于接口。

缺点:

  • JDK 动态代理有一个最致命的问题是它只能代理实现了某个接口的实现类,并且代理类也只能代理接口中实现的方法,要是实现类中有自己私有的方法,而接口中没有的话,该方法不能进行代理调用。
示例
public class JDKProxyDemo {

    public static void main(String[] args) {

        // 目标对象
        final Target target = new Target();
        
        // 生成代理对象
        Class<? extends Target> targetClass = target.getClass();
        ITarget proxy = (ITarget) Proxy.newProxyInstance(targetClass.getClassLoader(), targetClass.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                
                // 这里拓展原来的方法
                System.out.println("extra method.");
                Object invoke = method.invoke(target, args);
                return invoke;
            }
        });

        // 调用代理对象方法
        proxy.call();
    }
}
源码分析

java.lang.reflect.Proxy 的 newProxyInstance 方法源码中,最重要的是以下三条 statements:

这三条语句的作用:获得代理类的 class 对象、获得构造器、返回实例。

其中,InvocationHandler 最终被作为 java.lang.reflect.Constructor 中 sun.reflect.ConstructorAccessor constructorAccessor 的 newInstance 方法参数:

而 constructorAccessor 来自于 sun.reflect.ReflectionFactory 的 newConstructorAccessor 。所以 newInstance 最终调用的是以下几个实例的 newInstance:

CGLib 动态代理

核心 API:net.sf.cglib.proxy.Enhancer、net.sf.cglib.proxy.MethodInterceptor

特点/实现原理:

  • 基于ASM

  • 代理类去继承目标类,然后重写其中目标类的方法,同时设置拦截器,调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑。

注意:需要导入依赖

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>{cglib.version}</version>
</dependency>
示例
public class CGLibProxyDemo {

    public static void main(String[] args) {

        // 目标对象
        final Target target = new Target();
        
        // 生成代理对象
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Target.class);
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
            // 这里拓展原来的方法
            System.out.println("extra method.");
            Object invoke = method.invoke(target, objects);
            return invoke;
        });
        Target proxy = (Target) enhancer.create();

        // 调用代理对象方法
        proxy.call();
    }
}
源码分析

// TODO 24/11/2022 meyok: CGLib 源码分析

业界实践

Spring 默认使用 JDK 动态代理实现 AOP,同时也可以使用 CGLib 动态代理实现 AOP。

Spring 本身 AOP 不如 AspectJ 强大,Spring 融入 AspectJ 可添加以下依赖:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>
posted @ 2022-11-24 15:25  MeYokYang  阅读(219)  评论(0)    收藏  举报