代理
代理应该很熟悉了,大白话说就是中介,比如找工作、买房买车等,都可以找找中介,因为这样可以省很多时间;
在java代码中的代理其实很容易,就是用一个代理类将目标类封装起来,我们调用代理类的方法就行了,不需要直接和目标类打交道,画个简单的图:可以看到代理类和目标类的方法名最好要一样,这样的好处就是我们使用代理类就和使用目标类一样;另外,我们可以在代理类的方法中再调用一下其他类的方法,这样做有个什么好处呢?可以实现给目标类扩展新功能而不需要改变目标类的代码(专业一点就叫做解耦合)
在java中的代理分为两种,静态代理和动态代理:静态代理就是在源码阶段我们手动的写个代理类将目标类给包装起来,这种方式比较水,因为要自己写代码,最好可以自动生成这个代理类就最好了;于是就有了动态代理,动态代理就是在运行阶段有jvm自动生成这个代理类,我们直接用就好。显而易见,动态代理才是我们的主菜;
下面就分别说说静态代理和动态代理:
1.静态代理
这个没什么好说的,我们看一个最简单的例子就一目了然了,在这里,我们要思考一下怎么包装目标类最好呢?我们最好可以让代理类和目标类都实现同一个接口,那么两个类的方法名就是一样的了,然后就是把目标类传入代理类中
接口:
package com.wyq.day527;
public interface Animal {
public void run();
public void eat();
}
目标类:
package com.wyq.day527;
public class Dog implements Animal{
@Override
public void run() {
System.out.println("狗----run");
}
@Override
public void eat() {
System.out.println("狗----eat");
}
}
代理类及扩展Dog类中eat方法:
package com.wyq.day527;
public class DogAgent implements Animal{
//这里就是将通过构造器传进来的目标类给保存起来
private Animal animal;
public DogAgent(Animal animal) {
this.animal= animal;
}
@Override
public void run() {
animal.run();
}
@Override
public void eat() {
System.out.println("扩展------->这里可以进行日志或者事务处理。。。。。");
animal.eat();
}
public static void main(String[] args) {
Dog dog1 = new Dog();
DogAgent agent = new DogAgent(dog1);
//我们想对eat方法进行扩展,而不用修改Dog类中的源代码,直接在代理类中进行扩展即可
agent.eat();
}
}
测试结果如下,这样扩展起来很容易,而且对于那些不清楚源代码的程序员来说完全感觉不到Dog代理类的存在,还以为就是使用Dog类(在很多的框架中大量用到代理的这个思想)。。。
2.jdk动态代理
静态代理有个很大的缺陷,就是代理类需要自己去写,假如实际项目中用到的类跟我们这里测试的一样的简单就好了,那自己写就自己写吧!然而实际中一个类中的方法可能有几十个几百个,来,你去试试写个代理类。。。简直坑爹,而且写的代码还都差不多,这就意味着又要为另外一个类写代理的时候再重复写一遍,简直太糟糕了!
为了弥补这个缺陷,一些大佬就设计出了可以自动生成代理类的手段,这就很舒服了,这个手段是比较厉害的,但是有点儿不好理解,要仔细想想!而动态代理有两种方式,JDK动态代理和CGLib动态代理,下面说的是JDK动态代理。。。。
首先JDK动态代理就不止有代理类和目标类了,还有一个中间类,这个中间类有什么用呢?我们可以画个图看看;
上图可以简单的知道调用代理类中的所有方法实际上都是调用中间类的invoke方法,而在invoke方法中才是真正去调用对应的目标类的目标方法;这个比静态代理多了一层结构而已,好好理解一下还是很容易的。。。
在这里java已经为我们提供了Proxy代理类了,我们可以看看这个类中主要的东西:有参构造是传递进去一个InvocationHandler类型的参数然后复制给属性h;然后就是一个方法,这个方法最主要的是其中的三个参数,第一个参数是类加载器,任意类加载器都行,通常用目标类的类加载器即可;第二个参数是目标类实现的接口,跟静态代理差不多,这里是为了让代理类和目标类的方法名一样;第三个参数是一个InvocationHandler类型的参数,注意,这个h是我们要自己写代码实现的,而不是属性中的那个h哦~~
上面的InvocationHandler接口的实现类就是中间类,这个接口中只有一个invoke方法,我们可以用匿名类的形式,直接用new InvocationHandler(){重写invoke方法} 这种形式;
废话不多说我们来看一个很简单的例子就知道了:
接口:
package com.wyq.day527;
public interface Animal {
public void run();
public void eat();
}
目标类:
package com.wyq.day527;
public class Dog implements Animal{
@Override
public void run() {
System.out.println("狗----run");
}
@Override
public void eat() {
System.out.println("狗----eat");
}
}
代理类的使用以及测试结果;
package com.wyq.day527;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyProxy {
public static void main(String[] args) {
//生成$Proxy0的class文件,也就是代理类的字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Animal target = new Dog();
//注意类加载器可以是任意一个类加载器,当然我们就随便用用目标类的类加载器了;获取目标类接口的方法就不多说了;
//最主要的就是InvocationHandler中的invoke方法中的逻辑,想扩展什么就扩展什么
Animal proxyDog = (Animal)Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("现在执行的所有方法都经过动态代理....");
method.invoke(target, args);//这里有没有很熟悉,不就是反射么。。。
return null;
}
});
proxyDog.run();
proxyDog.eat();
}
}
3.CGLIB动态代理
CGLib的基本结构,下图所示,代理类去继承目标类,每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑,结构还是一目了然的;
1.CGLib的基本使用
使用一下CGLib,在JDK动态代理中提供一个Proxy类来创建代理类,而在CGLib动态代理中也提供了一个类似的类Enhancer;
使用的CGLib版本是2.2.2,我是随便找的,不同的版本有点小差异,建议用3.x版本的.....我用的maven项目进行测试的,首先要导入cglib的依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
目标类(一个公开方法,另外一个用final修饰):
package com.wyq.day527;
public class Dog{
final public void run(String name) {
System.out.println("狗"+name+"----run");
}
public void eat() {
System.out.println("狗----eat");
}
}
方法拦截器:
package com.wyq.day527;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class MyMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("这里是对目标类进行增强!!!");
//注意这里的方法调用,不是用反射哦!!!
Object object = proxy.invokeSuper(obj, args);
return object;
}
}
测试类:
package com.wyq.day527;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class CgLibProxy {
public static void main(String[] args) {
//在指定目录下生成动态代理类,我们可以反编译看一下里面到底是一些什么东西
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\java\\java_workapace");
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer = new Enhancer();
//设置目标类的字节码文件
enhancer.setSuperclass(Dog.class);
//设置回调函数
enhancer.setCallback(new MyMethodInterceptor());
//这里的creat方法就是正式创建代理类
Dog proxyDog = (Dog)enhancer.create();
//调用代理类的eat方法
proxyDog.eat();
}
}
测试结果:
之间区别
1、JDK动态代理通过反射机制实现:
通过实现InvocationHandlet接口创建自己的调用处理器;
通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。
2、CGLib动态代理:
CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程,底层是ASM实现
3、两者对比:
JDK动态代理是面向接口的。
CGLib动态代理是通过字节码底层继承要代理类来实现(被代理类不能被final关键字所修饰,)。
4、使用注意:
如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);
如果要被代理的对象不是个实现类,那么Spring会强制使用CGLib来实现动态代理