接着学习设计模式系列,今天讲解的是代理模式。面试
什么是代理模式?设计模式
代理模式,也叫委托模式,其定义是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。它包含了三个角色:bash
Subject:抽象主题角色。能够是抽象类也能够是接口,是一个最普通的业务类型定义。maven
RealSubject:具体主题角色,也就是被代理的对象,是业务逻辑的具体执行者。ide
Proxy:代理主题角色。负责读具体主题角色的引用,经过真实角色的业务逻辑方法来实现抽象方法,并在先后能够附加本身的操做。性能
用类图来表示的话大概以下:学习
这就是代理模式的设计思路,除此以外,代理模式分为静态代理和动态代理,静态代理是咱们本身建立一个代理类,而动态代理是程序自动帮咱们生成一个代理类,能够在程序运行时再生成对象,下面分别对它们作介绍。测试
静态代理在程序运行以前,代理类.class文件就已经被建立了。仍是用上面演员演戏的例子,在静态代理模式中,咱们要先建立一个抽象主题角色 Star ,ui
public interface Star {
// 演戏
void act();
}
复制代码
接下来就是建立具体的主题角色和代理主题角色,分别实现这个接口,先建立一个具体的主题角色 Actor ,this
/**
* 演员,也就是具体的主题角色
*
* @author Tao
* @since 2019/7/9 18:34
*/
public class Actor implements Star {
public void act() {
System.out.println("演员演戏~~~");
}
}
复制代码
而后就是建立代理主题角色,也就是代理类,代理类自己并不负责核心业务的执行流程,演戏这事还得明星本身来。因此在代理类中须要将真实对象引入,下面是具体的代码实现:
/**
* 代理对象
* @author Tao
* @since 2019/7/9 18:43
*/
public class Agent implements Star {
/**
* 接收真实的明星对象
*/
private Star star;
/**
* 经过构造方法传进来真实的明星对象
*
* @param star star
*/
public Agent(Star star) {
this.star = star;
}
public void act() {
System.out.println("签合同");
star.act();
System.out.println("演完戏就收钱了");
}
}
复制代码
代码的逻辑仍是比较清晰的,经过维护一个Star对象,能够在act
里调用具体主题角色的业务逻辑,而且在核心逻辑先后能够作一些辅助操做,好比签合同,收钱等,这样代理模式的角色就都分工完成了,最后用一个场景类来验证下:
public class Client {
public static void main(String[] args) {
Star actor = new Actor();
Agent agent = new Agent(actor);
agent.act();
}
}
复制代码
运行的结果以下:
签合同 演员演戏~~~ 演完戏就收钱了
动态代理分为两种,分别是JDK动态代理和 CGLIB 动态代理,怎么又分了,代理模式分类真多,不过来都来了,就都学习一下吧。
前面说了,在动态代理中咱们再也不须要再手动的建立代理类,咱们只须要编写一个动态处理器就能够了。真正的代理对象由JDK再运行时帮咱们动态的来建立。
/**
* 动态代理处理类
*
* @author Tao
* @since 2019/7/9 19:04
*/
public class JdkProxyHandler {
/**
* 用来接收真实明星对象
*/
private Object star;
/**
* 经过构造方法传进来真实的明星对象
*
* @param star star
*/
public JdkProxyHandler(Star star) {
super();
this.star = star;
}
/**
* 给真实对象生成一个代理对象实例
*
* @return Object
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(star.getClass().getClassLoader(),
star.getClass().getInterfaces(), (proxy, method, args) -> {
System.out.println("签合同");
// 执行具体的业务逻辑
Object object = method.invoke(star, args);
System.out.println("演出完经纪人去收钱……");
return object;
});
}
}
复制代码
这里说一下Proxy.newProxyInstance
这个方法,该方法包含了三个参数,
指定动态处理器,
执行目标对象的方法时会触发事件处理器的方法。写完了动态代理实现类,咱们写个场景类测试下,
public class Client {
public static void main(String[] args) {
Star actor = new Actor();
// 建立动态代理对象实例
Star jdkProxy = (Star) new JdkProxyHandler(actor).getProxyInstance();
jdkProxy.act();
}
}
复制代码
执行结果正常输出:
签合同 演员演戏~~~ 演出完代理去收钱……
因而可知,JDK 动态代理确实发挥了代理的功能,相对于静态代理,JDK 动态代理大大减小了咱们的开发任务,同时减小了对业务接口的依赖,下降了耦合度。但它一样有缺陷,就是动态代理的实现类须要类实现接口来完成代理的业务,也就是说它始终没法摆脱仅支持interface代理的桎梏,这是设计上的缺陷。而这时CGLIB 动态代理就派上用场了。
CGLib采用了很是底层的字节码技术,其原理是经过字节码技术为一个类建立子类,并在子类中采用方法拦截的技术拦截全部父类方法的调用,顺势织入横切逻辑。但由于采用的是继承,因此不能对final修饰的类进行代理。下面咱们写一个关于CGLib的动态代理类,值得说下的是,CGLib所在的依赖包不是JDK自己就有的,因此咱们须要额外引入,若是是用maven来管理的话,就能够直接引入以下的依赖:
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.3</version>
</dependency>
</dependencies>
复制代码
使用 CGLIB 须要实现 MethodInterceptor
接口,并重写intercept 方法,在该方法中对原始要执行的方法先后作加强处理。该类的代理对象可使用代码中的字节码加强器来获取。具体的代码以下:
public class CglibProxy implements MethodInterceptor {
/**
* 维护目标对象
*/
private Object target;
public Object getProxyInstance(final Object target) {
this.target = target;
// Enhancer类是CGLIB中的一个字节码加强器,它能够方便的对你想要处理的类进行扩展
Enhancer enhancer = new Enhancer();
// 将被代理的对象设置成父类
enhancer.setSuperclass(this.target.getClass());
// 回调方法,设置拦截器
enhancer.setCallback(this);
// 动态建立一个代理类
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("签合同");
// 执行具体的业务逻辑
Object result = methodProxy.invoke(o, objects);
System.out.println("演出完经纪人去收钱……");
return result;
}
}
场景测试类:
复制代码
public class Client {
public static void main(String[] args) {
Star actor = new Actor();
// 建立动态代理对象实例
Star proxy = (Star) new CglibProxy().getProxyInstance(actor);
proxy.act();
}
}
复制代码
能够看出,测试类的逻辑和JDK动态代理差很少,其实套路都是同样的,其实技术实现不一样。
总结一下CGLIB代理模式: CGLIB建立的动态代理对象比JDK建立的动态代理对象的性能更高,可是CGLIB建立代理对象时所花费的时间却比JDK多得多。因此对于单例的对象,由于无需频繁建立对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时因为CGLib因为是采用动态建立子类的方法,对于final修饰的方法没法进行代理。
这里扩展一个知识点,那就是Spring AOP的底层实现,为何在这里说起呢?由于Spring AOP的底层实现就是基于代理模式,而JDK 动态代理和 CGLIB 动态代理均是实现 Spring AOP 的基础。咱们能够看下AOP的部分底层源码:
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 判断目标类是不是接口或者目标类是否Proxy类型,如果则使用JDK动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 使用CGLIB的方式建立代理对象
return new ObjenesisCglibAopProxy(config);
}
else {
// 上面条件都不知足就使用JDK的提供的代理方式生成代理对象
return new JdkDynamicAopProxy(config);
}
}
}
复制代码
源码的判断逻辑并不难,主要是根据目标类是不是接口或者Proxy类型来判断使用哪一种代理模式建立代理对象,使用的代理模式正是JDK动态代理和CGLIB 动态代理技术。因而可知,了解代理模式仍是很重要的,起码之后面试官问AOP的底层实现时,咱们还能吹一波呢,哈哈~~~