设计模式——代理模式的思考

代理模式是一种经过中间代理访问目标对象,以达到加强或拓展原对象功能目的的设计模式,举个例子来讲,咱们在购买飞机票时每每会经过一些第三方平台来购买,在这里第三方平台就可当作代理对象,目标对象则是各大航空公司,常见的代理方式有静态代理、动态代理以及Cglib代理。java

静态代理

静态代理属于比较典型的代理模式,它的类图以下所示,从图中能够看到客户端是经过代理类的接口来访问目标对象的接口,也就是目标对象和代理类是一一对应的,若是有多个目标接口须要代理则产生多个代理类,实现方式比较冗余,另外若是拓展接口,对应的目标对象和代理类也需修改,不易维护。 git

动态代理

动态代理经过Java反射机制或者ASM字节码技术,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。它与静态代理的主要区别在与动态代理的代理类是在运行期才会生成的,也就是说不会在编译期代理类的Class文件。常见的动态代理有JDK动态代理和Cglib动态代理。github

JDK动态代理

JDK动态代理又称接口代理,它要求目标对象必须实现接口,不然不能代理。动态代理是基于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler类来实现的,其中Proxy是拦截发生的地方,而InvocationHandler则是发生调用地方,newProxyInstance方法返回一个指定接口的代理类实例。 newProxyInstance方法设计模式

public static Object newProxyInstance(ClassLoader loader,  //目标对象的类加载器
                                      Class<?>[] interfaces, // 目标对象所实现的接口
                                      InvocationHandler h) // 事件处理器
复制代码

InvocationHandler的Invoke方法bash

public Object invoke(Object obj, Object... args) // 该方法会调用目标对象对应的方法
复制代码

在这里抛出一个问题,JDK动态代理为何必须实现接口才能代理?要弄明白这个问题,咱们须要拿到生成的代理类,下面是经过技术手段拿到的运行期的代理类,能够看到$Proxy0代理类已经继承Proxy类,因为Java是单继承的,因此只能经过实现接口的方式来实现。app

public final class $Proxy0 extends Proxy implements IUserDao {
    private static Method m1;
    private static Method m2;
    private static Method m0;
    private static Method m3;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    ...

    public final void register() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    ...
}
复制代码

CGLIB代理

CGLib相对于JDK动态代理更加灵活,它是经过生成子类来拓展目标对象的功能,使用cglib代理的对象无需实现接口,能够作到代理类无侵入,另外因CGLib具有很好的性能,因此被不少AOP框架所引用,好比Spring、Hibernate。 Cglib代理方式是经过继承来实现,其中代理对象是由Enhancer建立(Enhancer是Cglib字节码加强器,能够很方便对类进行拓展),另外,能够经过实现MethodInterceptor接口来定义方法拦截器。框架

public Object getProxyInstance() {
    Enhancer en = new Enhancer();
    // 继承被代理类
    en.setSuperclass(target.getClass());
    // 设置回调函数
    en.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("开启事务");
            // 执行目标对象的方法
            Object returnValue = method.invoke(target, objects);
            System.out.println("关闭事务");
            return null;
        }
    });
    return en.create();
}
复制代码

UserDao$$EnhancerByCGLIB$$b0e8b18d是获取到的UserDao的Cglib代理,能够看到它继承了UserDao方法,并为UserDao的每一个方法生成了2个代理方法(这里只保留了register方法),第一个代理方法CGLIB$register$0()是直接调用父类的方法,第二个方法register()是代理类真正调用的方法,它会判断是否实现了MethodInterceptor接口,若是实现就会调用intercept方法,MethodInterceptor即为setCallback时注入的MethodInterceptor的实现类。ide

public class UserDao$$EnhancerByCGLIB$$b0e8b18d extends UserDao implements Factory {
	...
    final void CGLIB$register$0() {
        super.register();
    }

    public final void register() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        // 判断是否实现了MethodInterceptor接口
        if (var10000 != null) {
            var10000.intercept(this, CGLIB$register$0$Method, CGLIB$emptyArgs, CGLIB$register$0$Proxy);
        } else {
            super.register();
        }
    }
    ...
}
复制代码

Spring AOP

Spring AOP是基于动态代理实现的对代码无侵入的代码加强方式,它从本质上来讲,是将Spring生成代理类对象放入IOC容器中,每次获取目标对象bean时都是经过getBean()方法,若是一个类被代理,那么实际经过getBean方法获取的就是代理类的对象,这也是Spring AOP为何只能做用于IOC容器中的对象。 Spring AOP默认使用的JDK动态代理,若是目标对象没有实现接口,才会使用CGLib来代理,固然也能够强制使用CGLib代理,只需加上@EnableAspectJAutoProxy(proxyTargetClass = true)注解,@EnableAspectJAutoProxy通常用来开启Aspect注解配置,若是是基于xml配置的,在配置文件添加<aop:aspectj-autoproxy/>便可。
org.aopalliance包下有两个核心接口,分别是MethodInvocationMethodInterceptor,这两个接口也是Spring AOP中的核心类函数

  • MethodInvocation: AOP对须要加强方法的封装,它是真正执行AOP拦截的,该接口只包含getMethod()方法。
  • MethodInterceptor:AOP方法拦截器,AOP的相关操做通常在其内部完成 下面代码是JdkDynamicAopProxy类,它是Spring AOP中JDK动态代理的具体实现,其中invoke()方法做为代理对象的回调函数被触发,经过invoke方法具体实现来完成对目标对象方法调用拦截或者功能加强,在invoke()方法中会建立一个ReflectiveMethodInvocation对象,该对象的proceed()方法会调用下一个拦截器,直至拦截器链被调用结束。
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		MethodInvocation invocation;
		Object oldProxy = null;
		boolean setProxyContext = false;

		TargetSource targetSource = this.advised.targetSource;
		Object target = null;

		try {
			...
			//得到定义好的拦截器链(加强处理)
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
			//若是拦截器链为空,执行原方法
			if (chain.isEmpty()) {
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
                            // ReflectiveMethodInvocation实现了ProxyMethodInvocation接口
                            // ProxyMethodInvocation继承自MethodInvocation     
				invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// 执行proceed方法,调用下一个拦截器,直至拦截器链被调用结束,拿到返回值
				retVal = invocation.proceed();
			}
			Class<?> returnType = method.getReturnType();
			if (retVal != null && retVal == target &&
					returnType != Object.class && returnType.isInstance(proxy) &&
					!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
				retVal = proxy;
			}
			else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
				throw new AopInvocationException(
						"Null return value from advice does not match primitive return type for: " + method);
			}
			return retVal;
		}
		finally {
			if (target != null && !targetSource.isStatic()) {
				// Must have come from TargetSource.
				targetSource.releaseTarget(target);
			}
			if (setProxyContext) {
				// Restore old proxy.
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}
}
复制代码

解决自我调用时没法加强的问题

TestProxyImpl被Spring Aop加强时,testA()方法内部调用tesB()方法,那么testB()也会被加强吗?实际是不会的,从下面的输出结果能够看到testB()方法未被加强,能够很容易想到testB()未被加强的根本缘由是this指的目标对象而非代理类对象性能

@Component
public class TestProxyImpl implements ITestProxy {
    @Override
    public void testA() {
        System.out.println("testA() execute ...");
        this.testB();
    }

    @Override
    public void testB() {
        System.out.println("testB() execute ...");
    }
}
// 输出
[AOP] Before ...
testA() execute ...
testB() execute ...
复制代码

若是想在testA()方法调用testB()方法时加强testB()方法,即实际调用代理对象的testB()方法,下面有两种方法能够作到。
1.设置expose-proxy属性为true
若是是Spring Boot项目能够直接使用@EnableAspectJAutoProxy(exposeProxy = true)来暴露代理对象,若是是使用XML配置的,则用<aop:config expose-proxy="true">配置便可。该方法的原理就是使用ThreadLocal暂存代理对象,而后经过AopContext.currentProxy()方法从新拿到代理对象。

// JdkDynamicAopProxy类invoke方法中的代码片断
// 判断expose-proxy属性是否true
if (this.advised.exposeProxy) {
    // 暂存到ThreadLocal中,可点入setCurrentProxy方法查看
    oldProxy = AopContext.setCurrentProxy(proxy);
    setProxyContext = true;
}
复制代码

为了能拿到代理对象,能够testA()方法作以下修改

public void testA() {
        System.out.println("testA() execute ...");
        //从ThreadLocal中取出代理对象,前提已设置expose-proxy属性为true,暴露了代理对象
        ITestProxy proxy = (ITestProxy) AopContext.currentProxy();
        proxy.testB();
 }
复制代码

2.获取代理对象的Bean
还有一种方式和上面方法的原理差很少,都是获取的代理对象再调用testB()方法,不过该方法直接从Spring容器中获取,下面直接贴代码了~

@Component(value = "testProxy")
public class TestProxyImpl implements ITestProxy,ApplicationContextAware {
    
    private ApplicationContext applicationContext;

    @Override
    public void testA() {
        System.out.println("testA() execute ...");
        applicationContext.getBean("testProxy", ITestProxy.class).testB();
    }

    @Override
    public void testB() {
        System.out.println("testB() execute ...");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
复制代码

本文相关代码地址:github.com/LJWLgl/java…

原文连接:blog.ganzhiqiang.wang/2019/02/17/…

相关文章
相关标签/搜索