仿springboot @Autowired自动注入

使用springboot也有一段时间了,最近在学习java反射的时候突发奇想,觉得可以用反射来做一个简单的自动注入。

我们都知道,Spring在启动的时候会创建一个IOC容器,并将创建好的bean放到容器里进行托管,在需要使用的时候就从容器里面获取,不需要重新创建。而在springboot项目中,service层只是负责业务逻辑,真正的执行是在Impl层。而且接口是不能被实例化的。因此不难猜测,在使用@Autowired注入service时,spring创建的bean不可能是userService,而是它的实现类。下面,我们就将利用java的反射机制,简单实现一下如何使用注解来进行自动注入。话不多说,下面直接开干。

既然是要使用注解进行注入,参照springboot,我们需要用到两个注解,分别是@Autowired以及@Service,对这两个注解的定义如下:

  

其中,@Autowired主要作用在需要注入的字段上,所以@Target选择ElementType.FIELD,而@Service主要是模仿spring用来标记实现类,作用于实现类,所以@Target选择ElementType.TYPE。

下面我们来模拟一个简单的业务逻辑:

现在有以下三个角色:School、Teacher、Student。School里面有Teacher和Student,Teacher的任务是教学,Student的任务是学习。而我们的目的是在School里面去注入Teacher和Student,然后分别去调用它们的方法。

根据需求,Teacher和Student的接口定义如下:

public interface IStudent {
    void learn();
}
public interface ITeacher {
    void teach();
}

实现类如下:

@Service
public class TeacherImpl implements ITeacher {
    public void teach(){
        System.out.println("teacher teach");
    }
}
@Service
public class StudentImpl implements IStudent {
    public void learn(){
        System.out.println("student learn");
    }
}

对School的定义如下:

public class School {

    @Autowired
    private IStudent student;
    @Autowired
    private ITeacher teacher;

    public void work(){
        teacher.teach();
        student.learn();
    }

    public static void main(String[] args) {
        School school = new School();
        school.work();
    }

}

显然,上述的代码是不能正常运行的,此时的@Autowired并没有起到任何作用,直接运行将会抛出NPE异常,因为我们并没有注入IStudent以及ITeacher的实现类。下面,我们就试着来用反射对它们进行注入。

这里涉及到一个问题,对于接口,我们是没有办法直接获取到它们的实现类的。但是JDK提供了一个方法,可以用来判断类是否实现了某个接口,那就是Class类里的isAssignableFrom()方法。借助这个方法,提供注入的思路可归纳如下:

  1. 获取当前类所声明的字段;
  2. 通过getAnnotation判断字段是否使用了@Autowired注解;
  3. 若该字段使用了@Autowired注解,获得该字段的类型(即接口类型);
  4. 获取Impl层实现类(重点),类似spring的包扫描策略。
  5. 遍历这些实现类,若该类使用了@Service注解,且实现了使用@Autowired注入的接口,则使用field.set()方法注入该接口的实现类。

其中,步骤4是最难也是最重要的一步。如何获得指定包下的类呢?所幸的是,JDK同样为我们提供了工具,那就是:ClassLoader。借助它,我们可以轻松获得指定包下的类,确切来说是获取类的全路径名称,然后再通过Class.forName方法,一个个地添加到List集合中。下面贴出实现代码:

   private static List<Class> getClassesForPackage(String packageName) {
        List<Class> classes = new ArrayList<>();
        String className = "";
        try {
            ClassLoader cld = ClassLoader.getSystemClassLoader();
            if (cld == null) {
                throw new ClassNotFoundException("Can't get class loader.");
            }
            String packagePathStr = packageName.replace('.', '/');
            Enumeration<URL> resources = cld.getResources(packagePathStr);

            while (resources.hasMoreElements()) {
                URL res = resources.nextElement();
                String type = res.getProtocol();
                if (type.equalsIgnoreCase("file")) {
                    File impl = new File(res.getPath());
                    File[] files = impl.listFiles();
                    if (files != null) {
                        for (File file : files) {
                            String name = file.getName();
                            name = name.substring(0, name.lastIndexOf("."));
                            className = packageName + "." + name;
                            try {
                                classes.add(Class.forName(className));
                            } catch (NoClassDefFoundError error) {
                                error.printStackTrace();
                            }
                        }
                    }
                }
            }
        } catch (Throwable te) {
            te.printStackTrace();
        }
        return classes;
    }

 

进行到这,最难的步骤4已经解决了,我们的工作也已经完成了大半,下面就是根据接口创建实现类并进行注入了。同样先贴出实现代码:

 public static void inject(Object source) {
        String packageName = "com.nx.reflect.service.impl";
        List<Class> clas = getClassesForPackage(packageName);
        Field[] fields = source.getClass().getDeclaredFields();
        for (Field field : fields) {
            Autowired autowired = field.getAnnotation(Autowired.class);
            if (autowired != null) {
                try {
                    Class clazz = field.getType();
                    for (Class cla : clas) {
                        Service queries = (Service) cla.getAnnotation(Service.class);
                        // 添加了Service注解 且 实现了该接口
                        if (queries != null && clazz.isAssignableFrom(cla)) {
                            Object target = cla.newInstance();
                            field.setAccessible(true);
                            field.set(source, target);
                        }
                    }
                } catch (InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

至于代码里面的步骤,和前面描述的一致,这里就不再解释。可以新建一个AutoWired类,将上述两个方法放入其中。结束后文件目录如下:

至于使用,就十分的简单了,只需要在School的构造方法中调用AutoWired.inject(this);即可完成注入。

  public School() {
        AutoWired.inject(this);
    }

此时再运行School中的Main方法,发现实现类已经成功注入了。

创作不易,喜欢的话请点个赞吧~~~///(^v^)\\\~~~

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值