Spring框架学习第二节:Java设计模式(二)

本文探讨了动态代理模式和责任链模式的概念及其在Java中的应用。动态代理通过生成代理对象来控制对真实对象的访问,包括JDK动态代理和CGLIB动态代理。责任链模式则用于对象在多个角色间传递的场景,通过层层代理实现复杂的业务逻辑。

此博客用于个人学习,来源于ssm框架的书籍,对知识点进行一个整理。

2.2 动态代理模式和责任链模式

动态代理的意义在于生成一个占位(又称代理对象),来代理真实对象,从而控制真实对象的访问。
先举个例子,能更好的理解代理模式。你的公司是一家软件公司,你作为一名软件工程师,平时的工作肯定是跟代码打交道。客户来你们公司,肯定不是直接找你谈,而是去找商务谈。此时,对于客户来说,商务就是代表整个公司。
在这里插入图片描述
客户是通过商务与软件工程师进行沟通,那么商务(代理对象)的作用是什么呢?商务可以进行谈判,比如项目启动前的商务谈判,软件的价格等等,商务也有可能谈判失败,此时就可以结束与客户的合作关系,这些都不需要软件工程师来处理。因此,代理的作用就是,在真实对象访问之前或者之后加入对应的逻辑,或者根据其他的规则控制是否使用真实对象。
我们需要在调用者调用对象之前产生一个代理对象,而这个代理对象需要和真实对象建立代理关系,所以代理分为两个步骤:

  1. 代理对象和真实对象建立代理关系;
  2. 实现代理对象的代理逻辑方法。

Java中,代理技术由许多种,本篇主要讨论 Spring 常用的 JDK 代理技术和 CGLIB 代理技术。

2.2.1 JDK 动态代理

需要借助接口才能创建代理对象,首先定义一个 HelloWorld 接口:

public interface HelloWorld {

    public void sayHelloWorld();
}

然后通过 HelloWorldImpl 这个类来实现接口:

public class HelloWorldImpl implements HelloWorld{

    @Override
    public void sayHelloWorld() {
        System.out.println("Hello World!");
    }
}

进行动态代理:

public class JdkProxyExample implements InvocationHandler {

    //真实对象
    private Object target = null;

    /**
     * 建立代理对象与真实对象的代理关系
     * @param target 真实对象
     * @return 返回代理对象
     */
    public Object bind(Object target){
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    /**
     * 代理方法逻辑
     * @param proxy 代理对象
     * @param method 当前调度方法
     * @param args 当前方法参数
     * @return 代理结果返回
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("进入代理逻辑方法");
        System.out.println("在调度真实对象之前的服务");
        //相当于调用sayHelloWorld方法
        Object obj = method.invoke(target,args);
        System.out.println("在调度真实对象之后的服务");
        return obj;
    }
}

第一步,建立代理对象与真实对象的关系,这里通过 bind 方法去完成,先是通过类的属性 target 保存了真实对象,然后通过下面的代码建立并生成代理对象。

Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);

对 newProxyInstance 方法进行解释:

  1. 第一个是类加载器,我们采用了 target 本身的类加载器。
  2. 第二个是把生成的动态代理对象下挂在哪些接口下,这个写法就是放在 target 实现的接口下。
  3. 第三个是定义实现方法逻辑的代理类,this 表示当前对象,它必须实现 InvocationHandler 接口的 invoke 方法,它就是代理逻辑方法的现实方法。

第二步,实现代理逻辑方法。通过 invoke 函数实现:

  • proxy,代理对象,就是 bind 方法生成的对象。
  • method,当前调度的方法。
  • args,调度方法的参数。
Object obj = method.invoke(target,args);

这行代码相当于调度真实对象的方法,只是通过反射实现而已。
进行测试:

JdkProxyExample jdk = new JdkProxyExample();
//由于是挂在接口HelloWorld下,所以声明代理对象HelloWorld proxy
HelloWorld proxy = (HelloWorld) jdk.bind(new HelloWorldImpl());
proxy.sayHelloWorld();

输出结果:

进入代理逻辑方法
在调度真实对象之前的服务
Hello World!
在调度真实对象之后的服务

当我们创建 HelloWorld 对象的时候,就会进入代理的逻辑方法 invoke 中,因为 JDK 生成的代理类,它继承自 Proxy 实现我们定义的 HelloWorld 接口,在实现 HelloWorld 接口方法的内部,通过反射调用了 InvocationHandlerImpl 的 invoke 方法。

2.2.2 CGLIB 动态代理

对于 JDK 动态代理技术来说,必须提供接口才能使用,在一些不能提供接口的环境中,只能采用其他的第三方技术,比如 CGLIB 动态代理。它的优势在于不需要提供接口,只要一个非抽象类就能实现动态代理。

public class CglibProxyExample implements MethodInterceptor{

    /**
     * 生成 CGLIB 代理对象
     * @param cls class类
     * @return Class类的 CGLIB 代理对象
     */
    public Object getProxy(Class cls){
        //CGLIB enhancer 增强类对象
        Enhancer enhancer = new Enhancer();
        //设置增强类型
        enhancer.setSuperclass(cls);
        //定义代理逻辑对象为当前对象,要求当前对象实现 MethodInterception 方法
        enhancer.setCallback(this);
        //生成并返回代理对象
        return enhancer.create();
    }


    /**
     * 代理逻辑方法
     * @param proxy 代理对象
     * @param method 方法
     * @param args 方法参数
     * @param methodProxy 方法代理
     * @return 代理逻辑返回
     * @throws Throwable 异常
     */
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)throws Throwable{
        System.out.println("调用真实对象前");
        //CGLIB 反射调用真实对象方法
        Object result = methodProxy.invokeSuper(proxy,args);
        System.out.println("调用真实对象后");
        return result;
    }
}

这里用了 CGLIB 的加强者 Enhancer ,通过设置超类的方法(setSuperclass),然后通过 setCallback 方法设置哪个类成为它的代理类。其中,参数为 this 就意味着是当前对象,那就要求用这个 this 这个对象实现接口 MethodInterceptor 的方法——intercept,然后返回代理对象。在反射真实对象方法前后进行了打印,是通过如下代码完成的。

Object result = methodProxy.invokeSuper(proxy,args);

进行测试:

public void testCGLIBProxy(){
	CglibProxyExample cpe = new CglibProxyExample();
	ReflectServiceImpl obj = (ReflectServiceImpl)cpe.getProxy(ReflectServiceImpl.class);
	obj.sayHello("张三");
}

CGLIB 动态代理和 JDK 动态代理是相似的,它们都是用 getProxy 方法生成代理对象,制定代理的逻辑类,而代理逻辑类要实现一个接口的一个方法,那么这个接口定义的方法就是代理对象的逻辑方法,它可以控制真实对象的方法。

2.2.3 拦截器

设计者会为开发者设计一个拦截器供其使用,开发者只需要知道拦截器接口发方法,含义和作用即可,无须知道动态代理是怎么实现的。用 JDK 动态代理来实现一个拦截器的逻辑,先定义拦截器接口 Interceptor,代码如下:

public interface Interceptor {

    public boolean before(Object proxy, Object target, Method method,Object[] args);
    public void around(Object proxy, Object target, Method method,Object[] args);
    public void after(Object proxy, Object target, Method method,Object[] args);

}

其中要注意的是:

  • before 方法返回一个 boolean 值,它在真实对象前调用,当返回为 true 时,则反射真实对象的方法;当返回为 false 时,则调用 around 方法。
  • 在 before 方法返回为 false 的情况下,调用 around 方法。
  • 在反射真实对象方法或者是 around 方法执行之后,调用 after 方法。

实现这个接口——MyInterceptor,代码如下:

public class MyInterceptor implements Interceptor{
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("反射方法前逻辑");
        //不反射被代理对象的原有方法
        return false;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("反射方法后逻辑");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("取代了被代理对象的方法");
    }
}

接下来,使用 JDK 动态代理,就可以去实现这些方法在适当时的调用逻辑,此时,在 JDK 动态代理中使用拦截器。

public class InterceptorJdkProxy implements InvocationHandler {

    //真实对象
    private Object target;
    //拦截器的全限定名
    private String interceptorClass = null;

    public InterceptorJdkProxy(Object target,String interceptorClass){
        this.target = target;
        this.interceptorClass = interceptorClass;
    }

    /**
     * 绑定委托对象并返回一个【代理占位】
     * @param target 真实对象
     * @param interceptorClass 拦截器全限定名
     * @return 代理对象【占位】
     */
    public static Object bind(Object target,String interceptorClass){
        //取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InterceptorJdkProxy(target,interceptorClass));
    }

    /**
     * 通过代理对象调用方法,首先进入这个方法
     * @param proxy 代理对象
     * @param method 方法,被调用的方法
     * @param args 方法的参数
     * @return 代理结果返回
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(interceptorClass == null){
            //没有设置拦截器则直接反射原有方法
            return method.invoke(target,args);
        }
        Object result = null;
        //通过反射生成拦截器
        Interceptor interceptor = (Interceptor) Class.forName(interceptorClass).newInstance();
        //调用前置方法
        if (interceptor.before(proxy,target,method,args)){
            //反射原有对象方法
            result = method.invoke(target,args);
        }else{
            //返回false执行around方法
            interceptor.around(proxy,target,method,args);
        }
        //调用后置方法
        interceptor.after(proxy,target,method,args);
        return result;
    }
}

代码执行步骤:

  1. 在 bind 方法中使用 JDK 动态代理绑定了一个对象,然后返回代理对象。
  2. 如果没有设置拦截器,则直接反射真实对象的方法,然后结束,否则进行第三步。
  3. 通过反射生成拦截器,并准备使用它。
  4. 调用拦截器的 before 方法,如果返回为 true ,反射原来的方法;否则运行拦截器的 around 方法。
  5. 调用拦截器的 after 方法。
  6. 返回结果。
    在这里插入图片描述
    拦截器可以进一步简化动态代理的使用方法,使程序变得更简单,进行测试:
public static void main(String[] args) {
        HelloWorld proxy = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(),"ssm.learn.chapter2.interceptor.MyInterceptor");
        proxy.sayHelloWorld();
    }

得到结果:

反射方法前逻辑
反射方法后逻辑
取代了被代理对象的方法

由此可见,拦截器已经生效。

2.2.4 责任链模式

设计者往往会用拦截器去替代动态代理,然后将拦截器的接口提供给开发者,从而简化开发者的开发难度,但是拦截器可能有多个。举个例子,一个程序员需要请假一周,这个时候把请假申请单看成一个对象,那么这个对象会经过项目经理,部门经理,人事部等多个角色的审批,这个时候,每个角色都有机会可以通过拦截这个申请单进行审批或者修改。如图所示:
责任链模式
我们称这种模式为责任链模式,它用于一个对象在多个角色中传递的场景,例如上面这个例子,申请单到了项目经理这里,可以修改申请时间等信息,然后从而影响后面的审批,即,后面的审批需要根据前面的结果进行。这个时候就可以考虑用层层代理来实现,当申请单到项目经理处,使用第一个动态代理 proxy1;当它走到部门经理处,项目经理会得到一个在项目经理的代理 proxy1 基础上生成的 proxy2 来处理部门经理的逻辑;当它走到人事处,会在 proxy2 的基础上生成 proxy3。如果还有其他角色,以此类推即可。

首先,定义三个拦截器:

public class Interceptor1 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【拦截器1】的 before 方法");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【拦截器1】的 after 方法");
    }
}

public class Interceptor2 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【拦截器2】的 before 方法");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【拦截器2】的 after 方法");
    }
}

public class Interceptor3 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【拦截器3】的 before 方法");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【拦截器3】的 after 方法");
    }
}

对拦截器进行测试:

public static void main(String[] args) {
        HelloWorld proxy1 = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(),"ssm.learn.chapter2.responsibility.Interceptor1");
        HelloWorld proxy2 = (HelloWorld) InterceptorJdkProxy.bind(proxy1,"ssm.learn.chapter2.responsibility.Interceptor2");
        HelloWorld proxy3 = (HelloWorld) InterceptorJdkProxy.bind(proxy2,"ssm.learn.chapter2.responsibility.Interceptor3");
        proxy3.sayHelloWorld();
    }

然后就可以得到以下结果,注意观察方法的执行顺序。

【拦截器3】的 before 方法
【拦截器2】的 before 方法
【拦截器1】的 before 方法
Hello World!
【拦截器1】的 after 方法
【拦截器2】的 after 方法
【拦截器3】的 after 方法

可以看出,before 方法是按照从最后一个拦截器到第一个拦截器的加载顺序运行,而 after 方法正好相反。
从代码中可见,责任链模式的优点在于我们可以在传递链上加入新的拦截器,增加拦截逻辑,其缺点是会增加代理和反射,而代理和反射的性能不高。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值