需求

  1. 统计每一个方法的运行时间
  2. 扩展性的创建不同类型的车,通过配置文件进行配置

实现

配置文件

  1. 配置需要什么类型的车
  2. 对于aodi车还需要特殊类型的配置
  3. 是否启用方法运行的时间代理
# 工厂类型,选择其他汽车
car.class=reflectTest.Aodi
aodi.type=A5
# 是否启用车辆的时间代理
car.time.flag=true

时间代理

  1. 这里过度设计了代理接口,以及增强器接口,想根据Spring的通知进行设计。结果画虎不成反类犬
  2. 直接看时间代理就行了。注意需要开启setAccessible(true);,这个就是上面的访问权限的问题导致的
  3. 代理的核心就是InvokeHandler
// 创建了一个接口,前、后、环形通知
interface Proxyable {
    /**
     * 之前通知,之后通知,环形通知
     * */
    Object beforeProxy(Object obj, Class<?> interfacess, Enhancer e);
    Object afterProxy(Object obj, Class<?> interfacess, Enhancer e);
    Object aroundProxy(Object obj, Class<?> interfacess, Enhancer before, Enhancer after);
}

// 具体的增强器
abstract class Enhancer {
    public abstract Object enhance(Object... args);
}

// 时间代理
public class ProxyTime implements Proxyable{

    @Override
    public Object beforeProxy(Object obj, Class<?> interfacess, Enhancer e) {
        return null;
    }

    @Override
    public Object afterProxy(Object obj, Class<?> interfacess, Enhancer e) {
        return null;
    }

    @Override
    public Object aroundProxy(Object obj, Class<?> interfacess, Enhancer before, Enhancer after) {
         return getProxy(obj,interfacess);
    }

    public static Object getProxy(Object target, Class<?>interfacess) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class[]{interfacess}, (proxy, method, args)->{
            long startTime = System.currentTimeMillis();
            // 开启可以访问的权限,否则无法进行访问!
            method.setAccessible(true);
            Object result = method.invoke(target, args);
            long endTime = System.currentTimeMillis();
            long spendTime = endTime - startTime;
            System.out.println(target.getClass() + "." + method.getName() + "()方法运行时间为:" + spendTime + "ms");
            return result;
        });
    }

}

交通工具 – 这里默认是Car

  1. 接口一般是…able,这里做成Car不合适
  2. 为了方便复用睡眠的代码,这里实现了一个RunCar,可以通过继承这个类,然后调用run()方法进行睡眠100ms
  3. 所有的实现都放在了一个文件中,导致访问权限只能是包类型,所以后面调用的时候,需要设置setAccessible(true)
public interface Car {
    void run();
}
class RunCar implements Car{
    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class BaoShiJie extends RunCar {
    @Override
    public void run() {
        System.out.println("保时捷启动!");
        super.run();
    }
}
class LaoSiLaiSi extends RunCar {
    @Override
    public void run() {
        System.out.println("劳斯莱斯启动!");
        super.run();
    }
}
class BaoMa implements Car {
    @Override
    public void run() {
        System.out.println("宝马启动!");
        System.out.println("宝马报废了,不是可运行的车!");
    }
}
class Aodi extends RunCar {
    private String type;
    @Override
    public void run() {
        System.out.println("奥迪" + this.type +"启动!");
        super.run();
    }
    public Aodi(String type) {
        this.type = type;
    }
    public Aodi() {
        // 默认是奥迪a7
        this.type = "A7";
    }
}

车工厂

工厂的创建逻辑

  1. 首先检测是否配置了汽车,如果没有,不允许
  2. 根据车类型,生成一个车。 这里为了测试反射的有参构造器,在奥迪车中加入和具体类型,如果配置了具体类型,就调用有参构造器。注意反射调用构造器的时候,可能全选不允许,需要射成True
  3. 看是否配置了代理,如果配置了,启用代理
class CarFactory {
    /**
     * 工厂需要有现成的产品
     * 这个产品需要你去配置,一个工厂可以生产多种类型的产品,
     * 但是每次运行只有一种产品可以进行使用,起到产品的选择作用
     * */
    private Car car;
    private static final String CAR_CLASS = "car.class";
    private static final String CAR_TIME_FLAG = "car.time.flag";
    private static  final String AODI_CLASS = "reflectTest.Aodi";
    private static final String AODI_TYPE= "aodi.type";
    public CarFactory() throws NullPropertiesException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        // 直接从配置文件中读取需要什么类型的车,不用传递参数了
        String carClass = AppPropertiesUtils.getProperty(CAR_CLASS);
        String flag = AppPropertiesUtils.getProperty(CAR_TIME_FLAG);
        String aodiType = AppPropertiesUtils.getProperty(AODI_TYPE);
        if (StringUtils.isAnyBlank(carClass)) {
            throw new NullPropertiesException("汽车参数未全部配置,无法创建汽车!");
        }
        // 1. 先生成一个车;生成之前需要把构造器设置成可以访问!!
        if (carClass.equals(AODI_CLASS)) {
            // 奥迪车,需要特殊生成
            this.car = generateAodi(aodiType);
        } else {
            // 其他车,直接生成
            this.car = (Car) Class.forName(carClass).newInstance();
        }
        // 2. 如果需要代理的话,进行代理;工厂直接创建一个代理类,统计车辆运行的时间
        if (!StringUtils.isBlank(flag) && flag.equals("true")) {
            this.car = (Car) ProxyTime.getProxy(this.car, Car.class);
        }
    }
    private Car generateAodi(String aodiType) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 如果配置了类型需要配置到对象中
        if (!StringUtils.isBlank(aodiType)) {
            return (Car) Class.forName(AODI_CLASS).getConstructor(String.class).newInstance(aodiType);
        } else return (Car)Class.forName(AODI_CLASS).newInstance();
    }


    // 可以通过直接传递Car的方式来构造工厂
    public CarFactory(Car car) {
        this.car = car;
    }
    public Car getCar() {
        return this.car;
    }
}

工具类

public class AppPropertiesUtils {
    private static Properties properties = new Properties();
    // 配置文件的路径
    private static final String APP_PROPERTIES_PATH = "src/main/resources/application.properties";
    static {
        // 读取参数,如果没有配置,抛出异常
        try (InputStream input = new FileInputStream(APP_PROPERTIES_PATH)) {
            // 加载 properties 文件
            properties.load(input);
        } catch (IOException e) {
            System.out.println("系统异常!请联系管理员");
            e.printStackTrace();
        }
    }
    public static String getProperty(String key) {
        return properties.getProperty(key);
    }
}

总结

一些知识:

  • 反射就是获取运行时的信息,原理是通过Class保存了运行时类的全部信息,然后对象有一个类型指针,指向这个类。
  • 反射可以访问私有成有成员,为了控制这个权限,可以通过SecurityManager进行限制,重写其中的checkPemission()方法,抛出异常来进行限制
  • 一个类的信息有:类名、字段和字段类型、构造方法和其他方法
  • 反射的常用的方法有:获取类,生成实例;获取字段,获取字段类型;获取所有构造方法,获取指定构造方法;获取所有方法,获取指定方法;方法调用

不足:

  1. 设计的不足,上述已经分析了
  2. 偷懒,没有创建文件,偷懒没有把构造器的访问权限打开,可能会导致创建不了类
03-24 06:00