之前面试时面试官问我:“你知道spring中有哪几种代理吗?” 啊?代理?啥子代理?VPN代理吗?嘿嘿,面试官你要种子直说啊......被刷下来了。好吧,入门学习下代理。
为什么需要代理?参考大佬的博客:
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。
就是说代理对象可以在执行目标对象的方法的前后,加加日志啊,判断参数啊...之类的。
代理有哪些?
代理有:静态代理,JDK动态代理,CGLIB代理。
静态代理:代理对象和目标对象实现同一个接口或继承同一个父类。这里以实现接口为例子。
学员接口:
public interface StudentInterface {
//保存数据
void saveData();
}
学员接口实现(目标对象):
public class StudentImp implements StudentInterface {
@Override
public void saveData() {
System.out.println("I have been saved data.");
}
}
静态代理对象:
public class StaticProxyBean implements StudentInterface {
StudentInterface studentInterface;
public StaticProxyBean(StudentInterface studentInterface) {
this.studentInterface = studentInterface;
}
@Override
public void saveData() {
System.out.println("start proxy.");
studentInterface.saveData();
System.out.println("end proxy.");
}
}
测试:
public class TestStaticProxy {
public static void main(String[] args) {
StudentInterface userDao = new StudentImp();
//代理对象 代理 目标对象
StaticProxyBean staticProxyBean = new StaticProxyBean(userDao);
staticProxyBean.saveData();
}
}
静态代理很简单,不需要引入任何的jar包,但是它有缺点:代理对象需要实现所有的目标对象方法。就比如目标对象StaticProxyBean有deleteData()方法,findData()方法,代理对象StaticProxyBean也得实现。代码很多。如何解决呢?JDK动态代理。
动态代理:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)。
也就是说,1.代理对象不需要实现StudentInterface接口了,但它要实现jdk的InvocationHandler接口。2.java会生成class字节码级别的代理对象,也就是说,代理对象是一个新的类。我们在文后会给出。
JDK代理对象,参考第二位大佬的博客:
public class DynamicProxyBean implements InvocationHandler {
private Object target;
public Object bind(Object target) {
this.target = target;
//loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来生成代理对象进行加载
//interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
//h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上,间接通过invoke来执行
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//输出代理的方法
System.out.println("==========" + method.getName() + "=============");
//方法执行前后 打印当前时间
System.out.println("begin:" + DateTimeEx.TimeStampToString(System.currentTimeMillis(), "yyyy-MM-dd hh:mm:ssSSS"));
Object invoke = method.invoke(target, args);
System.out.println("end:" + DateTimeEx.TimeStampToString(System.currentTimeMillis(), "yyyy-MM-dd hh:mm:ssSSS"));
return invoke;
}
}
测试:
public class TestDynamicProxy {
public static void main(String[] args) {
StudentInterface dao = new StudentImp();
//通过bind方法获得被代理后的对象
dao = (StudentInterface) new DynamicProxyBean().bind(dao);
dao.saveData();
}
}
好吧,JDK动态代理其实也有缺点,就是目标对象必须要实现接口,也就是例子中StudentImp实现了StudentInterface接口,否则不能使用动态代理。那么这时候CGLIB代理就来了。
CGLIB代理:
1.需要引入cglib的jar文件。
2.引入功能包后,就可以在内存中动态构建子类。
3.代理的类不能为final,否则报错。
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
5.如果方法为static,private则无法进行代理。
CGLIB代理对象:
public class CgLibProxyBean implements MethodInterceptor {
private Object target;
public Object createProxyObject(Object target) {
this.target = target;
//增强器,动态代码生成器
Enhancer enhancer = new Enhancer();
//设置生成类的父类类型
enhancer.setSuperclass(target.getClass());
//回调方法
enhancer.setCallback(this);
//动态生成字节码并返回代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("==========" + method.getName() + "=============");
//方法执行前后 打印当前时间
System.out.println("begin:" + DateTimeEx.TimeStampToString(System.currentTimeMillis(), "yyyy-MM-dd hh:mm:ssSSS"));
Object invoke = methodProxy.invokeSuper(o, objects);
System.out.println("end:" + DateTimeEx.TimeStampToString(System.currentTimeMillis(), "yyyy-MM-dd hh:mm:ssSSS"));
return invoke;
}
}
测试:
public class TestCglibProxy {
public static void main(String[] args) {
CgLibProxyBean cgLibProxyBean = new CgLibProxyBean();
StudentInterface userDao = new StudentImp();
userDao = (StudentInterface) cgLibProxyBean.createProxyObject(userDao);
userDao.saveData();
}
}
测试结果就不贴图片了,都一样。
看到这同学可能会问,CGLIB就没有缺点了吗?有的,慢,他比JDK动态代理慢。所以Spring AOP需要代理的时候,先会判断能否使用JDK动态代理,不能的话,才使用CGLIB代理。
最后贴一个JDK动态代理生成的字节码文件,参考http://www.importnew.com/26116.html:
public class DynamicProxyUtil {
public static void main(String[] args) throws IOException {
byte[] classFile = ProxyGenerator.generateProxyClass("TestProxyGen", StudentImp.class.getInterfaces());
File file = new File("C:\\Users\\Administrator\\Desktop\\TestProxyGen.class");
FileOutputStream fos = new FileOutputStream(file);
fos.write(classFile);
fos.flush();
fos.close();
}
}
public final class TestProxyGen extends Proxy implements StudentInterface {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public TestProxyGen(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void saveData() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("proxy_test.static_proxy.StudentInterface").getMethod("saveData");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
它实现了StudentInterface接口,m3获取了saveData()方法,通过反射执行saveData()方法。
说实话,代理还是有很多不懂,都是从网上看的其他大佬的博客,自己先拼凑记录下来。
完--