最初看Spring相关书籍,讲到AOP部分,都会首先讲解代理模式、动态代理相关内容,现在看来,java框架中代理模式是使用非常频繁的,如注解、AOP、RPC等。
代理模式
代理模式,通俗来讲,就是派出一个“代表”,代表真实对象与外界交互。”代表“ 除了实现真实对象的逻辑外,还可以增加额外的功能,比如权限校验、注解解析等。java中实现代理通常有静态代理,动态代理之分。
静态代理:在编译时真实对象已经被替换成代理对象了,通常是真实对象(AService)和代理对象(ProxyService)实现同一个接口(SpringmaService),代理类引入真实对象,因此代理对象在真实对象的基础上实现了额外功能,通过多态的特性,在调用方真实使用的是代理对象。
动态代理:在初始化时,利用反射、继承或编码,动态生成代理实例。
JDK动态代理:静态代理是在编码阶段,通过实现接口来创建具体的代理类,但是这种方式不够灵活,每增加一个方法或接口,都需要额外编码。JDK动态代理就是实现了接口,但是通过反射技术将生成代理对象的时机放在运行时,自动创建代理对象。
接口:
public interface ProxyTestInterface {
void printString();
}
真实实现:
public class ProxyTestImpl implements ProxyTestInterface {
private final static Logger LOG = LoggerFactory.getLogger(ProxyTestImpl.class);
@Override
public void printString() {
LOG.info("实际类中的实现!");
}
}
JDK代理实现:
public class ProxyInvocationHandler implements InvocationHandler {
private final static Logger LOG = LoggerFactory.getLogger(ProxyInvocationHandler.class);
private Object targetObject;
public Object getProxyObject(Object originalObject) {
this.targetObject = originalObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(),this); //使用反射返回代理对象,入参依次是真实对象的类加载对象,真实类实现的接口,InvocationHandler的实现对象
/**
* @param proxy 需要被代理的真实对象
* @param method 真实对象要调用的方法名
* @param args 真实对象调用方法的入参
* @return
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
LOG.info("动态代理包装。。。begin");
if (method.getAnnotation(Springma.class) == null) { //对于注解等额外功能的实现在此增加处理
LOG.info("存在springma注解,可以增加相应功能!");
}
Object object = method.invoke(targetObject,args); //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象(
ProxyInvocationHandler对象)的invoke方法来进行调用,此时实际调用的是真实对象方法
LOG.info("动态代理包装。。。end"); return object; }} 测试:
@Test
public void testJDKDynamicProxy() {
ProxyTestInterface interfaceObject = new ProxyTestImpl(); //真实对象
ProxyTestInterface proxyObject = (ProxyTestInterface) new ProxyInvocationHandler().getProxyObject(interfaceObject); //代理对象
proxyObject.printString();
}
Cglib实现动态代理:JDK动态代理需要被代理类实现了接口,而有些场景是没有实现接口仍然需要代理的。Cglib通过在编码时动态为被代理类增加子类,在子类方法中增加额外的功能,同时拦截一切父类的调用都走子类实例,来实现代理。所以,被代理类及其方法必须是可被继承的,没有被final修饰的。
真实类实现:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProxyClassImpl {
private static final Logger LOG = LoggerFactory.getLogger(ProxyClassImpl.class);
public void printString() {
LOG.info("不继承接口单独类打印!");
}
}
Cglib代理实现:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class DynamicProxyByCglib implements MethodInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(DynamicProxyByCglib.class);
private Object targetObject;
public DynamicProxyByCglib(Object targetObject) {
this.targetObject = targetObject;
}
public Object getProxyObject() {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(this);
enhancer.setSuperclass(targetObject.getClass());
return enhancer.create();
}
/**
*
* @param o 生成的代理对象
* @param method 需要被代理类的方法
* @param objects 方法中的入参
* @param methodProxy 代理类中方法的反射对象
* @return
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
LOG.info("cglib 代理开始--begin");
Object result = methodProxy.invoke(targetObject, objects);
LOG.info("cglib 代理结束--end");
return result;
}
}
测试:
@Test
public void testCglibDynamicProxy() {
ProxyClassImpl originalObject = new ProxyClassImpl();
ProxyClassImpl proxyObject = (ProxyClassImpl)new DynamicProxyByCglib(originalObject).getProxyObject();
proxyObject.printString();
}
javaassist字节码实现:需要直接操作字节码生成代理对象,相较于使用JDK动态代理和Cglib 难度较高,dubbo框架追求性能使用的是javaassist这种方式。
javaassist相关实现可参考大神的文章:
https://blog.youkuaiyun.com/quhongwei_zhanqiu/article/details/41597219
http://javatar.iteye.com/blog/814426
代理模式为我们分离业务代码和公用模板代码提供了重要的设计思路,想想看,我们把业务代码想象成真实对象,把常用的公共代码抽象成模块代码当做附加在代理对象上的扩展功能,这样我们每次业务功能的开发将只关注业务逻辑,而对于其他的日志、权限等模块则不需要过多关注,只要加上扩展功能的标记(注解、切面等)就可以通过代理模式实现。这样是不是很爽~
spring AOP 大量使用了动态代理模式,实际上大多数框架都使用了动态代理。