SSM涉及到的设计模式与动态代理技术

《JavaEE 互联网轻量级框架整合开发》第 2 章讲解的是SSM框架的前置知识:设计模式

本章重点包含:

1.反射技术 之前写过一篇:博客链接 包含了:

  • Class类的使用
  • 动态加载类
  • 方法信息的反射
  • 获取成员变量&构造函数
  • 方法反射的基本操作
  • 通过反射了解集合泛型的本质

2.动态代理、责任链模式 以及拦截器的概念

3.观察者模式

4.工厂模式和抽象工厂模式

5.Builder(构建)模式


​ 反射技术之前已经总结过了。现在学习设计模式。《JavaEE 互联网轻量级框架整合开发》这本书中只是一笔带过,讲的不够通俗,这里我们结合《图解设计模式》这本书来学习设计模式,更好的理解SSM。

动态代理模式

动态代理和责任链模式在Spring的AOP以及MyBatis中都有涉及,学好这些就能更好的探究Spring以及MyBatis的原理。

proxy模式

在了解代理模式前,先了解下什么是proxy模式:

《图解设计模式》中将Proxy归为“避免浪费”类别中,用一句话说明“只在必要的时候生成实例”来概括。

代理人以本人的身份出现,去完成工作,只有在自己无法解决的时候就会找本人来解决。

代理模式的优点,例如:

一个文档里面有一个图片,图片有标题、像素等信息,以及真实的大小。如果图片打开很慢,我们在打开文档就会很慢。那么我们就可以使用代理技术,使代理图片的标题、像素信息的“代理图”放入文档。这样我们的文档就会打开的很快,等真正浏览到图片的时候,再去加载这张图片。

代理的两个步骤:

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

静态代理和动态代理:

1、静态代理:代理类由程序员创建的然后编译成.class文件。但是其中缺点是,具有重复代码,灵活性不好,例如在执行接口A中所有方法之前加上日志逻辑,那么使用静态代理的话,在代理类中每个方法都得加,如果我想add* 开头方法加上一种逻辑,select* 开头方法加上另一种逻辑,那么就很难去实现和维护了,想解决以上困惑就要使用动态代理了。

2、动态代理:是在运行的时候,通过jvm中的反射进行动态创建对象,生成字节码对象(构造方法参数 InvocationHandler h类型),传入由我们实现InvocationHandler接口的对象,通过反射创建代理对象。 然后当调用代理对象的任何方法都会调用h中的 invoke(Object proxy,Method method,Object[] args)传入当前代理对象、当前调用的方法、方法参数值。

在Java中有多种动态代理技术,比如JDK、CGLIB、Javassist、ASM等,其中最常用的是JDK和CGLIB。

JDK动态代理:

JDK动态代理是 Java.lang.reflect .*包提供的方式,但是它必须借助一个接口才能实现。

代码演示:

package jdkproxy;
/**
 * 接口 代理类和真实对象共同的接口
 */
public interface Real {
    public String print(String x,String y);
}    
/**
 * 真实对象
 */
package jdkproxy;

public class RealImpl implements Real {
    @Override
    public String print(String x, String y) {
        return x+"爱"+y;
    }
}
package jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest {
    public static void main(String[] args) {
        Real proxy = null;
        try {
            proxy = (Real)Proxy.newProxyInstance(
                    Class.forName("jdkproxy.RealImpl").getClassLoader(),
                    Class.forName("jdkproxy.RealImpl").getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            return method.invoke(
                                    Class.forName("jdkproxy.RealImpl").newInstance(),
                                    args
                                    );
                        }
                    }
            );
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(proxy.print("牛牛","小猪"));
    }

}

上述代码的关键是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)方法,该方法会根据指定的参数动态创建代理对象。三个参数的意义如下:

  1. loader,指定代理对象的类加载器;
  2. interfaces,代理对象需要实现的接口,可以同时指定多个接口;
  3. handler,方法调用的实际处理者,代理对象的方法调用都会转发到这里(*注意)。

newProxyInstance()会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给InvocationHandler.invoke()方法。理解上述代码需要对Java反射机制有一定了解。动态代理神奇的地方就是:

  1. 代理对象是在程序运行时产生的,而不是编译期;
  2. 对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;之后我们通过某种方式执行真正的方法体,示例中通过反射调用了Hello对象的相应方法,还可以通过RPC调用远程方法。

注意:对于从Object中继承的方法,JDK Proxy会把hashCode()、equals()、toString()这三个非接口方法转发给InvocationHandler,其余的Object方法则不会转发。详见JDK Proxy官方文档。

CGLIB动态代理:

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承(非抽象类)方式实现代理。没有实现接口的话,JDK动态代理不可使用,我们就可以使用CGLIB动态代理。

代码演示:

package cglibproxy;

/**
 * 真实对象类
 */
public class Real {
    public String getString(String x,String y){
        return x+"喜欢"+y;
    }
}
public class ProxyExample implements MethodInterceptor {

    /**
     * 生成代理对象
     * @param cls ---Class类  真实对象的类类型
     * @return Class类的CGLIB代理对象
     */
    public Object getProxy(Class cls){
        Enhancer enhancer = new Enhancer();//CGLIBde 增强类对象
        enhancer.setSuperclass(cls);//设置增强类型,也就是需要代理的类
        enhancer.setCallback(this);//设置代理逻辑对象为实现了MethodInterceptor的本来对象
        return enhancer.create();//创建代理类
    }

    /**
     * 代理逻辑方法
     * @param proxy 代理对象
     * @param method 方法
     * @param args 参数
     * @param methodProxy  方法代理
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        return methodProxy.invokeSuper(proxy,args);
    }
}
package cglibproxy;

public class ProxyTest {
    public static void main(String[] args) {
        ProxyExample proxyExample = new ProxyExample();
        Real proxy = (Real)proxyExample.getProxy(Real.class);
        System.out.println(proxy.getString("牛牛","小猪"));
    }
}

上述代码中,我们通过CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,在intercept()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;通过调用MethodProxy.invokeSuper()方法,我们将调用转发给原始对象,具体到本例,就是HelloConcrete的具体方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很类似,都是方法调用的中转站。

注意:对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不会,因为它是final方法,CGLIB无法代理。

两种代理方式的总结:

  1. JDK动态代理是Java原生的实现方式,而CGLIB是第三方工具包提供的方式。
  2. JDK动态代理需实现接口代理,而CGLIB是通过继承的方式进行。
  3. JDK动态代理除了代理所有的接口方法还会代理hashCode()、equals()、toString()这三个方法,因为这个非接口的方法会转发给InvocationHandler。而CGLIB除了如getClass()、wait()等final的方法不能代理之外,所有的方法你无需指定都能代理。

拦截器

拦截器是什么?我觉得百度百科上说的很清楚:拦截器-百度百科 但是这里还是粘出来说一下:

java里的拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行,同时也提供了一种可以提取action中可重用部分的方式。在AOP(Aspect-Oriented Programming)中拦截器用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。

拦截器与过滤器的区别

过滤器可以简单理解为“取你所想取”,忽视掉那些你不想要的东西;拦截器可以简单理解为“拒你所想拒”,关心你想要拒绝掉哪些东西,比如一个BBS论坛上拦截掉敏感词汇。

1.拦截器是基于java反射机制的,而过滤器是基于函数回调的。

2.过滤器依赖于servlet容器,而拦截器不依赖于servlet容器。

3.拦截器只对action起作用,而过滤器几乎可以对所有请求起作用。

4.拦截器可以访问action上下文、值栈里的对象,而过滤器不能。

5.在action的生命周期里,拦截器可以多起调用,而过滤器只能在容器初始化时调用一次。

拦截器的实现代码:

(拦截器接口)

package interceptor;

import java.lang.reflect.Method;

public interface Interceptor {
    /**
     * 调用真实对象前执行的方法
     * @param proxy     代理对象
     * @param target    真实对象
     * @param method    需要真实对象执行的方法
     * @param args      方法参数
     * @return
     */
    public boolean before(Object proxy, Object target, Method method, Object[] args);

    /**
     * 调用真实对象
     * @param proxy
     * @param target
     * @param method
     * @param args
     */
    public void around(Object proxy, Object target, Method method, Object[] args);

    /**
     * 调用真实对象后的方法
     * @param proxy
     * @param target
     * @param method
     * @param args
     */
    public void after(Object proxy, Object target, Method method, Object[] args);
}

(拦截器实现类)

package interceptor;

import java.lang.reflect.Method;

public class InterceptorImpl 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("反射方法后逻辑");
    }
}

(绑定拦截器与真实对象)

package interceptor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

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));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(interceptorClass==null){ //如果没有拦截器的话直接反射真实对象方法
            return method.invoke(target,args);
        }
        Object result = null;
        //通过反射生成拦截器
        Interceptor intercepter = (Interceptor)Class.forName(interceptorClass).newInstance();
        //调用前置方法决定是否调用真实对象的方法
        if(intercepter.before(intercepter,target,method,args)){
            result = method.invoke(target,args);//调用真实对象方法
        }else {
            intercepter.around(intercepter,target,method,args);//不调用真实对象的方法,调用拦截器的方法
        }
        intercepter.after(intercepter,target,method,args);//后置方法
        return result;
    }
}

(测试类)

package interceptor;

import jdkproxy.Real;
import jdkproxy.RealImpl;

public class InterceptorTest {
    public static void main(String[] args) {
        //我们 借用jdkproxy中用到的Real接口以及它的实现类作为真实对象
        Real proxy= (Real)InterceptorJdkProxy.bind(new RealImpl(), "interceptor.InterceptorImpl");
        proxy.print("小猪","牛牛");
    }
}

责任链(Chain of Responsibility)模式

对于《JavaEE 互联网轻量级框架整合开发》“请假流程审批“的例子讲解责任链模式,我更倾向于《图解设计模式》中所说的“推卸责任”的理解,将一系列对象组成链表一样的责任链,当一个问题需要处理,先到第一个人,能处理就处理,如果不能处理就转发给下一个人。

”请假流程审批“是责任链模式结合拦截器的这样一个例子。对于拦截器我们可以使用层级代理的形式给各个拦截器之间形成一条责任链。

如下例代码所示:

Real proxy1= (Real)InterceptorJdkProxy.bind(new RealImpl(), "interceptor.Interceptor1");
Real proxy2= (Real)InterceptorJdkProxy.bind(proxy1, "interceptor.Interceptor2");
Real proxy3= (Real)InterceptorJdkProxy.bind(proxy2, "interceptor.Interceptor3");
proxy3.print("牛牛","小猪");

观察者(Observer)模式

观察者模式又称为“发布订阅模式”,所以这两个是同一个。

重点:

  • 一对多,一个被观察者可以同时被多个观察者监视
  • 被观察者发生变化,会通知所有的观察者。

这样一个机制反而说“发布订阅模式”就能更好的理解。

例子:

假设有一家品牌化妆品供应商,淘宝和京东都已授权并关注这家的产品,一旦有新的产品推出。这两家电商平台就会在自家的平台上上架这款产品。接下来我们就用Java自带的java.util.Observable类去实现这样的场景;

(某化妆品厂商产品库)

package observer;

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

public class ProductList extends Observable {
    private List<String> productList = null;
    private static ProductList instance;
    private ProductList(){}
    public static ProductList getInstance(){
        if(instance == null){
            instance = new ProductList();
            instance.productList = new ArrayList<String>();
        }
        return instance;
    }
    //截止到这 以上都是以单例模式窗机商品库

    //添加观察者【给京东、淘宝授权】
    public void addProductListObserver(Observer observer){
        this.addObserver(observer);
    }
    //添加新产品
    public void addProduct(String newProduct){
        productList.add(newProduct);
        System.out.println("新推出["+newProduct+"]产品入库");
        this.setChanged();//设置被观察者发生了变化
        this.notifyObservers(newProduct);//通知观察者
    }
}

(京东和淘宝)

package observer;

import java.util.Observable;
import java.util.Observer;

public class JingDongObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        String newProduct = (String)arg;
        System.out.println("[京东商城]上架新款化妆品:"+newProduct);
    }
}
-------------------------------------------------------------------
package observer;

import java.util.Observable;
import java.util.Observer;

public class TaoBaoObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        String newProduct = (String)arg;
        System.out.println("【淘宝商城】上架新款化妆品:"+newProduct);
    }
}

(测试类)

package observer;

public class ObserverTest {
    public static void main(String[] args) {
        ProductList productList = ProductList.getInstance();
        JingDongObserver jingDongObserver = new JingDongObserver();
        TaoBaoObserver taoBaoObserver = new TaoBaoObserver();
        productList.addProductListObserver(jingDongObserver);
        productList.addProductListObserver(taoBaoObserver);
        productList.addProduct("SK III 新一代神仙水");
    }
}

工厂模式与抽象工厂模式

建造者(Builder)模式