【Java笔记】Reflection的一个实践(模拟框架的服务管理与服务注入)

前言&Reference

最近在学一些理论知识(背八股),在看AOP相关的资料的时候看到了Reflection:
(开始吟唱)Spring AOP的实现基于动态代理,动态代理的实现有两种方式:

  • JDK动态代理:代理实现接口的类,通过实现接口实现对目标方法的增强,通过反射(比如先通过Class.getMethod()等方法获得Method,再Method.invoke())调用方法;
  • CGLIB动态代理:代理未实现接口的类,通过【子类继承父类】的方式继承目标类,重写目标方法实现增强,直接调用重写的增强方法

那么这个反射具体是怎么实现“代理”的过程,完成服务的管理与注入?
可以参考AlbertShen的这个视频
本文也是跟着这个视频学习的一个记录
代码都放github了

0. 需求

通过一个订单Order对象获取用户User信息和地址Address信息

1. 常规方法

Address address = new Address("99 Shangda Road", "114514");
Customer customer = new Customer("Hansdas", "Hansdas@xx.com");
Order order = new Order(customer, address);
order.getCustomer().printName();
order.getAddress().printStreet();

【缺陷】在面对动态场景时存在限制,一旦代码完成,就无法动态创建对象或调用方法

2. 反射方法实现

2.1 定义Config类与Container类

  • 先定义一个Config类:用于定义Customer服务与Address服务,通过对应方法进行实例化并返回需要的对象

    public class Config {
        public Customer customer(){
            // 定义Customer服务与Address服务,通过对应方法进行实例化并返回需要的对象
            return new Customer("Hansdas", "hansdas@xx.com");
        }
    
        public Address address(){
            return new Address("99 Shandda Road", "114514");
        }
    
        public Message message(){
            return new Message("Hi there!");
        }
    }
    
  • 再定义一个Container类:注册和获得实例

    public class Container {
        // 创建一个Map类型来存放Config里的所有方法
        // K: 方法返回的Class,V:对应的方法
        // 注意:这里存储的时=是方法本身,而不是方法执行后获得的实例(具体实例在执行这些方法后获取,这样可以节省资源并提高性能)
        private Map<Class<?>, Method> methods;
        private Object config; // 用于存放config实例
    
        // 初始化注册加载所有的方法和实例化Config对象
        public void init() throws ClassNotFoundException {
            Class<?> clazz = Class.forName("Config");
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method: methods){
                System.out.println(method.getName());
            }
        }
    }
    

2.2 使用注解过滤得到目标方法

比如上述Config中,我们只需要customer()address(),不需要message(),可以在customer()address()加自定义注解,通过Method.getDeclaredAnnotation()过滤。

  • 自定义一个Bean

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Bean {
        
    }
    
  • 在Config中为需要的方法加上注解

    public class Config {
        @Bean
        public Customer customer(){
            // 定义Customer服务与Address服务,通过对应方法进行实例化并返回需要的对象
            return new Customer("Hansdas", "hansdas@xx.com");
        }
        @Bean
        public Address address(){
            return new Address("99 Shandda Road", "114514");
        }
    
        public Message message(){
            return new Message("Hi there!");
        }
    }
    
  • 修改Container init() 中注册服务的过程

    public void init() throws ClassNotFoundException {
        Class<?> clazz = Class.forName("Config");
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method: methods){
            if (method.getDeclaredAnnotation(Bean.class) != null){
                // 如果有Bean注解的话就来处理这个方法
                System.out.println(method.getName());
            }
        }
    }
    

2.3 使用Map在Container中进行管理

2.3.1 通过Map管理目标实例方法

前面Container中有个Map类型的methods属性

// 创建一个Map类型来存放Config里的所有方法
// K: 方法返回的Class,V:对应的方法
// 注意:这里存储的时=是方法本身,而不是方法执行后获得的实例(具体实例在执行这些方法后获取,这样可以节省资源并提高性能)
private Map<Class<?>, Method> methods;
  • 在Container的init()中,将需要的Method加入methods的map中,并实例化config用于之后调用方法服务与对象

    public void init() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
            this.methods = new HashMap<>();
            Class<?> clazz = Class.forName("Config");
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method: methods){
                if (method.getDeclaredAnnotation(Bean.class) != null){
                    // 如果有Bean注解的话就来处理这个方法
                    this.methods.put(method.getReturnType(), method);
                }
            }
            this.config = clazz.getConstructor().newInstance();
     }
    
  • 在Container中写一个getServiceInstanceByClass方法,通过类的class对象获取相应的服务实例

    public Object getServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
    	   // 使用Class对象作为Key,在事先初始化的map中查找对应的方法
    	   // 找到了相应的方法就执行该方法,返回对应的实例化服务对象
    	  if (this.methods.containsKey(clazz)){
    	      Method method = this.methods.get(clazz);
    	      Object obj = method.invoke(this.config);
    	      return obj;
    	  }
    	  return null;
    }
    
  • main中测试一下能否根据class拿到相应对象

    public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        Container container = new Container();
        container.init();
        Class<?> clazz = Class.forName("Customer");
        Object obj = container.getServiceInstanceByClass(clazz);
        System.out.println(obj);
    
    }
    

    输出如下,成功拿到

    Customer@21b8d17c
    

这里成功通过该方法获得了一个对象实例,但此时每次调用这个方法获取一个实例时,都会创建一个新对象,有的服务是全局的,希望只创建一次,之后就复用这个实例,因此可以增加一个Map来维护这个对实例的唯一性

2.3.2 通过Map实现目标的唯一性

创建一个Map来存储已经创建的实例,在init()中初始化,并修改 getServiceInstanceByClass 返回实例的流程,修改后的Container如下:

public class Container {
    // 创建一个Map类型来存放Config里的所有方法
    // K: 方法返回的Class,V:对应的方法
    // 注意:这里存储的时=是方法本身,而不是方法执行后获得的实例(具体实例在执行这些方法后获取,这样可以节省资源并提高性能)
    private Map<Class<?>, Method> methods;
    private Object config; // 用于存放config实例
    private Map<Class<?>, Object> services; // 存放已经创建的对象实例

    // 初始化注册加载所有的方法和实例化Config对象
    public void init() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        this.methods = new HashMap<>();
        this.services = new HashMap<>();
        Class<?> clazz = Class.forName("Config");
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method: methods){
            if (method.getDeclaredAnnotation(Bean.class) != null){
                // 如果有Bean注解的话就来处理这个方法
                this.methods.put(method.getReturnType(), method);
            }
        }
        this.config = clazz.getConstructor().newInstance();
    }

    public Object getServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
        if (this.services.containsKey(clazz)){
            // 如果已经创建过这个class的实例,直接返回
            return this.services.get(clazz);
        }else{
            // 如果没有创建过这个class的实例:
            // 使用Class对象作为Key,在事先初始化的map中查找对应的方法,找到了相应的方法就执行该方法,返回对应的实例化服务对象
            if (this.methods.containsKey(clazz)){
                Method method = this.methods.get(clazz);
                Object obj = method.invoke(this.config);
                this.services.put(clazz, obj);
                return obj;
            }
            return null;
        }

    }
}

2.4 实现服务自动注入

在Container中定义一个createInstance方法,用于通过Class对象创建普通实例,并且将服务自动注入到对象中

2.4.1 获取目标对象的构造器

  • 增加createInstance方法,通过Class.getDeclaredConstructors() 获取目标的构造器

    public Object createInstance(Class<?> clazz) throws NoSuchMethodException {
        Constructor<?>[] constructors =  clazz.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors){
            System.out.println(constructor);
        }
        return null;
    }
    
  • 在main中测试一下

    public class Main {
        public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException {
            Container container = new Container();
            container.init();
            Class<?> clazz = Class.forName("Order");
            container.createInstance(clazz);
        }
    }
    

    输出如下,成功拿到Order的两个构造器

    public Order(Customer, Address)
    public Order()
    

2.4.2 通过注解获得指定构造器

上面已经成功拿到了目标对象Order的连个给构造器,与前面2.2相同,具体获取哪一个可以通过注解选择

  • 定义一个Autowired注解

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.CONSTRUCTOR)
    public @interface Autowired {
    }
    
  • 在Order中需要的构造函数上加注解

    public class Order {
        private Customer customer;
        private Address address;
    
        @Autowired
        public Order(Customer customer, Address address) {
            this.customer = customer;
            this.address = address;
        }
    	...
    }
    
  • 修改createInstance,根据注解进行过滤

    public Object createInstance(Class<?> clazz) throws NoSuchMethodException {
        Constructor<?>[] constructors =  clazz.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors){
            if (constructor.getDeclaredAnnotation(Autowired.class) != null){
                System.out.println(constructor);
            }
        }
        return null;
    }
    
  • 在main里测试一下,只有加了Autowired注解的有参构造了

    public Order(Customer, Address)
    

2.4.3 通过构造器实例化对象

  • 修改createInstance

  • public Object createInstance(Class<?> clazz) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        Constructor<?>[] constructors =  clazz.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors){
            if (constructor.getDeclaredAnnotation(Autowired.class) != null){
                // 获取所有的参数类型
                Class<?>[] parameterTypes = constructor.getParameterTypes();
                // 存储参数的对象实例
                Object[] arguements = new Object[parameterTypes.length];
                for (int i = 0; i < parameterTypes.length; i++) {
                    arguements[i] = getServiceInstanceByClass(parameterTypes[i]);
                }
                return constructor.newInstance(arguements);
            }
        }
        // 无Autowired注解直接通过无参构造返回实例
        return clazz.getDeclaredConstructor().newInstance();
    }
    
  • 注入测试

    public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        Container container = new Container();
        container.init();
        Class<?> clazz = Class.forName("Order");
        Object obj = container.createInstance(clazz);
        Field field = clazz.getDeclaredField("customer");
        field.setAccessible(true);
        Object fieldValue = field.get(obj);
        System.out.println(fieldValue);
    }
    

    输出如下,获取到Customer服务对象,注入成功

    Customer@41cf53f9
    

2.4.4 调用服务对象的方法

  • 在main中通过Class.getDeclaredMethods获得服务对象的所有方法,print一下

    Method[] methods = fieldValue.getClass().getDeclaredMethods();
    for (Method method: methods){
        System.out.println(method.getName());
    }
    

    输出如下,成功拿到Customer的方法

    getName
    printEmail
    printName
    getEmail
    

    这里获得了Customer的所有方法,如果想要获取带print的方法,也只需要加上注解即可

  • 定义Printable注解

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Printable {
    }
    
  • 在print相关方法上增加注解

    public class Customer {
    		...
    
        @Printable
        public void printName(){
            System.out.println("Customer email: "+ name);
        }
        @Printable
        public void printEmail(){
            System.out.println("Customer email: "+ email);
        }
    }
    
  • 测试增加约束,过滤掉不含Printable注解的方法

    Method[] methods = fieldValue.getClass().getDeclaredMethods();
    for (Method method: methods){
        if (method.getDeclaredAnnotation(Printable.class) != null){
            System.out.println(method.getName());
        }
    }
    

    输出如下,成功

    printName
    printEmail
    
  • Main中使用Method.invoke执行方法

    Method[] methods = fieldValue.getClass().getDeclaredMethods();
    for (Method method: methods){
        if (method.getDeclaredAnnotation(Printable.class) != null){
            method.invoke(fieldValue);
        }
    }
    

    输出

    Customer email: Hansdas
    Customer email: hansdas@xx.com
    
  • 也可以把类名与变量名放到变量里,方便配置,最终的Main如下

    public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException {
            Container container = new Container();
            container.init();
            String className = "Order";
            String fieldName = "customer";
            Class<?> clazz = Class.forName(className);
            Object obj = container.createInstance(clazz);
            Field field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
            Object fieldValue = field.get(obj);
            Method[] methods = fieldValue.getClass().getDeclaredMethods();
            for (Method method: methods){
                if (method.getDeclaredAnnotation(Printable.class) != null){
                    method.invoke(fieldValue);
                }
            }
     }
    

2.5 其他测试

如果要换一下class跟field,记得加上相应注解:

  1. User中将相应构造函数加@Autowired
  2. Message中print方法加@Printable
  3. Config 中将message()加@Bean
public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException {
    Container container = new Container();
    container.init();
    String className = "User";
    String fieldName = "message";
    Class<?> clazz = Class.forName(className);
    Object obj = container.createInstance(clazz);
    Field field = clazz.getDeclaredField(fieldName);
    field.setAccessible(true);
    Object fieldValue = field.get(obj);
    Method[] methods = fieldValue.getClass().getDeclaredMethods();
    for (Method method: methods){
        if (method.getDeclaredAnnotation(Printable.class) != null){
            method.invoke(fieldValue);
        }
    }
}

反射的优缺点

反射的优点

可以看出,此时对象的实例化和方法的调用不再依赖于硬编码的类名和方法名,而是基于字符串和注解Annotation提供的配置信息

使得代码非常的灵活且通用,适合编写需要高度解耦的框架与程序

反射的缺点

会带来性能开销、安全性问题和维护的复杂性,需要在灵活性与这些问题中平衡

  • 48
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我可以帮你设计一个简单的笔记管理系统。首先,你需要定义数据模型来存储笔记本信息,例如品牌、型号、颜色和价格等。然后,你可以使用Java编写控制台应用或者图形用户界面 (GUI) 应用来实现该系统。以下是一个简单的控制台应用的代码示例: ```java import java.util.ArrayList; import java.util.Scanner; public class NotebookManager { private ArrayList<Notebook> notebookList; public NotebookManager() { this.notebookList = new ArrayList<Notebook>(); } public void addNotebook(Notebook notebook) { this.notebookList.add(notebook); } public void removeNotebook(Notebook notebook) { this.notebookList.remove(notebook); } public void displayNotebookList() { System.out.println("Notebook List:"); for (Notebook notebook : this.notebookList) { System.out.println(notebook); } } public static void main(String[] args) { NotebookManager notebookManager = new NotebookManager(); Scanner scanner = new Scanner(System.in); int choice = 0; do { System.out.println("Notebook Management System"); System.out.println("==========================="); System.out.println("1. Add a new notebook"); System.out.println("2. Remove an existing notebook"); System.out.println("3. Display notebook list"); System.out.println("4. Exit"); System.out.println("Enter your choice:"); choice = scanner.nextInt(); switch (choice) { case 1: System.out.println("Add a new notebook"); System.out.println("==================="); System.out.println("Enter brand name:"); String brand = scanner.next(); System.out.println("Enter model:"); String model = scanner.next(); System.out.println("Enter color:"); String color = scanner.next(); System.out.println("Enter price:"); double price = scanner.nextDouble(); Notebook newNotebook = new Notebook(brand, model, color, price); notebookManager.addNotebook(newNotebook); break; case 2: System.out.println("Remove an existing notebook"); System.out.println("============================="); notebookManager.displayNotebookList(); System.out.println("Enter index to remove:"); int index = scanner.nextInt(); Notebook notebook = notebookManager.notebookList.get(index); notebookManager.removeNotebook(notebook); break; case 3: System.out.println("Display notebook list"); System.out.println("======================="); notebookManager.displayNotebookList(); break; case 4: System.out.println("Goodbye!"); break; default: System.out.println("Invalid choice!"); } } while (choice != 4); } } class Notebook { private String brand; private String model; private String color; private double price; public Notebook(String brand, String model, String color, double price) { this.brand = brand; this.model = model; this.color = color; this.price = price; } public String getBrand() { return brand; } public String getModel() { return model; } public String getColor() { return color; } public double getPrice() { return price; } @Override public String toString() { return String.format("Brand: %s, Model: %s, Color: %s, Price: %.2f", brand, model, color, price); } } ``` 这个笔记管理系统可以执行以下操作: 1. 添加一个新的笔记本 2. 删除一个已有的笔记本 3. 显示当前的笔记本列表 4. 退出系统 你可以选择用一个简单的菜单界面命令来执行这些操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值