Spring支持两种代理,JDK代理和CGLIB代理。
代理的核心目标是拦截方法调用,必要的时候执行应用于特定方法的advice链。
为了这个核心功能,代理还必须支持一些附加属性。比如可以通过AopContext类(这是一个抽象类)暴露自己。所有的代理类也都实现了Advised接口,这样,在代理增加以后还可以修改advice链。代理也必须确保返回this的任何方法返回代理而不是目标。
使用JDK动态代理
JDK只能为接口增加代理。想代理的任何类必须最少实现i一个接口,返回的代理是实现了该接口的一个对象。
使用JDK代理的时候,所有的方法调用被JVM拦截,路由到代理的invoke()方法。这个方法决定方法是否是advised(使用定义了pointcut),如果是,就调用advice链,然后是方法自己(反射机制)。
对于代理里的unadvised方法,仍然要调用invoke()方法,还要执行全部检查,方法也仍然通过反射调用。
使用setInterfaces()方法,指定代理的接口列表,ProxyFactory就会使用JDK代理。
使用CGLIB代理
CGLIB为每个代理在运行期动态生成字节码,尽可能地重新使用已经生成的类。这样,代理类型会是目标对象类型的子类。
当第一次增加一个CGLIB代理的时候,CGLIB询问Spring,想怎么处理每个方法。这意味着在JDK代理里每次执行invoke()作出的决定,CGLIB代理可能只需要执行一次。因为CGLIB生成实际的字节码,带来很多灵活性。比如,CGLIB生成恰当的字节码调用unadvised的方法,减少了开销。CGLIB还能确定一个方法是否返回this,如果不,允许方法被直接调用。
CGLIB处理frozen-advice链(代理生成以后不再修改)的方法也和JDK代理不同。CGLIB用特殊方法处理frozen-advice链,减少运行期开销。
比较代理性能
我们增加DefaultSimpleBean类,用来增加代理的目标对象。
interface SimpleBean {
void advised();
void unadvised();
}
class DefaultSimpleBean implements SimpleBean {
private long dummy = 0;
@Override
public void advised() {
dummy = System.currentTimeMillis();
}
@Override
public void unadvised() {
dummy = System.currentTimeMillis();
}
}
TestPointcut用于静态检查
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import javax.annotation.Nonnull;
import java.lang.reflect.Method;
public class TestPointcut extends StaticMethodMatcherPointcut {
@Override
public boolean matches(@Nonnull Method method, @Nonnull Class cls) {
return ("advise".equals(method.getName()));
}
}
NoOpBeforeAdvice是一个before advice
import org.springframework.aop.MethodBeforeAdvice;
import javax.annotation.Nonnull;
import java.lang.reflect.Method;
public class NoOpBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(@Nonnull Method method, @Nonnull Object[] args, Object target) throws Throwable {
// no-op
}
}
下来,是使用不同代理的测试代码:
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
public class ProxyPerfTest {
public static void main(String... args) {
SimpleBean target = new DefaultSimpleBean();
Advisor advisor = new DefaultPointcutAdvisor(new TestPointcut(), new NoOpBeforeAdvice());
runCglibTests(advisor, target);
runCglibFrozenTests(advisor, target);
runJdkTests(advisor, target);
}
private static void runCglibTests(Advisor advisor, SimpleBean target) {
ProxyFactory pf = new ProxyFactory();
pf.setProxyTargetClass(true);
pf.setTarget(target);
pf.addAdvisor(advisor);
SimpleBean proxy = (SimpleBean) pf.getProxy();
System.out.println("Running CGLIB (Standard) Tests");
test(proxy);
}
private static void runCglibFrozenTests(Advisor advisor, SimpleBean target) {
ProxyFactory pf = new ProxyFactory();
pf.setProxyTargetClass(true);
pf.setTarget(target);
pf.addAdvisor(advisor);
pf.setFrozen(true);
SimpleBean proxy = (SimpleBean) pf.getProxy();
System.out.println("Running CGLIB (Frozen) Tests");
test(proxy);
}
private static void runJdkTests(Advisor advisor, SimpleBean target) {
ProxyFactory pf = new ProxyFactory();
pf.setTarget(target);
pf.addAdvisor(advisor);
pf.setInterfaces(SimpleBean.class);
SimpleBean proxy = (SimpleBean) pf.getProxy();
System.out.println("Running JDK Tests");
test(proxy);
}
private static void test(SimpleBean bean) {
long before = 0;
long after = 0;
int times = 500000;
System.out.println("Testing Advised Method");
before = System.currentTimeMillis();
for (int x = 0; x < times; x++) {
bean.advised();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after - before) + " ms");
System.out.println("Testing Unadvised Method");
before = System.currentTimeMillis();
for (int x = 0; x < times; x++) {
bean.unadvised();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after - before) + " ms");
System.out.println("Testing equals() Method");
before = System.currentTimeMillis();
for (int x = 0; x < times; x++) {
bean.equals(bean);
}
after = System.currentTimeMillis();
System.out.println("Took " + (after - before) + " ms");
System.out.println("Testing hashCode() Method");
before = System.currentTimeMillis();
for (int x = 0; x < times; x++) {
bean.hashCode();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after - before) + " ms");
Advised advised = (Advised) bean;
System.out.println("Testing Advised.getProxyTargetClass() Method");
before = System.currentTimeMillis();
for (int x = 0; x < times; x++) {
advised.getTargetClass();
}
after = System.currentTimeMillis();
System.out.println("Took " + (after - before) + " ms");
System.out.println(">>>\n");
}
}
我们使用了三种代理:
- 标准的CGLIB代理
- 使用frozen advice链的CGLIB代理(通过setFrozen()方法),advice不能修改
- JDK代理
每种代理,执行了五个测试用例:
- Advised method:是一个before advice,其中没有任何操作,减少了advice的影响
- Unadvised method:unadvised的方法
- equals():执行equals()的开销
- hashCode()
- Advised接口的执行方法:用来修改代理和查询代理信息
用例 | CGLIB (Standard) | CGLIB (Frozen) | JDK |
---|---|---|---|
Advised method | 75 | 14 | 78 |
Unadvised method | 53 | 15 | 98 |
equals() | 16 | 6 | 94 |
hashCode() | 17 | 9 | 57 |
Advised.getProxyTargetClass() | 9 | 18 | 39 |