JAVA反射实践 - 模拟一个框架

注意:
阅读本文前,请先具备Java反射相关知识。

引入

有一个Student类,我们希望实现其成员变量CollegeMajor的自动注入(依赖注入)。本文将展示如何通过反射实现这一功能,最终模拟一个简易的依赖注入框架。

1. 基本类

College类

/**
 * 学院信息
 */
public class College {
    private String name;

    public College() {
    }

    public College(String name) {
        this.name = name;
    }
}

Major类

/**
 * 专业信息
 */
public class Major {
    private String name;

    public Major(){
    }

    public Major(String name) {
        this.name = name;
    }
}

Student类

/**
 * 学生信息
 */
public class Student {
    private College college;
    private Major major;

    public Student() {
    }

    public Student(College college, Major major) {
        this.college = college;
        this.major = major;
    }

    public void print(){
        System.out.println(college);
        System.out.println(major);
    }
}

这里仅仅提供了简单的三个类,主要是展示如何实现依赖注入。

2. 配置类

Config类

public class Config {
    public College college(){
        return new College("信息工程学院");
    }

    public Major major(){
        return new Major("软件工程");
    }

    /**
    * 普通方法,非返回对应实例的方法
    */
    public String msg(){
        return "hello MyFramework!";
    }
}

本文将CollegeMajor的实例定义为服务Service
在配置类中定义方法返回对应服务。

3. 容器类

使用容器类注册服务获取服务

Container类

public class Container {
    /**
    * 保存服务的Class和获取对应服务的方法
    */
    private Map<Class<?>, Method> methods;
    private Object config;
}

3.1 向Container类中添加init方法,用于初始化

public void init() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    methods = new HashMap<>();
    Class<?> clazz = Class.forName("com.gor.framework.Config");
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
        System.out.println(declaredMethod);
    }
    config = clazz.getConstructor().newInstance();
}

此时运行init方法,做一个测试,将会输出配置类的所有方法

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Container container = new Container();
        container.init();
    }
}

在这里插入图片描述

可以看到msg非服务方法,但程序还是输出了此方法的信息,不是Student实例所需要的服务。那么应该如何只获取Student实例所需要的服务呢?(即只获取college、major方法)

解决方案

使用自定义注解,只有该方法含有此注解,此方法才会被放入Map<Class<?>, Method> methods集合。

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyBean {
}

@Retention(RetentionPolicy.RUNTIME)的含义:
注解保留在源代码和编译后的class文件中,但是在运行时JVM 不保留。所以此时通过反射是获取不到注解信息的。需要显式指定注解的保留策略,即在注解头部加入这个注解。

  • RetentionPolicy.SOURCE:注解只保留在源代码级别,编译后的class文件中不包含。
  • RetentionPolicy.CLASS:注解保留在源代码和编译后的class文件中,但是在运行时 VM 不保留。(默认)
  • RetentionPolicy.RUNTIME:注解保留在源代码、编译后的class文件中,且在运行时 VM 也会保留,因此可以通过反射机制读取注解信息。

修改Config

package com.gor.concurrent.reflection.framework;

public class Config {
    @MyBean
    public College college(){
        return new College("信息工程学院");
    }

    @MyBean
    public Major major(){
        return new Major("软件工程");
    }

    public String msg(){
        return "hello MyFramework!";
    }
}

完善init方法

public void init() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    methods = new HashMap<>();
    Class<?> clazz = Class.forName("com.gor.framework.Config");
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
        if(declaredMethod.getDeclaredAnnotation(MyBean.class) != null){
            methods.put(declaredMethod.getReturnType(), declaredMethod);
        }
    }
    config = clazz.getConstructor().newInstance();
}

3.2 注册服务

成员变量保存获取服务的方法后,就可以注册服务了。

public Object getServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
    if(methods.containsKey(clazz)){
        Method method = methods.get(clazz);
        Object obj = method.invoke(config);
        services.put(clazz, obj);
        return obj;
    }
    return null;
}

困惑点

这里的返回参数为什么是Object?

答:因为是动态生成的实例,编译器无法确切知道实例类型

服务可以复用,也就是不必每次都创建新的实例。在这里我创建一个Map集合(Map<Class<?>, Object> services)用于保存已创建的服务。

完善getServiceInstanceByClass方法

public Object getServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
    if(services.containsKey(clazz)){
        return services.get(clazz);
    }
    if(methods.containsKey(clazz)){
        Method method = methods.get(clazz);
        Object obj = method.invoke(config);
        services.put(clazz, obj);
        return obj;
    }
    return null;
}

注意:此方法存在线程安全问题,因本文重点是反射,读者可以自己试试改造这个方法为线程安全的方法。

3.3 获取实例

服务注册完毕,也就是Student实例所需要的依赖(服务)已创建,可以创建Student实例。

public Object createInstance(Class<?> clazz) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
    Constructor<?>[] constructors = clazz.getConstructors();
    for (Constructor<?> constructor : constructors) {
        /**
        * 类的构造器有很多,应该使用哪一个来创建对象?
        */
    }
}
public Student() {
}

public Student(College college, Major major) {
    this.college = college;
    this.major = major;
}

在本例中,Student有两个构造器,怎么指定构造器?

解决方案

这里使用了和上文一样的解决方案,自定义注解。在想要执行的构造器上添加注解,这样即可通过注解获取到这个构造器。

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
}

Student类的有参构造器中加入@MyAutowired注解
在这里插入图片描述

完善createInstance方法

public Object createInstance(Class<?> clazz) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
    Constructor<?>[] constructors = clazz.getConstructors();
    for (Constructor<?> constructor : constructors) {
        if(constructor.getDeclaredAnnotation(MyAutowired.class) != null){
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            /**
             * 存放参数类型对应的实例
             */
            Object[] arguments = new Object[parameterTypes.length];
            for (int i = 0; i < parameterTypes.length; i++) {
                arguments[i] = getServiceInstanceByClass(parameterTypes[i]);
            }
            return constructor.newInstance(arguments);
        }
    }
    /**
     * 如果没有使用MyAutowired注解,默认返回通过无参构造器创建的对象实例
     */
    return clazz.getConstructor().newInstance();
}

容器类完整代码

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Container {
    /**
     * 保存服务的Class和获取对应服务的方法
     */
    private Map<Class<?>, Method> methods;
    private Map<Class<?>, Object> services;
    private Object config;

    public void init() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        methods = new HashMap<>();
        services = new HashMap<>();
        Class<?> clazz = Class.forName("com.gor.framework.Config");
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            if(declaredMethod.getDeclaredAnnotation(MyBean.class) != null){
                methods.put(declaredMethod.getReturnType(), declaredMethod);
            }
        }
        config = clazz.getConstructor().newInstance();
    }

    public Object getServiceInstanceByClass(Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
        if(services.containsKey(clazz)){
            return services.get(clazz);
        }
        if(methods.containsKey(clazz)){
            Method method = methods.get(clazz);
            Object obj = method.invoke(config);
            services.put(clazz, obj);
            return obj;
        }
        return null;
    }

    public Object createInstance(Class<?> clazz) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> constructor : constructors) {
            if(constructor.getDeclaredAnnotation(MyAutowired.class) != null){
                Class<?>[] parameterTypes = constructor.getParameterTypes();
                /**
                 * 存放参数类型对应的实例
                 */
                Object[] arguments = new Object[parameterTypes.length];
                for (int i = 0; i < parameterTypes.length; i++) {
                    arguments[i] = getServiceInstanceByClass(parameterTypes[i]);
                }
                return constructor.newInstance(arguments);
            }
        }
        /**
         * 如果没有使用MyAutowired注解,默认返回通过无参构造器创建的对象实例
         */
        return clazz.getConstructor().newInstance();
    }
}

4. 最终效果

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Container container = new Container();
        container.init();

        Class<?> clazz = Class.forName("com.gor.framework.Student");
        Object student = container.createInstance(clazz);
        Method method = clazz.getDeclaredMethod("print");
        method.invoke(student);
    }
}

在这里插入图片描述

控制台成功打印出CollegeMajor实例,Student实例的依赖成功注入。

至此,利用反射成功实现了一个简单的框架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值