水下功夫做透,水上才能顺风顺水。

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();
    }
}

 

posted @ 2020-06-22 14:36  北方寒士  阅读(148)  评论(0编辑  收藏  举报