生活中一定经常遇到代理这种情况:买火车票不一定在火车站买,也可以去代售点;游戏可以自己往,还可以找代练帮你玩;去买彩票不用去彩票机构,一个个代理销售点就可以等。而在java开发的工作中也是经常遇到:Spring的AOP,mybatis的接口实现等。
很早之前,阿笨就有淘宝开店的想法,但苦于一直找不到好的货源渠道,不知道卖什么,加上进货出货的麻烦,便放弃了。这种自己生产、自己开店、自己销售、自己出货……的经营方式确实需要精力财力,阿笨心有余而力不足。后来,阿笨想为什么不挑选好的线下厂家或者实体店,让他们负责进出货,阿笨只负责网上销售,这样所有从阿笨这里的销售额,阿笨拿提成不就行了!说不定还能赚取一定的差价,不是有句广告嘛,没有中间商不赚差价(阿笨好像说错了~~),而且还可以负责多个产品的销售。
这样,阿笨岂不越赚越多,很快就能走向人生巅峰(哈哈哈哈)。等等,这图怎么越看越有点AOP的味道,看来阿笨还是只有写代码的命呀,还是先停止意淫了,回到正题上去吧。
以上面的情况为例,假设阿笨找了一家线下实体店合作。首先抽象设计出一个商店的功能接口,这里假设使用两个,一个是商品介绍,一个是商品卖出。
public interface IShop {
// 顾客customer商品goods的信息
void goodsInfo(String goods,String customer);
// 卖出num件商品goods给顾客customer
void goodsSell(String goods,String customer,int num);
}
假设实体店是一家卖酒的店,店内一般保持总量为99的货量,低于一半就需要补货,至于货品种类这里先不管,反正店家按需按量补货。
public class WineShop implements IShop {
// 库存数量
private static int GOODS_NUM = 99;
@Override
public void goodsInfo(String goods, String customer) {
System.out.println(String.format("%s询问了商品%s的数量:%d", customer, goods, GOODS_NUM));
}
@Override
public void goodsSell(String goods, String customer, int num) {
System.out.println(String.format("卖出%d瓶%s给%s", num, goods, customer));
GOODS_NUM -= num;
if (GOODS_NUM < 50) {
System.out.println("库存不足,进行补货。");
GOODS_NUM = 99;
}
}
}
阿笨开了一家代理店,肯定也要具备这店的能力,而且最终卖出商品的还得是实体店,阿笨只是销售让顾客下单,然后再通过实体店卖出商品。
public class ProxyShop implements IShop {
private IShop shop;
public ProxyShop(IShop shop) {
this.shop = shop;
}
@Override
public void goodsInfo(String goods, String customer) {
System.out.println(String.format("%s向阿笨询问了商品%s的信息", customer, goods));
shop.goodsInfo(goods, customer);
}
@Override
public void goodsSell(String goods, String customer, int num) {
System.out.println(String.format("%s在阿笨店里购买了%d瓶%s", customer, num, goods));
shop.goodsSell(goods, customer, num);
}
}
测试一下:
public class Test {
public static void main(String[] args) {
String customer = "A";
// 阿笨开了一家代理店
ProxyShop proxyShop = new ProxyShop(new WineShop());
// 顾客A询问茅台的信息
proxyShop.goodsInfo("茅台",customer);
// 顾客A买了25瓶茅台
proxyShop.goodsSell("茅台",customer,25);
// 顾客A又询问五粮液的信息
proxyShop.goodsInfo("五粮液",customer);
// 顾客A买了25瓶五粮液
proxyShop.goodsSell("五粮液",customer,25);
// 过了一阵子,A又来询问了古井的信息
proxyShop.goodsInfo("古井",customer);
}
}
运行结果:
A向阿笨询问了商品茅台的信息
A询问了商品茅台的数量:99
A在阿笨店里购买了25瓶茅台
卖出25瓶茅台给A
A向阿笨询问了商品五粮液的信息
A询问了商品五粮液的数量:74
A在阿笨店里购买了25瓶五粮液
卖出25瓶五粮液给A
库存不足,进行补货。
A向阿笨询问了商品古井的信息
A询问了商品古井的数量:99
可以看出进货出货都是实体店负责,阿笨只是对进店的顾客进行了商品销售,术业有专攻,好不轻松,这让阿笨想起了另一句广告:我们不生产水,我们只是大自然的搬运工。
所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。
为什么要采用这种间接的形式来调用对象呢?一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。
一般包含如下角色:
代理模式的实现一般又分静态代理和动态代理,上面场景中使用的就是静态代理,而且是通过组合的方式实现。所谓静态代理就是代理类提前已实现,并在程序运行前就编译好,不是由程序动态产生的,可以通过组合和继承两种方式实现:组合式中代理类构造函数注入被代理类,且代理类及被代理类都实现同一个接口,灵活多变;继承式的实现方式则不够灵活。
静态代理容易理解和实现,没有额外开销,但当需要代理的对象多而不同时,又需要创建过多的代理类,对于维护和扩展都有一定的局限性;而动态代理牺牲一些资源开销,但带来的便利和强大确十分明显。
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。目前常见的两种实现方式有:Jdk动态代理和Cglib动态代理。
Jdk动态代理是使用jdk提供类库,不依赖第三方,基于接口实现,代理类必须实现InvocationHandler接口,并由Proxy类动态创建代理类,以上面场景为例修改:
public class ShopProxy implements InvocationHandler {
private IShop shop;
public ShopProxy(IShop shop) {
this.shop = shop;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("goodsInfo".equals(method.getName())) {
System.out.println(String.format("%s向阿笨询问了商品%s的信息", args[1], args[0]));
} else if ("goodsSell".equals(method.getName())) {
System.out.println(String.format("%s在阿笨店里购买了%d瓶%s", args[1], args[2], args[0]));
}
return method.invoke(shop, args);
}
}
动态创建交由Proxy.newProxyInstance()实现,测试一下:
public class Test {
public static void main(String[] args) {
String customer = "A";
// 1、new 一个目标对象,实现目标接口
IShop wineShop = new WineShop();
// 2、new一个InvocationHandler,注入目标对象为属性,重写invoke(代理类,被代理的方法,方法的参数列表)方法
InvocationHandler shopProxy = new ShopProxy(wineShop);
// 3、通过Proxy创建代理对象,强转为目标对象的接口类型即可使用(生成的代理对象实现了目标接口
IShop proxyInstance = (IShop) Proxy.newProxyInstance(wineShop.getClass().getClassLoader(),
wineShop.getClass().getInterfaces(), shopProxy);
// 4、通过代理实例调用方法
// 顾客A询问茅台的信息
proxyInstance.goodsInfo("茅台", customer);
// 顾客A买了25瓶茅台
proxyInstance.goodsSell("茅台", customer, 25);
// 顾客A又询问五粮液的信息
proxyInstance.goodsInfo("五粮液", customer);
// 顾客A买了25瓶五粮液
proxyInstance.goodsSell("五粮液", customer, 25);
// 过了一阵子,A又来询问了古井的信息
proxyInstance.goodsInfo("古井", customer);
}
}
运行结果:
A向阿笨询问了商品茅台的信息
A询问了商品茅台的数量:99
A在阿笨店里购买了25瓶茅台
卖出25瓶茅台给A
A向阿笨询问了商品五粮液的信息
A询问了商品五粮液的数量:74
A在阿笨店里购买了25瓶五粮液
卖出25瓶五粮液给A
库存不足,进行补货。
A向阿笨询问了商品古井的信息
A询问了商品古井的数量:99
重点地方:
public class ShopProxy implements InvocationHandler {
...
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(shop, args);
}
}
由于需要方法增强,而这个增强是需要留给开发人员开发的,因此代理类不能直接包含被代理对象,而是一个InvocationHandler,该InvocationHandler包含被代理对象,并负责分发请求给被代理对象。method.invoke(shop, args)通过反射执行被代理对象的原方法,在此方法之前或者之后加上增强代码,重写该方法即可。
IShop proxyInstance = (IShop) Proxy.newProxyInstance(wineShop.getClass().getClassLoader(),
wineShop.getClass().getInterfaces(), shopProxy);
Proxy.newProxyInstance()方法需要3个参数:类加载器(要代理的类)、被代理类实现的接口,事务处理器,根据被代理类实现的接口生成一个实现类,它就是动态代理类( $Proxy+数字)。
public final class $Proxy0 extends Proxy implements IShop {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
...
public final void goodsInfo(String var1, String var2) throws {
try {
super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
...
public final void goodsSell(String var1, String var2, int var3) throws {
try {
super.h.invoke(this, m4, new Object[]{var1, var2, var3});
} catch (RuntimeException | Error var5) {
throw var5;
} catch (Throwable var6) {
throw new UndeclaredThrowableException(var6);
}
}
...
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("pattern.proxy.IShop").getMethod("goodsInfo", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("pattern.proxy.IShop").getMethod("goodsSell", Class.forName("java.lang.String"), Class.forName("java.lang.String"), Integer.TYPE);
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
具体的生成过程需要进一步分析newProxyInstance方法,这里暂不做详述,大体上说一下创建的过程:由于拿到了接口,便可以获知接口的所有信息(主要是方法的定义),也就能声明一个类去实现该接口的所有方法,不过这些方法都是“虚”的,全部通过调用h(InvocationHandler)实现,就是传递的第三个参数。
注意 super.h.invoke(this,…) 时把自己this传递了过去,InvocationHandler的invoke第一个参数就是动态代理实例类,业务上有需要时可以使用它,但千万不要把请求分发它,否则就死循环了。
Java只允许单继承,上面的分析中,Proxy生成的代理类本身就继承了Proxy类,所以Jdk不能完成继承式的动态代理,当遇到基于类的动态代理,那么Cglib就是一个很好的选择。Cglib是一个Java字节码生成库,提供了易用的API对Java字节码进行创建和修改。关于这个开源库的更多细节,请移步至Cglib在github上的仓库:https://github.com/cglib/cglib。
ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
public class WineShop {
private static int GOODS_NUM = 99;
public void goodsInfo(String goods, String customer) {
System.out.println(String.format("%s询问了商品%s的数量:%d", customer, goods, GOODS_NUM));
}
public void goodsSell(String goods, String customer, int num) {
System.out.println(String.format("卖出%d瓶%s给%s", num, goods, customer));
GOODS_NUM -= num;
if (GOODS_NUM < 50) {
System.out.println("库存不足,进行补货。");
GOODS_NUM = 99;
}
}
}
public class CglibProxy implements MethodInterceptor {
// o 代理类本身
// method 拦截的方法
// objects 入参
// methodProxy cglib方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if ("goodsInfo".equals(method.getName())) {
System.out.println(String.format("%s向阿笨询问了商品%s的信息", objects[1], objects[0]));
} else if ("goodsSell".equals(method.getName())) {
System.out.println(String.format("%s在阿笨店里购买了%d瓶%s", objects[1], objects[2], objects[0]));
}
// 注意不要使用method.invoke(o, objects),自调用死循环
return methodProxy.invokeSuper(o, objects);
}
}
测试一下:
public class Test {
public static void main(String[] args) {
String customer = "A";
// 和Proxy类差不多,既能够代理普通的class,也能够代理接口
Enhancer enhancer = new Enhancer();
// 设置需要代理的对象
enhancer.setSuperclass(WineShop.class);
// 设置代理人
enhancer.setCallback(new CglibProxy());
WineShop wineShopProxy = (WineShop) enhancer.create();
// 顾客A询问茅台的信息
wineShopProxy.goodsInfo("茅台", customer);
// 顾客A买了25瓶茅台
wineShopProxy.goodsSell("茅台", customer, 25);
// 顾客A又询问五粮液的信息
wineShopProxy.goodsInfo("五粮液", customer);
// 顾客A买了25瓶五粮液
wineShopProxy.goodsSell("五粮液", customer, 25);
// 过了一阵子,A又来询问了古井的信息
wineShopProxy.goodsInfo("古井", customer);
}
}
运行结果:
A向阿笨询问了商品茅台的信息
A询问了商品茅台的数量:99
A在阿笨店里购买了25瓶茅台
卖出25瓶茅台给A
A向阿笨询问了商品五粮液的信息
A询问了商品五粮液的数量:74
A在阿笨店里购买了25瓶五粮液
卖出25瓶五粮液给A
库存不足,进行补货。
A向阿笨询问了商品古井的信息
A询问了商品古井的数量:99
从代码可以看出,Cglib只需要一个类就可以产生一个代理对象,是“类的代理”。
大体过程:生成一个继承被代理的对象的代理类,持有一个MethodInterceptor,在setCallback时传入的。代理类重写被代理的对象所有的公开方法,然后构建名叫“CGLIB”+“父类方法名”的方法(暂称cglib方法),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。这样,代理类中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。当调用代理类的重写方法时,先用MethodInterceptor的intercept方法,在intercept里通过调用cglib方法间接调用父类方法完成整个方法链的调用,可以在此前后进行方法增强。
这里一定注意,如果通过反射 method.invoke(o,objects) 方式是无法调用到父类的方法的,代理类进行了方法重写,隐藏了父类的方法,如果执行method.invoke(o,objects)很明显会死循环。所以使用 methodProxy.invokeSuper(o, objects) 并且不是简单的invoke,而是invokeSuper,因为cglib采用了fastclass机制,不仅巧妙的避开了调不到父类方法的问题,还加速了方法的调用。fastclass基本原理是:给每个方法编号,通过编号找到方法执行避免了通过反射调用。
既然Cglib利用继承实现,那么很明显无法代理被final修饰的类,也无法代理非公开方法。
首先回顾一下如何用编程的方式去加载MyBatis框架:
String resource = "mybatis-config.xml";
SqlSession session = null;
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
session = factory.openSession();
Mapper mapper = session.getMapper(Mapper.class);
Object result = mapper.getById("ID");
...
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != session) {
session.close();
}
}
首先读取配置文件,构建SqlSessionFactory,Mybatis的核心类之一,其中主要是构建Configuration,包含了所有的配置信息,还有就是映射器等,这里先不深入讨论,主要讨论分析Mapper mapper = session.getMapper(Mapper.class) 这样一句:
分析SqlSession的执行过程前,先提出一个问题:接口是抽象的,是不能实例化和直接运行的,Mybatis是如何通过接口执行sql的呢?
public class DefaultSqlSession implements SqlSession {
...
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
...
}
调用了Configuration对象的getMapper方法,来获取对应的接口对象,继续:
public class Configuration {
...
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
...
}
调用映射器的注册器MapperRegistry的getMapper方法,来获取对应的接口对象,继续:
public class MapperRegistry {
...
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
...
}
首先判断是否注册一个mapper,如果没有会抛出异常,有则调用 mapperProxyFactory.newInstance(sqlSession) 生成一个代理实例,继续:
public class MapperProxyFactory<T> {
...
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
至此,可以看出Mapper映射是通过动态代理来实现的,可以看到Jdk动态代理对接口的绑定,它的作用是生成动态代理对象,而代理的方法则被放到了MapperProxy类中,继续:
public class MapperProxy<T> implements InvocationHandler, Serializable {
...
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
}
看到实现了接口InvocationHandler,并重写了invoke方法,这里稍微讲一下这里的逻辑:上面的Jdk动态代理中讲过,Proxy生成的代理对象中除了接口的方法,还会有hashCode()、toString()、equals(Object var1)三个所有类继承超类Object的方法,所以要先判断这个方法是来自于接口还是继承的Object方法。如果是Object类继承来的方法,直接反射调用,如果是接口规定的方法,就初始化一个MapperMethod对象,通过execute()方法执行,并把SqlSession和当前运行参数传进去。
这里主要讲动态代理的应用,鉴于篇幅关于execute的执行逻辑这里就暂不讲解,有兴趣的同学可以自行查看,另外上面的提出的问题相信大家已经找到答案了。
现在流行的AOP编程也是利用的动态代理,最常见的就是spring-aop,其详细的过程比较复杂,本文就暂不细述了,简单提一下关键代码,能看到使用的是动态代理技术:
DefaultAopProxyFactory
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
...
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// config.isOptimize()是否使用优化的代理策略,目前使用与CGLIB
// config.isProxyTargetClass()是否目标类本身被代理而不是目标类的接口
// hasNoUserSuppliedProxyInterfaces()是否存在代理接口
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
// 使用Jdk代理
return new JdkDynamicAopProxy(config);
} else {
// 获取AOP配置的目标类
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.");
} else {
// 如果目标类是接口或者已经是代理类,使用JDK动态代理,否则使用Cglib代理
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
...
}
JdkDynamicAopProxy
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
...
public Object getProxy(ClassLoader classLoader) {
...
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
} else {
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
...
}
...
}
ObjenesisCglibAopProxy 继承 CglibAopProxy
class CglibAopProxy implements AopProxy, Serializable {
...
public Object getProxy(ClassLoader classLoader) {
...
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new CglibAopProxy.ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
Callback[] callbacks = this.getCallbacks(rootClass);
Class<?>[] types = new Class[callbacks.length];
for(x = 0; x < types.length; ++x) {
types[x] = callbacks[x].getClass();
}
enhancer.setCallbackFilter(new CglibAopProxy.ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
...
}
...
private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
...
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
...
}
...
}
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样对每一个方法或方法组合进行处理。
Jdk动态代理只适用于接口,Cglib既适用于普通的class,也适用于接口,那平时的工作中应该怎么选择呢?Spring默认使用Jdk动态代理,如果类没有接口,则使用Cglib的策略给了我们一种选择参考。
关于常用的两种动态代理性能:
Jdk创建对象的速度远大于Cglib,因为Cglib创建对象需要操作字节码,但Cglib执行速度略大于Jdk(曾经优势明显),毕竟反射调用时需要一定的开销(不过Jdk的反射性能一直在优化)。另外由于Cglib的大部分类是直接对Java字节码进行操作,这样生成的类会一直占用资源中,要注意避免Cglib动态代理操作过多。