JDK动态代理的常见问题

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调用不会被拦截

展开阅读全文

没有更多推荐了,返回首页