Java反射机制
反射概念
像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。反射之所以被称为框架的灵魂,主要因为它提供了我们:在运行时分析类以及执行类中方法的能力。通过反射机制可以获取任意类的所有属性和方法,并用以调用。
反射的利弊:
利好:代码更加灵活、为各种框架的实现提供了便利。
弊端:增加代码的安全风险,性能略差。
Class类对象:
反射机制的实现需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。
Java 提供了四种方式获取 Class 对象:
1. 具体类
Class alunbarClass = TargetObject.class;
2.实例对象
TargetObject o = new TargetObject(); Class alunbarClass = o.getClass();
3.类的全限定名
(1)Class alunbarClass = Class.forName("cn.javaguide.TargetObject");
(2)ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");//类加载器方式
反射的基操:
1.创建一个类 TargetObject
package cn.javaguide; public class TargetObject { private String value;
public TargetObject() { value = "JavaGuide"; } public void publicMethod(String s) { System.out.println("I love " + s); } private void privateMethod() { System.out.println("value is " + value); } }
2. 使用反射操作这个类的方法以及属性
package cn.javaguide; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException { /** * 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例 */ Class<?> targetClass = Class.forName("cn.javaguide.TargetObject"); TargetObject targetObject = (TargetObject) targetClass.newInstance(); /** * 获取 TargetObject 类中定义的所有方法 */ Method[] methods = targetClass.getDeclaredMethods(); for (Method method : methods) { System.out.println(method.getName()); } /** * 获取指定方法并调用 */ Method publicMethod = targetClass.getDeclaredMethod("publicMethod",String.class);//方法名和参数的类型的Class类对象 publicMethod.invoke(targetObject, "JavaGuide"); /** * 获取指定参数并对参数进行修改 */ Field field = targetClass.getDeclaredField("value"); //为了对类中的参数进行修改我们取消安全检查 field.setAccessible(true); field.set(targetObject, "JavaGuide"); /** * 调用 private 方法 */ Method privateMethod = targetClass.getDeclaredMethod("privateMethod"); //为了调用private方法我们取消安全检查 privateMethod.setAccessible(true); privateMethod.invoke(targetObject); } }
反射的应用:
概念:动态代理就是不事先写代理类,而是在运行的时候,动态地创建对应的代理类。它可以在不修改源代码的情况下进行增强操作。
Java 对代理模式提供了内建的支持,jdk动态代理需要实现在java.lang.reflect包下面的Proxy类和一个InvocationHandler的接口。
InvocationHandler接口中定义了invoke方法,该方法接收一个代理对象、被代理对象的方法和参数,并返回被代理对象的方法进行增强后的返回值。
Proxy类中则提供了一个静态方法newProxyInstance,用于创建代理对象。
jdk动态代理的实现步骤:
(1) 要实现InvocationHandler接口。
(2) 需要提供一个方法来实现:把具体的目标对象和动态代理绑定起来,并在绑定好后,返回被代理的目标对象的接口,以利于客户端的操作。
(3) 需要实现invoke方法。
public class MyInvocationHandler implements InvocationHandler { //需要代理的目标对象 private Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { //调用方法之前,我们可以添加额外逻辑 System.out.println("before method " + method.getName()); Object result = method.invoke(target, args); //调用方法之后,我们也可以添加额外逻辑 System.out.println("after method " + method.getName()); return result; } }
public class TestDynamicProxy { public static void main(String[] args) { //查看代理类源码,会在项目根目录生成一个目录:com/sum/proxy/$Proxy0.java //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); //创建一个实例对象,这个对象是被代理的对象(这里是接口实现类) OurService ourService = new OurServiceImpl(); //创建一个与代理对象相关联的InvocationHandler InvocationHandler stuHandler = new MyInvocationHandler(ourService); //创建一个代理对象stuProxy来代理OurServiceImpl // 代理对象的每个执行方法都会替换执行Invocation中的invoke方法 OurService stuProxy = (OurService)Proxy.newProxyInstance( ourService.getClass().getClassLoader(), // 目标类的类加载 ourService.getClass().getInterfaces(), // 需要代理的接口,可指定多个 stuHandler); //代理去执行方法--买药 stuProxy.buyMed(); } }
jdk动态代理的类必须实现接口,有很强的局限性。
扩展:cglib动态代理
JDK 动态代理有⼀个致命的问题是其只能代理实现了接⼝的类。
有些场景下,我们的业务代码是直接实现的,并没有接⼝定义。为了解决这个问题,引入了CGLIB 动态代理机制来解决.
CGLIB(Code Generation Library)是⼀个 [基于ASM的] 字节码⽣成库,它允许我们在运⾏时对字节码进⾏修改和动态⽣成.
ASM
是一个通用的 Java
字节码操作和分析框架。
CGLIB 通过继承⽅式(代理类继承被代理类-猜测)实现代理, 很多知名的开源框架都使⽤到了CGLIB.
例如 Spring 中的 AOP 模块中: 如果⽬标对象实现了接⼝,则默认采⽤ JDK 动态代理, 否则采⽤ CGLIB 动态代理.
CGLIB 动态代理类实现步骤
1.添加依赖
和 JDK 动态代理不同, CGLIB(Code Generation Library) 实际是属于⼀个开源项⽬,如果你要使⽤它的话,需要⼿动添加相关依赖
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
2. 定义被代理类(实现类)
//声明房东(目标对象)要执行的相关操作 public interface HouseSubject { void rentHouse(); //出租房子 void saleHouse(); //卖房子 } public class Landlord implements HouseSubject{ @Override public void rentHouse() { System.out.println("房东出租房子"); } @Override public void saleHouse() { System.out.println("房东卖房子"); } }
3. ⾃定义 MethodInterceptor 并重写 intercept ⽅法, intercept ⽤于增强⽬标⽅法,和 JDK 动态代理中的 invoke ⽅法类似(写代理对象的逻辑)
// CGLIB 动态代理的逻辑 public class CGLIBDynamicProxy implements MethodInterceptor { private Object target; public CGLIBDynamicProxy(Object target){ this.target=target; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //动态代理的逻辑 System.out.println("代理接手,工作开始"); //执行目标对象中的操作 Object result=methodProxy.invoke(target,objects); System.out.println("代理离手,工作结束"); return result; } }
4. 通过 Enhancer 类的 create() 创建并使用代理类
//创建代理对象并使用 public class DynamicMain { public static void main(String[] args) { HouseSubject target=new Landlord();//有接口 Landlord target1=new Landlord();//无接口 //通过 CGLIB 创建代理对象 HouseSubject proxy= (HouseSubject) Enhancer.create(target.getClass(),new CGLIBDynamicProxy(target)); Landlord proxy1=(Landlord) Enhancer.create(target1.getClass(),new CGLIBDynamicProxy(target1)); //使用代理对象 proxy.saleHouse(); proxy.rentHouse(); proxy1.saleHouse(); proxy1.rentHouse(); } }