需求
- 统计每一个方法的运行时间
- 扩展性的创建不同类型的车,通过配置文件进行配置
实现
配置文件
- 配置需要什么类型的车
- 对于aodi车还需要特殊类型的配置
- 是否启用方法运行的时间代理
# 工厂类型,选择其他汽车
car.class=reflectTest.Aodi
aodi.type=A5
# 是否启用车辆的时间代理
car.time.flag=true
时间代理
- 这里过度设计了代理接口,以及增强器接口,想根据Spring的通知进行设计。结果画虎不成反类犬
- 直接看时间代理就行了。注意需要开启
setAccessible(true);
,这个就是上面的访问权限的问题导致的 - 代理的核心就是
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
- 接口一般是…able,这里做成Car不合适
- 为了方便复用睡眠的代码,这里实现了一个RunCar,可以通过继承这个类,然后调用run()方法进行睡眠100ms
- 所有的实现都放在了一个文件中,导致访问权限只能是包类型,所以后面调用的时候,需要设置
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";
}
}
车工厂
工厂的创建逻辑
- 首先检测是否配置了汽车,如果没有,不允许
- 根据车类型,生成一个车。 这里为了测试反射的有参构造器,在奥迪车中加入和具体类型,如果配置了具体类型,就调用有参构造器。注意反射调用构造器的时候,可能全选不允许,需要射成True
- 看是否配置了代理,如果配置了,启用代理
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()
方法,抛出异常来进行限制 - 一个类的信息有:类名、字段和字段类型、构造方法和其他方法
- 反射的常用的方法有:获取类,生成实例;获取字段,获取字段类型;获取所有构造方法,获取指定构造方法;获取所有方法,获取指定方法;方法调用
不足:
- 设计的不足,上述已经分析了
- 偷懒,没有创建文件,偷懒没有把构造器的访问权限打开,可能会导致创建不了类