设计模式学习(六)——结构型模式之“代理模式(重构时常用)”

  • 适配器模式——把一个类的接口变成客户端所期待的另一种接口
  • 装饰模式——又名包装模式,以对客户端透明的方式扩展对象的功能,是继承关系的替代方案
  • 代理模式——开闭原则的直接支持
  • 桥接模式
  • 组合模式
  • 外观模式

一、代理与代理模式

1.1、代理

所谓静态代理就是自己手写(或者用工具生成)代理类,程序运行前就已经存在的编译好的代理类;如果需要很多代理,每个都这样去创建浪费时间也可能有很多重复代码,采用动态代理可以在程序运行期间根据需要动态的创建代理类及其实例来完成具体的功能。总的来说,根据代理的创建时机和创建方式不同,可以将代理分为静态代理和动态代理。

代理的作用:

  • 用于拦截对真实业务对象的访问(比如不需要直接买演唱会门票,把这个任务交给黄牛);
  • 代理对象应具有和目标对象相同的方法(实现相同的接口或者继承于同一个类);
  • 代理对象应该是目标对象的增强(否则就没必要代理了)。

事实上,真正的业务功能还是由目标类来实现,代理类只是用于扩展、增强目标类的行为。例如,在项目开发中我们没有加入缓冲、日志这些功能而后期需要加入,我们就可以使用代理来实现,而没有必要去直接修改已经封装好的目标类。

1.2、代理模式

在Spring AOP 与MyBatis缓存机制等都使用了代理模式。代理模式使得客户端在使用目标对象的时候 间接通过操作代理对象进行,代理对象是对目标对象的增强。本质是对 开闭原则(软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化 的直接支持。

代理模式包含如下角色:

  • 客户端:客户端面向接口编程,使用代理角色完成某项功能
  • 抽象主题:一般实现为接口,是对被代理对象行为的抽象
  • 被代理角色(目标类):直接实现上述接口,是抽象主题的具体实现
  • 代理角色(代理类):实现上述接口,是对被代理对象角色的增强

代理模式应用场景:

SpringAOP、事务原理、日志打印、权限控制、远程调用

二、静态代理

2.1、静态代理的实现模式

首先创建一个接口——>创建具体实现类实现该接口——>创建一个代理类也实现该接口。

不同之处在于具体实现类的方法要把业务逻辑功能实现起来,而代理类的方法只需要调用具体类中对应方法即可,这样在需要使用过接口中某个方法功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层

2.2、代码实例

电影的例子。电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告(最大缺点:静态代理需要生成代理对象,对象多很麻烦)

//1.抽象主题(接口)
//先得有一个接口,通用的接口是代理模式实现的基础(该接口 代表电影这个主题)
public interface Movie {
    void play();
}
//2.被代理角色(目标类)与代理角色(代理类)
//要有一个真正的实现这个 Movie 接口的类和一个只是实现接口的代理类
public class RealMovie implements Movie {
    @Override
    public void play() {
        System.out.println("您正在观看电影 《肖申克的救赎》");
    }
}

/**
这个表示真正的影片。它实现了 Movie 接口。Cinema 就是代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告;也就是说,Cinema(代理类) 与 RealMovie(目标类) 都可以播放电影,但是除此之外,Cinema(代理类)还对“播放电影”这个行为进行进一步增强,即增加了额外的处理,同时不影响RealMovie(目标类)的实现。
**/
public class Cinema implements Movie {
    RealMovie movie;
    public Cinema(RealMovie movie) {
        super();
        this.movie = movie;
    }
    @Override
    public void play() {
        guanggao(true);    // 代理类的增强处理
        movie.play();     // 代理类把具体业务委托给目标类,并没有直接实现
        guanggao(false);    // 代理类的增强处理
    }
    public void guanggao(boolean isStart){
        if ( isStart ) {
            System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
        } else {
            System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
        }
    }
}
//3.客户端
/**
如前所述,代理模式可以在不修改被代理对象的基础上(符合开闭原则),通过扩展代理类,进行一些功能的附加与增强。由于Cinema(代理类)是事先编写、编译好的,而不是在程序运行过程中动态生成的,因此这个例子是一个静态代理的应用
**/
public class ProxyTest {
    public static void main(String[] args) {
        RealMovie realmovie = new RealMovie();
        Movie movie = new Cinema(realmovie);
        movie.play();
    }
}/** Output
        电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!
        您正在观看电影 《肖申克的救赎》
        电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!
 **/

三、动态代理

3.1、什么是动态代理

动态代理可以在程序运行期间根据需要 动态地创建代理类及其实例 来完成具体的功能费(不需要代理对象)

3.2代码示例(JDK动态代理)

(1)抽象主题(接口)

首先得有一个接口,通用的接口是代理模式实现的基础

public interface Subject {
    public void doSomething();
}

(2)被代理角色(目标类)

要有一个真正的实现这个 Subject 接口的类,以便代理

public class RealSubject implements Subject {
    public void doSomething() {
        System.out.println("call doSomething()");
    }
}

(3)代理角色(代理类)与客户端

在动态代理中,代理类及其实例都是程序自动生成的,因此不需要手动去创建代理类。

在JDK动态代理中,InvocationHandler(Interface)接口和Proxy(Class)类是实现动态代理必须用到的,Proxy通过使用InvocationHandler对象生成具体的代理代理对象,下面是InvocationHandler接口的实现:

/**
 * Title: InvocationHandler 的实现 
 * Description: 每个代理的实例都有一个与之关联的 InvocationHandler
 * 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它调用invoke()去处理。
 */
public class ProxyHandler implements InvocationHandler {
    private Object proxied;   // 被代理对象
    public ProxyHandler(Object proxied) {
        this.proxied = proxied;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // 在转调具体目标对象之前,可以执行一些功能处理
        System.out.println("前置增强处理: yoyoyo...");
        // 转调具体目标对象的方法(三要素:实例对象 + 实例方法 + 实例方法的参数)
        Object obj = method.invoke(proxied, args);
        // 在转调具体目标对象之后,可以执行一些功能处理
        System.out.println("后置增强处理:hahaha...");
        return obj;
    }
}

在实现了InvocationHandler接口后,我们就可以创建代理对象了。在Java的JDK动态代理机制中,我们使用Proxy类的静态方法newProxyInstance创建代理对象,如下:

public class Test {
    public static void main(String args[]) {
        // 真实对象real
        Subject real = new RealSubject();
        // 生成real的代理对象
        Subject proxySubject = (Subject) Proxy.newProxyInstance(
                Subject.class.getClassLoader(), new Class[] { Subject.class },
                new ProxyHandler(real));
        proxySubject.doSomething();
        System.out.println("代理对象的类型 : " + proxySubject.getClass().getName());
        System.out.println("代理对象所在类的父类型 : " + proxySubject.getClass().getGenericSuperclass());
    }
}/** Output
        前置增强处理: yoyoyo...
        call doSomething()
        后置增强处理:hahaha...
        代理对象的类型 : com.sun.proxy.$Proxy0
        代理对象所在类的父类型 : class java.lang.reflect.Proxy
 **/

代理对象proxySubject所对应的类继承自java.lang.reflect.Proxy类,这也正是JDK动态代理机制无法实现对class的动态代理的原因:Java只允许单继承。

(4)JDK中InvocationHandler接口与Proxy类

InvocationHandler接口:

InvocationHandler 是一个接口,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。的invoke() 方法决定了怎么样处理代理传递过来的方法调用

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

Proxy类:

DK通过 Proxy 的静态方法 newProxyInstance 动态地创建代理,Proxy 动态产生的代理对象调用目标方法时,代理对象会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。

    /**  
     * @param loader 类加载器
     * @param interfaces 目标类所实现的接口
     * @param h  InvocationHandler 实例
     */
    public static Object newProxyInstance(ClassLoader loader,
            Class<?>[] interfaces,
            InvocationHandler h)

3.3、CGLIB动态代理

(1)CGLIB动态代理原理

利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理

(2)什么是CGLIB

使用cglib实现动态代理,并且不要求委托类必须实现接口,底层采用asm字节码生成框架 生成代理类的字节码

public class CglibProxy implements MethodInterceptor {
	private Object targetObject;
	// 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
	public Object getInstance(Object target) {
		// 设置需要创建子类的类
		this.targetObject = target;
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(target.getClass());
		enhancer.setCallback(this);
		return enhancer.create();
	}

	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("开启事物");
		Object result = proxy.invoke(targetObject, args);
		System.out.println("关闭事物");
		// 返回代理对象
		return result;
	}
	public static void main(String[] args) {
		CglibProxy cglibProxy = new CglibProxy();
		UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDao());
		userDao.save();
	}
}

3.4、CGLIB动态代理与JDK动态代理的区别

(1)jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前 调用InvokeHandler来处理;

而cglib动态代理是利用asm开源包,对代理类的class文件加载进来,通过修改其字节码生成子类来处理。

(2)jdk动态代理只能对 实现了接口的类 生成代理,而不能针对类;cglib是针对 实现代理,主要是对指定的类生成一个子类,覆盖其中的方法

3.5、在Spring中什么时候用CGLIB或者JDK动态代理

如果目标对象实现了接口,默认采用JDK动态代理实现AOP;如果没实现接口,必须使用CGLIB库,Spring会自动切换。

 

四、Spring AOP 与动态代理

AOP专门用于处理系统中分布于各个模块中交叉关注点问题,使用动态代理机制实现的。Spring AOP 默认使用动态代理来创建AOP代理,具体通过以下几步完成:

  • Spring IOC容器创建Bean(目标类对象);
  • Bean创建完后,根据具体的切面逻辑及Bean本身使用Java动态代理技术生成 代理对象;
  • 应用程序使用上述生成的代理对象替代原对象来完成业务逻辑,从而达到增强处理的目的。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值