spring源码学习 七
前言
我们知道spring aop是通过动态代理来实现的,而代理技术有两种,一种是JDK自带的代理实现,另一种是Cglib的扩展技术。
一、代理之JDK实现
jdk代理只能针对接口去实现,所以我们先写一个接口Foo,里边定义一个foo方法,同时写一个Target类去实现Foo接口:
public class JDKproxy {
interface Foo{
void foo();
}
static class Target implements Foo{
@Override
public void foo() {
System.out.println("target里的foo方法");
}
}
}
接下来我们需要调用JDK的Proxy类里的newProxyInstance方法来创建代理,我们先看这个方法需要什么参数:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
ClassLoader loader
我们是没有写过代理类的源代码的,所以编译的时候也没有编译代理类,它的字节码是在运行期间生成的,因此需要一个classloader去做加载代理类字节码的工作。
Class<?>[] interfaces
传入接口的Class,即指定代理类去实现哪个接口(JDK代理基于接口)。
InvocationHandler h
指定代理类要做什么动作(核心)。
我们在main方法里调用此方法:
public static void main(String[] args) {
ClassLoader classLoader = JDKproxy.class.getClassLoader();//用来加载在运行期间动态生成的字节码
Foo proxy = (Foo)Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before 前置增强");
return null;
}
});
proxy.foo();
}
我们通过class.getClassLoader()
拿到main方法所在类的classLoader
第二个参数传入Foo.class,注意这里是Class数组,代表可以实现多个接口
第三个参数我们通过匿名内部类实现InvocationHandler
接口,invoke
方法代表需要代理类执行的具体逻辑,这里我们就让它先打印一句话"before 前置增强",接着把newProxyInstance的返回值转为Foo
,这里可以实现强转是因为代理类实现了Foo接口(参数传进去了),然后我们调用我们创建的代理的foo方法。
输出:
现在我们来看看invoke方法的参数
- 第一个是
Object proxy
,从名字可以看出,这个是代理类自己 - 第二个参数是
Method method
,它的作用是通过反射去执行被代理类的原始方法 - 第三个参数
Object[] args
代表原始方法的参数
现在我们就手动去new一个Target类(实现Foo接口),充当要被代理的对象,在invoke方法里通过method去调用它的foo方法,完成代理增强。
我们再加一行代码,充当后置增强逻辑:
public static void main(String[] args) {
//目标对象
Target target = new Target();
ClassLoader classLoader = JDKproxy.class.getClassLoader();//用来加载在运行期间动态生成的字节码
Foo proxy = (Foo)Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before 前置增强");
Object result = method.invoke(target, args);//调用目标方法
System.out.println("after 后置增强");
return result;//返回结果
}
});
proxy.foo();
}
要注意的是我们的method.invoke是可以返回一个Object的你虽然我们的foo方法返回值是void,但是还是要把result当作InvocationHandler.invoke的返回值,因为需要留给有返回值的函数使用。
运行后输出:
我们可以知道,jdk代理生成的代理类与目标类之间是平级的关系,因为都实现的同一个接口,是兄弟关系,不可以相互强制类型转换,但是可以通过共同的接口来转换(Foo)。
二、代理之Cglib实现
接下来我们来看Cglib的代理。
我们通过它的EnHancer类的create方法来创建代理,与jdk代理不同的是,它是基于继承来创建代理类的,需要指定它的父类。
首先我们创建一个Target类,里面定义一个foo方法,这次不需要去实现某个接口:
public class CglibProxy {
static class Target{
public void foo(){
System.out.println("Target里的foo方法");
}
}
}
我们来看看EnHancer类的其中一个create方法:
public static Object create(Class type, Callback callback){
......
}
第一个参数代表需要代理的类型,充当父类,第二个Callback
定义代理类要做的事,我们一般使用它的一个子接口,叫做MethodInterceptor
:
public static void main(String[] args) {
Enhancer.create(Target.class, new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return null;
}
});
}
Object obj
表代理本身,Method method
用来执行目标类方法,Object[] args
代表方法参数,后面的MethodProxy proxy
也可以看着目标类方法,但是有些差异,后续再聊。
这样看来,它其实和InvocationHandler
非常像,那我们如法炮制,new一个Target目标对象,添加前后增强逻辑,反射调用目标方法,添加返回结果:
public static void main(String[] args) {
Target target = new Target();//代理目标
Target proxy = (Target)Enhancer.create(Target.class, new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before 前置增强");
Object result = method.invoke(target, args);
System.out.println("after 后置增强");
return result;
}
});
proxy.foo();
}
这里我们用Target类型做强转,是允许的,因为代理类就是Target类的子类,是父子关系,不是兄弟关系。
运行结果:
这里我们思考一下
- Target类可以加final修饰吗?
肯定不行,因为加了final就不能被继承了,Cglib这种基于继承的方式代理就没用了。
- 那foo方法可以加final修饰吗?
也不行,因为这种继承的方式其实就是去重写foo方法,把它变成增强后的方法,如果加了final修饰,方法就不能被重写,那就不能被增强。
我们在foo方法加上final修饰:
static class Target{
public final void foo(){
System.out.println("Target里的foo方法");
}
}
在运行一下main:
增强不见了,因为是调用父类Target的原方法。
- 那jdk的代理,目标类可以加final吗?
可以,因为它是基于接口的方式去创建代理类,接口是可以被实现的,不会受Target的final修饰受影响。
接下来我们来看看MethodInterceptor.intercept的第四个参数:MethodProxy proxy
,它可以避免用反射的方式去调用目标方法。
public static void main(String[] args) {
Target target = new Target();//代理目标
Target proxy = (Target)Enhancer.create(Target.class, new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before 前置增强");
// Object result = method.invoke(target, args);//反射调用
//MethodProxy可以避免反射调用
Object result = proxy.invoke(target, args); //内部不是反射调用
System.out.println("after 后置增强");
return result;
}
});
proxy.foo();
}
我们看到这次调用的是MethodProxy的invoke方法,名字一样,参数也一样,但是内部不是反射调用!
MethodProxy还有一个方法是invokeSuper
,传进去的参数obj,也就是代理类自己,也可以不反射调用实现目标方法,这下连目标类也不需要创建了。
public static void main(String[] args) {
// Target target = new Target();//代理目标
Target proxy = (Target)Enhancer.create(Target.class, new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before 前置增强");
// Object result = method.invoke(target, args);//反射调用
//MethodProxy可以避免反射调用
// Object result = proxy.invoke(target, args); //内部不是反射调用
Object result = proxy.invokeSuper(obj, args);// new
System.out.println("after 后置增强");
return result;
}
});
proxy.foo();
}
spring用的是proxy.invoke(target, args),没有使用invokeSuper。
后续文章我们再深入学习里边的精髓…