1. 代理只代理接口上的方法本身,不关注实现 类具体实现
一个类有a,b两个方法,a中调用了b,如果用动态代理拦截了b,那么调用a方法时,a中对b调用会被拦截么?
public class Bean1 implements Bean {
public void a() {System.out.println('a');b();}
public void b() {System.out.println('b');}
}
public class Test implements InvocationHandler {
public Bean bean;
public Test(Bean bean) {this.bean = bean;}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals('b')) {
System.out.println('c');
}
return method.invoke(bean, args);
}
public static void main(String[] args) {
Bean b = new Bean1();
Test t = new Test(b);
Bean proxy = (Bean) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[] { Bean.class }, t);
proxy.a();
proxy.b();
}
}
上述代码创建了Bean1的实例b的代理,拦截Bean上的b()方法,执行该方法前打印出 ‘c’。但调用proxy.a()方法并不会打印出c,只打印了a,和b。直接调用proxy.b()则会打印出c和b。
if (method.getName().equals('b')) {
System.out.println('c');
}
return method.invoke(bean, args);
仔细看这段代码不难发现执行proxy.a()时直接判断不需要拦截,然 后用反射invoke代理目标的方法。method.invoke(bean, args)这句等价于bean.a(),直接调用了bean1.a()方法, 根本没有被拦截,所以不 会打印出c。
2. 关 于接口上的Tair注解
如果@GetCache注解在实现类上,还会被拦截 么?
TairCacheListener有这行代码。
GetCache an = invocation.getMethod().getAnnotation(GetCache.class);
JDKProxy代理是基于接口的,所以这里的getMethod获取到的实际上是接口的方法签名,如果注解不在接口上,Listener是找不到注解的。同样,如果你有个接口中有两个方法
xxxManager{
public xxx getA();
@GetCache
public xxx getB();
}
xxxManagerImpl{
public xxx getA(){
…
getB()//getA 中调用了getB
…
}
public getB(){...};
}
和第一个例子一样,如果直接调用getA方法,其中getB()根本 不会被拦截。
所以有时候会看到有个manager依赖自己,就不惊讶了。
xxxManagerImpl{
xxxManager manager;
public xxx getA(){
…
manager.getB()
…
}
public getB(){...};
}
这样调用getA是会调用manager.getB()这个方法会被listener拦截到,如果实现的接口上 有注解,则会执行对应aop逻辑。
3.接口上注解的一些问题
public interface A{
int a();
}
public interface B{
@GetCache
int a();
}
如果一个类同时实现了以上两个接口。。。可能根据实现先后顺序不同就 有不同的行为了
另外,接口上有注解也会污染业务,Spring的@Transactional也是在实现类上的。如果缓 存的过期时间是通过运行时计算的,接口中还会有过期时间,这样造成调用复杂和接口不纯净。如果不用annotation,目前可以像Spring JDBC Template这样设计,等到遥远的java8也许可以用lamada表达式简化代码。
4.AOP联盟(aop aopalliance)的拦截和Spring的@Transactional注解
AOP联盟是一些开源机构共同参 与完成AOP底层API。Spring,Google Guice等容器的aop都是基于该api。
前面提到Spring的@Transaction注解在实现类上,因为
Spring的TransactionInterceptor中有如下代码
Class targetClass = (invocation.getThis() != null ? invocation.getThis().getClass() : null);
// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr =
getTransactionAttributeSource().getTransactionAttribute(
invocation.getMethod(), targetClass);
看到targetClass = (invocation.getThis() != null ? invocation.getThis().getClass() : null) 这行,Spring直接获得代理目标(target)的类型。再根据此类型查找annotation。所以它的注解是加在实现 类上。
AOP联盟API在拦截方法时提供了更多的 上下文信息,开发人员可以感知到正在拦截的目标是什么。
5. cglib代理
cglib代理和jdk dynamic proxy不同, jdk动态代理创建代理接口的实 现类,拦截完毕后直接调用目标对象的方 法。cglib代理通过字节码增强,直接 生层代理目标类型的子类型,并修改拦截子 类型方法实现。
所以,它可以获得实现类的方法签名。
xxxManagerImpl{
public xxx getA(){
…
getB()
…
}
@Transactional
public getB(){...};
}
这种类型的cglib代理中, getA方法中的getB调用仍旧会被拦截到(经秋年验证)。如果是jdk代理(spring里bean实现了接口且不 指定proxy-target-class),则getA里的getB调用不会被拦截