近况
昨天来的杭州,暂时住的青旅,毕竟工作还没稳定下来,也不方便租房。。。
今年得赶紧行动起来了,还有很多东西都不会,我还是太差了,继续学习吧~
回归正题,之前写过反射和代理,今天重新复习了一下这方面的东西,并把他们整理到一起
之前的几篇就删除吧,温故而知新,可以为师矣~
下面内容借鉴JavaGuide中讲解,也可以直接去那里查看,比我写的好一万倍~
反射
反射使得我们拥有可以在类的运行时期执行类的方法和属性的能力
动态代理模式的实现也依赖反射机制,一般和注解配合使用,例如@Component,@Value都是使用动态代理模式
学习反射操作就是学会使用Class对象的API?不知道正不正确,我简单的这么理解
那么什么是Class对象呢?
Java中万物都是对象,对象是类的具体,类是对象的概述,例如水果与苹果,蔬菜与白菜 and so on
那么我们编写的类当然也可以是对象,既然我们编写的一个个类也可以看做是一个对象,
那么这些对象的类是谁呢?Class类!我们可以通过这个类创建Class对象,这个对象包含了我们编写的类的信息
换句话说,我们编写的一个个类,也可以作为一个对象呈现出来,即Class对象,这个对象包含类的很多方法和属性
获取Class对象的方式有以下几种:
知道具体类的情况下可以使用:
Class alunbarClass = TargetObject.class;
但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化通过
Class.forName()
传入类的全路径获取:Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
通过对象实例
instance.getClass()
获取:TargetObject o = new TargetObject(); Class alunbarClass2 = o.getClass();
通过类加载器
xxxClassLoader.loadClass()
传入类路径获取:Class clazz = ClassLoader.loadClass("cn.javaguide.TargetObject");
通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行
获取构造器对象及使用
获取构造器对象的几种方法:
aClass1.getConstructor(); 获取单个的公共的构造器对象
aClass1.getConstructors();
aClass1.getDeclaredConstructor();
aClass1.getDeclaredConstructors(); 获取所有私有公有的构造器集合
构造器对象使用
Constructor<? extends Student> constructor =
aClass1.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Student instance = constructor.newInstance("胡歌");
获取字段和方法对象及其使用
获取字段和方法对象的几种方法
aClass1.getField(); 获取一个公有的字段
aClass1.getFields();
aClass1.getDeclaredField();
aClass1.getDeclaredFields(); 获取多个公有私有字段
aClass1.getMethod();
aClass1.getMethods();
aClass1.getDeclaredMethod();
aClass1.getDeclaredMethods();
字段的使用
Constructor<? extends Student> constructor =
aClass1.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Student student1 = constructor.newInstance("胡歌");
Field age = aClass1.getDeclaredField("age");
age.setAccessible(true);
age.set(student1,38);
System.out.println(student1);
方法的使用
Constructor<? extends Student> constructor =
aClass1.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
Student student1 = constructor.newInstance("胡歌",38);
Method show = aClass1.getDeclaredMethod("show",String.class);
show.setAccessible(true);
show.invoke(student1,"男");
反射的练习
//往ArrayList<Integer>集合中添加字符串数据
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(10);
arrayList.add(20);
Class<? extends ArrayList> aClass = arrayList.getClass();
Method add = aClass.getDeclaredMethod("add", Object.class);
add.setAccessible(true);
add.invoke(arrayList,"胡歌");
add.invoke(arrayList,"张译");
System.out.println(arrayList);
代理模式
代理这个词用的很广泛,例如我们都知道nginx是反向代理服务器,它代理的是后端Tomcat服务器,
nginx代替Tomcat接收转发请求,帮助服务器一方做一些事就是反向代理,而我们熟知的VPN则是正向代理
VPN就是接收墙外数据再转发给我们,帮助客户端做一些事就是正向代理。
总之,代理的意思就是帮助被代理方做一些增强,使之拥有一些能力
而代理模式也是同样的意思,对被代理类做一些增强,例如在被代理类方法执行的前后做一些操作等等。
代理模式有两种实现,一是静态代理,二是动态代理,下面具体看看吧
静态代理
静态代理是,接口的实现类(被代理类)并不直接创建对象调用实现方法,而是将实现类对象注入到另一个实现类(代理类)中
由代理类去执行实现类中的实现方法,代理类还可以在执行实现方法前后加入自己的方法,使得程序更加健壮。
Thread就是典型的代理类,Thread类也是实现了Runnable接口,当我们自定义类实现Runnable接口后,
将自定义类对象通过Thread构造方法交给Thread后,由Thread类代替我们去执行我们的实现方法,即调用start方法。静态代理实现步骤:
- 定义一个接口及其实现类;
- 创建一个代理类同样实现这个接口
- 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
静态代理案例
定义发送短信的接口
public interface SmsService { String send(String message); }
实现发送短信的接口
public class SmsServiceImpl implements SmsService { public String send(String message) { System.out.println("send message:" + message); return message; } }
创建代理类并同样实现发送短信的接口
public class SmsProxy implements SmsService { private final SmsService smsService; public SmsProxy(SmsService smsService) { this.smsService = smsService; } @Override public String send(String message) { //调用方法之前,我们可以添加自己的操作 System.out.println("before method send()"); smsService.send(message); //调用方法之后,我们同样可以添加自己的操作 System.out.println("after method send()"); return null; } }
实际使用
public class Main { public static void main(String[] args) { SmsService smsService = new SmsServiceImpl(); SmsProxy smsProxy = new SmsProxy(smsService); smsProxy.send("java"); } }
运行上述代码之后,控制台打印出:
before method send() send message:java after method send()
可以输出结果看出,我们已经增加了
SmsServiceImpl
的send()
方法
静态代理的缺陷
- 从实现和应用角度来说,静态代理非常不灵活,比如接口新增方法,目标对象和代理对象都要进行更改,
而且非常麻烦(需要对每个目标类都单独写一个代理类),实际应用场景非常少。 - 从JVM 角度来说,静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
而动态代理就不会产生新的class文件
动态代理
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,
并且也不需要我们必须实现接口,我们可以直接代理实现类( CGLIB 动态代理机制)。从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等。
JDK动态代理
在JDK动态代理机制中InvocationHandler接口和Proxy类是核心。
Proxy类中使用频率最高的方法是:newProxyInstance(),这个方法主要用来生成一个代理对象。JDK动态使用步骤
- 定义一个接口及其实现类,该实现类作为被增强类。
- 自定义类实现InvocationHandler接口并重写invoke方法,该自定义类作为代理类,
在invoke方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑。 - 通过Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法
创建代理对象。
案例:
1)UserService接口 public interface UserService { String saying(String data); } 2)UserServiceImpl,被代理类,需要增强的类 public class UserServiceImpl implements UserService { @Override public String saying(String data) { System.out.println("被增强类:"+data); return data; } } 3)JDKProxy,代理类,自定义类实现InvocationHandler接口,重写invoke方法 当代理对象执行方法时,会执行invoke方法。 public class JDKProxy implements InvocationHandler { private final Object target; public JDKProxy(Object target) { this.target = target; } /* invoke()方法有下面三个参数: proxy: 动态生成的代理类 method: 与代理类对象调用的方法相对应 args: 当前method方法的参数 增强真实对象方法可以增强三处: 增强方法体:真实对象方法执行前后执行代理对象的方法,即对真实对象方法体的增强 增强参数:对真实对象方法参数进行修改,可以达到增强真实对象方法的参数 增强返回值:对真实对象方法返回值进行修改,将返回值类型该为其他类型, 可以达到增强真实对象方法的返回值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("目标方法运行前******"); Object resultValue = method.invoke(target, args); System.out.println("目标方法运行后******"); return resultValue; } } 4)MyProxyFactory,静态代理工厂,用于创建代理对象 public class MyProxyFactory { /* 静态工厂方式创建代理对象 newProxyInstance方法一共有3个参数: 第一个参数:target.getClass().getClassLoader(),被代理类的类加载器, 用于加载代理对象。 第二个参数:target.getClass().getInterfaces(),被代理类实现的一些接口。 第三个参数:new JDKProxy(target),实现了InvocationHandler接口的对象。 第三个参数也可以使用匿名内部类的方式实现。 */ public static Object getProxy(Object target){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new JDKProxy(target)); } } 5)Test测试类 public class Test { public static void main(String[] args) { UserService proxyClass = (UserService)MyProxyFactory. getProxy(new UserServiceImpl()); proxyClass.saying("fuck"); } } 6)测试结果 目标方法运行前****** 被增强类:fuck 目标方法运行后******
通过代理工厂获得代理对象后,当我们代理对象调用方法时,实际会调用到实现InvocationHandler接口的类的invoke()方法。
在本案例中就是会调用JDKProxy类的invoke方法。CGLIB动态代理
JDK动态代理有一个最致命的问题是其只能代理实现了接口的类。
为了解决这个问题,我们可以用CGLIB动态代理机制来避免。CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码
进行修改和动态生成。CGLIB 通过继承被代理类的方式实现代理。很多知名的开源框架都使用到了CGLIB,
例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,
否则采用 CGLIB 动态代理。在CGLIB动态代理机制中MethodInterceptor接口和Enhancer类是核心。
案例:1)因为CGLIB是一个开源项目,所以需要导入cglib依赖 <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> 2)还是使用JDK代理中的UserServiceImpl类作为被增强类 public class UserServiceImpl implements UserService { @Override public String saying(String data) { System.out.println("被增强类:"+data); return data; } } 3)自定义类实现MethodInterceptor,该自定义类作为代理类,实现intercept方法, 类似InvocationHandler接口的invoke方法 public class CglibProxy implements MethodInterceptor { /** * @param o 被代理的对象(需要增强的对象) * @param method 被拦截的方法(需要增强的方法) * @param objects 方法入参 * @param methodProxy 用于调用原始方法 */ public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("cglib代理******"); Object resultValue = methodProxy.invoke(o, objects); System.out.println("cglib代理******"); return resultValue; } } 4)MyProxyFactory,静态代理工厂,用于创建代理对象 public class MyProxyFactory { public static Object getProxy(Class<?> clazz){ //创建动态代理增强类 Enhancer enhancer = new Enhancer(); // 设置类加载器 enhancer.setClassLoader(clazz.getClassLoader()); // 设置被代理类 enhancer.setSuperclass(clazz); // 设置方法拦截器 enhancer.setCallback(new CglibProxy()); // 创建代理类 return enhancer.create(); } } 5)测试类 public class Test { public static void main(String[] args) { UserService proxyClass = (UserService)MyProxyFactory.getProxy(UserServiceImpl.class); proxyClass.saying("fuck"); } } 6)测试结果 cglib代理****** 被增强类:fuck cglib代理******
JDK动态代理和CGLIB动态代理对比
- JDK动态代理只能只能代理实现了接口的类或者直接代理接口,而CGLIB可以代理未实现任何接口的类。
另外,CGLIB动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为
final类型的类和方法。 - 就二者的效率来说,大部分情况都是JDK动态代理更优秀,随着JDK版本的升级,这个优势更加明显。