Java-第十八部分-设计模式-代理模式和模板方法模式

444 阅读6分钟

设计模式全文

代理模式

  • Proxy,为对象提供一个替身,以控制对这个对象的访问(被控制对象)。通过代理对象访问目标对象,在目标对象的实现的基础上,增强额外的功能操作,扩展目标对象的功能
  • 远程对象、创建开销大的对象、需要安全控制的对象
  • 代理形式,静态代理动态代理(JDK代理、接口代理)Cglib代理(可以在内存中动态创建对象,不需要实现接口,属于动态代理的范畴) demo.png

静态代理

  • 需要定义接口或父类,被代理对象与代理对象一起实现相同的接口或者继承相同的父类
  • 如果有很多对象有相似的方法,可以将共同的方法提出来,通过代理对象完成,安全性/合法性验证
  • 优点,再不修改目标对象的功能前提下,通过代理对象对目标对象功能进行拓展
  • 缺点,实现同一个接口,一旦增加方法,代理对象和目标对象都需要维护 demo2.png
  • ITeacherDao
public interface ITeacherDao {
    void teach();
}
  • TeacherDao
public class TeacherDao implements ITeacherDao{
    @Override
    public void teach() {
        System.out.println("TeacherDao teach");
    }
}
  • TeacherDaoProxy
public class TeacherDaoProxy implements ITeacherDao{
    private ITeacherDao iTeacherDao;
    public TeacherDaoProxy(ITeacherDao iTeacherDao) {
        this.iTeacherDao = iTeacherDao;
    }
    @Override
    public void teach() {
        iTeacherDao.teach();
        System.out.println("TeacherDaoProxy teach");
    }
}

动态代理

  • 代理对象不需要实现接口,但是目标对象要实现接口
  • 利用JDK的API,动态得在内存中构建代理对象 demo.png
public static Object newProxyInstance(
ClassLoader loader, //指定当前目标对象使用的类加载器
Class<?>[] interfaces, //目标对象实现的接口类型,使用泛型方式确认
InvocationHandler h) //事件处理,执行目标对象方法时,触发事件处理器方法,
//会将当前执行的目标方法作为参数传入,在调用目标对象方法时的处理方式
  • ProxyFactory
public class ProxyFactory {
    //维护目标对象
    private Object target;
    //初始化目标对象
    public ProxyFactory(Object target) {
        this.target = target;
    }
    //给目标对象生成代理对象
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("dynamic proxy...");
                        //target 目标对象
                        Object returnVal = method.invoke(target, args);
                        System.out.println("end");
                        return returnVal;
                    }
                });
    }
}
  • 使用,用接口接
ProxyFactory pf = new ProxyFactory(new TeacherDao());
ITeacherDao proxy = (ITeacherDao) pf.getProxyInstance();
proxy.teach();
proxy.hello("zhangsan");
  • 通过反射的方式获取其方法名,进行调用 image.png
  • invoke方法 image.png
  • 最终调用NativeMethodAccessorImpl的方法,调用目标对象的方法 image.png
  • 代理对象中包含target目标对象 image.png

cglib代理

  • 针对于没有实现任何接口的类进行代理,底层ASM
  • 通过继承的形式,子类代理,是一个代码生成包,被应用到AOP框架中,SpringAOP
  1. 目标对象需要实现接口,JDK代理
  2. 目标对象不需要实现接口,Cglib代理
  • 底层通过字节码处理框架ASM转换字节码并生成新的类
  • 代理的目标类不能是final修饰;目标对象的方法如果是final/static,就不会被拦截,不会执行额外的业务方法 demo.png
  • 需要引包
  • ProxyFactory
public class ProxyFactory implements MethodInterceptor {
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    //是target的代理对象
    public Object getProxyInterface() {
        //创建工具类
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(target.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建子类对象,代理对象
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib proxy");
        Object returnVal = method.invoke(target, objects);
        return returnVal;
    }
}

image.png

  • 同样包含target image.png
  • 过程
  1. 代理类调用teacher.teach();,调用代理类中的teach,并最终调用intercept
  2. 当调用method.invoke(target, objects);,通过反射进行调用
  3. 当调用methodProxy.invokeSuper(o, objects);,通过代理类FastClass,通过索引,最终调用代理类的FastClass中的super.teacher(),调用父类方法
  4. 当调用methodProxy.invoke(o, objects);,通过代理类FastClass,调用被代理类FastClassteacher()

动态代理与cglib

  • 为什么还需要JDK代理
  1. cglib不能处理final修饰的类,这种类无法继承
  2. cglib需要强制无参构造
  • 动态代理为什么需要实现接口
  1. 代理对象需要继承Proxy,而Java是单继承的,只能通过接口去扩展
  2. 底层通过反射,创建一个暂时的$Proxy0类,并生成对应的class文件,加载该class文件,生成代理对象,通过InvocationHandler,去执行方法
  • cglib
  1. 采用创建一个继承实现类的子类,子类包含父类的实例对象,通过这个实例对象进行方法调用
  2. FastClass机制,会导致整个过程生成三个类。过程中,对类的方法建立索引,调用方法时,根据方法的签名来计算索引,通过索引调用对应的方法
  3. 生成的代理类、为生成的代理类中的每个方法建立了索引的类、为被代理类的所有方法建立了索引的类
  4. methodProxy调用invokeSuper,由代理类,最终调用代理类中的拦截器方法,在调用父类方法;调用invoke,如果由代理类调用,那么进入被代理类的FastClass调用invoke,再次由代理类调用该方法,导致循环,造成栈溢出,应该由被代理类调用,调用被代理类的FastClassinvoke,调用其被代理类的本方法

小结

  • 应用场景
  1. 防火墙代理,内网通过代理穿透防火墙,实现对公网的访问
  2. 缓存代理,请求图片资源,先到缓存代理取,取不到,再到公网或数据库中取
  3. 远程代理,远程对象的本地代表,将远程对象当作本地对象调用,通过网络与远程对象沟通
  4. 同步代理,多线程编程中,完成多线程间同步工作

模板方法模式

  • 案例,制作豆浆,制作流程一样,选材不同,口味不同
  • Template Method Pattern,行为型模式,在一个抽象类中公开定义了执行它的方法的模板,子类按需要重写方法,但是调用要以抽象类中定义的方式进行
  • 定义了一个操作的算法骨架,将一些步骤延迟到子类进行,使子类不改变算法接口,可以重定义该算法的某些特定的步骤 demo.png
  • 案例类图 demo2.png
  • SoyaMilk,算法骨架
public abstract class SoyaMilk {
    //final不让子类覆盖
    final void make() {
        select();
        addIngredient();
        soak();
        beat();
    }
    private void select() {
        System.out.println("select fresh soya bean.");
    }
    //子类实现
    abstract void addIngredient();
    private void soak() {
        System.out.println("soak...");
    }
    private void beat() {
        System.out.println("beat...");
    }
}
  • PeanutSoyaMilk,子类按需实现
public class PeanutSoyaMilk extends SoyaMilk{
    @Override
    void addIngredient() {
        System.out.println("PeanutSoyaMilk");
    }
}

钩子方法

  • 默认不做任何事,可以是一种条件,子类按需覆盖
  • 案例中,需要制作纯豆浆,不添加任何配料
  • 通过customerWantIngredient判断
final void make() {
    select();
    if (customerWantIngredient()) {
        addIngredient();
    }
    soak();
    beat();
}
  • customerWantIngredient,钩子方法
boolean customerWantIngredient() {
    return true;
}
  • PureSoyaMilk,重写钩子方法customerWantIngredient
public class PureSoyaMilk extends SoyaMilk{
    @Override
    void addIngredient() {

    }
    @Override
    boolean customerWantIngredient() {
        return false;
    }
}

Spring源码

  • IOC初始化时运用到了模板方法模式 ConfigurableApplicationContext demo.png
  • refresh方法 image.png
  • obtainFreshBeanFactory方法,调用两个抽象方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    this.refreshBeanFactory();
    return this.getBeanFactory();
}
  • refreshBeanFactory image.png
  • getBeanFactory image.png
  • 钩子方法,默认空实现 image.png
  • postProcessBeanFactory空实现 image.png
  • onRefresh空实现 image.png

小结

  • 算法只存在父类中,容易修改,只需要修改父类的模板方法,实现最大代码复用
  • 既统一了算法,也具有很大的灵活性,保证算法的结构不变,由子类提供部分步骤的实现
  • 每个不同的实现,都需要一个子类实现,导致类的个数增加
  • 模板方法用final修饰,防止子类重写
  • 适用于当完成某个过程,该过程要执行一系列步骤,这些步骤顺序基本相同,仅在个别步骤的实现不同,可以用模板方法