一,问题
最近想看一下Spring的AOP源码,因为它里面涉及到了代理模式,所以就先了解一下我们常用的代理模式。其中包括静态代理、JDK的动态代理和Cglib的动态代理。在这个文章会简要地介绍这三种代理模式,同时会提供相应的实例案例。
二,解决方案
2.1 静态代理
①特点:
代理类的代码,在程序未运行前就已经处理好
②实现:
UserInterface
public interface UserInterface {
public void eat();
public void sleep();
}
UserDao
public class UserDao implements UserInterface{
@Override
public void eat() {
System.out.println("我在吃饭!!!");
}
@Override
public void sleep() {
System.out.println("我在睡觉!");
}
}
UserProxy
public class UserProxy implements UserInterface {
private UserDao userDao = new UserDao();
@Override
public void eat() {
System.out.println("我在上班");
userDao.eat();
System.out.println("我吃完了");
}
@Override
public void sleep() {
System.out.println("我在开party");
userDao.sleep();
System.out.println("我起床了!");
}
}
StaticUserMain
public class StaticUserMain {
public static void main(String[] args){
UserProxy userProxy = new UserProxy();
userProxy.eat();
System.out.println("======================");
userProxy.sleep();
}
}
③静态代理的优缺点:
优点:思路简单,实现简单
缺点:在UserInterface接口中增加方法或者删除方法,那么目标对象跟代理对象所对应的类都需要
进行修改
2.2 JDK的动态代理
①特点:
在程序运行期间,JDK会自动帮我们实现同目标对象同样的接口,然后再根据我们在代理工厂中配置的
规则,对指定的方法进行增强。
②实现:
PersonInterface
public interface PersonInterface {
public void work();
public void rest();
}
ExtraInterface
public interface ExtraInterface {
public void extraMethod();
}
PersonDao
public class PersonDao implements PersonInterface ,ExtraInterface{
@Override
public void work() {
System.out.println("我在工作呢,别打扰我!");
}
@Override
public void rest() {
System.out.println("我在休息呢,请打扰我!");
}
@Override
public void extraMethod() {
System.out.println("这是额外的方法!");
}
}
PersonProxyFactory
public class PersonProxyFactory {
//接收目标对象
private Object target;
PersonProxyFactory(Object target){
this.target = target;
}
//根据目标对象生成代理对象
public Object getProxyInstance(){
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() { //当代理对象执行方法的时候会调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断需要增强的方法,然后进行方法增强操作
String methodName = method.getName();
//方法执行的结果
Object result = null;
if ("work".equals(methodName)){
System.out.println("我在搭地铁上班呢");
method.invoke(target,args);
System.out.println("我下班了!");
}
if ("rest".equals(methodName)){
System.out.println("还有23小时就休息了!");
method.invoke(target,args);
System.out.println("不用上班的日子真难受,好想加班!");
}
if ("extraMethod".equals(methodName)){
method.invoke(target,args);
System.out.println("这个方法竟然会被执行?");
}
//代理对象执行对应方法的计算结果
return result;
}
});
return proxy;
}
}
JDKDynamicProxyMain
public class JDKDynamicProxyMain {
public static void main(String[] args){
PersonDao personDao = new PersonDao();
System.out.println("目标对象的HashCode:"+ Objects.hashCode(personDao.getClass()));
//这个地方不能强转成目标对象,也不能用目标对象来接收。否则会报错java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to DynamicProxy.JDKDynamicProxy
//应该强转成目标对象实现的接口类,以及用接口类来接收
//个人估计原因,未看源码:因为这里返回的是代理对象,而代理对象跟目标对象并不是同一个对象,所以如果我们把它强转成目标对象的话,那么在进行方法调用的时候,它就会抛出异常
PersonInterface proxyObject = (PersonInterface) new PersonProxyFactory(personDao).getProxyInstance();
System.out.println("代理对象的HashCode:"+ Objects.hashCode(proxyObject.getClass()));
proxyObject.work();
System.out.println("=================================");
proxyObject.rest();
System.out.println("==================================");
//将JDK自动生成的代理对象的class文件保存到D盘,然后我们反编译查看它的内容
String path = "D:/$Proxy0.class";
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", PersonDao.class.getInterfaces());
FileOutputStream out;
try {
out = new FileOutputStream(path);
out.write(classFile);
out.flush();
}catch (Exception e) {
e.printStackTrace();
}
}
}
③JDK动态代理的优缺点:
优点:自动帮我们实现跟目标对象一样的接口,同时对该对象的方法进行增强
缺点:目标对象必须实现接口
2.3 Cglib的动态代理
①特点:
在程序运行时,动态生成子类去继承目标对象,实现增强目标对象的方法
②实现
导入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>5.0.3</version>
</dependency>
User
public class User {
public String work(){
System.out.println("我在工作!");
return "123";
}
}
UserProxy
public class UserProxy implements MethodInterceptor {
public static <T> T newInstance(Class<? extends User> clazz) {
//创建一个生成动态代理的对象
Enhancer e = new Enhancer();
//设置动态代理对象的父类
e.setSuperclass(clazz);
//设置动态代理对象的回调对象
e.setCallback(new UserProxy());
//返回动态代理对象
return (T) e.create(); // Generate a class object
}
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName();
if ("work".equals(methodName)){
System.out.println("我在上班!");
String str = (String)proxy.invokeSuper(obj, args);
System.out.println("原方法执行的结果为:"+str);
System.out.println("我在做小程序开发!");
}
//返回的结果
return "111";
}
}
UserProxyMain
public class UserProxyMain {
public static void main(String[] args) {
User user = new User();
User userProxy = UserProxy.newInstance(user.getClass());
System.out.println(userProxy.work());
}
}
③Cglib动态代理的优缺点:
优点:对于可以继承的类都可以实现方法增强。
缺点:目标类不能被final修饰
2.4 小总结
对于静态代理,它的实现是挺简单的。但是问题就在于,目标类和代理类它们都实现了同一个接口,那么当这个接口添加或删除了某个方法的时候,目标类和代理类的代码都需要修改。麻烦指数2颗星。
对于JDK的动态代理,我觉得它是基于静态代理来进行优化的一种方法,其实经过反编译之后,我们就会发现JDK自动帮我们生成的代理类其实也是实现了跟目标类一样的接口。但是这种方式跟静态代理相比就没这么麻烦了,起码到时候,我们如果要在接口上增加或删除某个方法,那可以只修改目标类的代码。另外,那个自动生成的代理类在调用目标类的方法时,是根据反射的机制去实现的。麻烦指数1颗星。
对于Cglib的动态代理,我觉得它是基于JDK动态代理来进行优化的一种方法,因为JDK的动态代理,一定要目标类实现接口,那对于没有实现接口的目标类,就没用了。所以Cglib使用的方式是继承目标类,Cglib的代理范围要比JDK的方式更广一些。它会在程序运行期间,自动帮我们创建一个代理类,让代理类去继承这个目标类。同时,在代理类调用目标类的方法时,它也是通过反射机制去实现的。麻烦指数1颗星。
对于上述的总结,可能会存在一些小偏差,但是时间有限,后面再不断学习,不断完善。有问题,欢迎大家指出。谢谢!