一、概述
主要是通过仿照Beanfactory的思想,利用反射来实现一个非常简单的ioc容器。大体思想为:
- 自定义注解
- 实现ioc容器的管理功能,通过识别注解标识的类,利用反射创建对象并加入beanfactory中
- 实现依赖注入,为容器中对象,若其属性有注解标示,则为其赋值
以上就是大体的思路了,我们在实现的时候只关注核心功能,一些细节之处便不再关注,如异常处理,这里便直接throws Exception了
二、创建注解
我们首先创建两个注解,@Bean
和@di
分别用于表示bean的管理和依赖的注入
@Bean
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
@di
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}
这里面有几个要注意的点:
@Target表示注解作用的范围,ElementType.TYPE和ElementType.FIELD分别表示作用于类本身和类的字段上。
@ 是 Java 注解的一个元注解,用于指定注解的保留策略。RetentionPolicy
枚举类型定义了注解的保留策略,有三种可能的值:
SOURCE
:注解仅保留在源代码级别,编译时会被丢弃,不会出现在字节码中。CLASS
:注解会保留在编译后的字节码中,但在运行时不可见(即无法通过反射获取)。RUNTIME
:注解会保留在编译后的字节码中,并且在运行时可以通过反射获取
三、创建父接口ApplicationContext
核心功能就一个,根据传入的类的class对象获得bean
public interface ApplicationContext {Object getBean(Class clazz);
}
四、创建实现类,实现主要的功能
- 首先是有两个属性,beanFactory是最核心的一个字段,存储了类class对象和类对象的一个映射。BasePath存储的是类的绝对路径的前半部分,主要是便于后续操作。
- 通过有参构造,传入包的全路径,表示扫描该包及其子包中的类。
public class AnnotationApplicationContext implements ApplicationContext {public Map<Class,Object> beanFactory=new HashMap<Class,Object>();private String BasePath;public AnnotationApplicationContext(String basePackage) throws Exception {}public Object getBean(Class clazz) {return beanFactory.get(clazz);}
}
- 现在来具体实现以下步骤
- 将传入包的类路径转换成文件资源的绝对路径
- 扫描该路径下的资源,找到.class文件
- 判断是否携带有注解,有则创建对象,加入beanFactory中
public AnnotationApplicationContext(String basePackage) throws Exception {//替换包包路径中中的.为\,注意要进行转义String basePackagePath = basePackage.replaceAll("\\.", "\\\\");//获得绝对路径Enumeration<URL> resources= Thread.currentThread().getContextClassLoader().getResources(basePackagePath);while (resources.hasMoreElements()) {URL url = resources.nextElement();//这里有一个细节,由于我们之前将.替换为了\,此时的\为编码值,还未进行解码String absolutePath = URLDecoder.decode(url.getFile(), "UTF-8");BasePath=absolutePath.substring(0,absolutePath.length()-basePackagePath.length());//传入该路径的File对象,后续核心代码在loadBean中进行,避免构造函数冗余loadBean(new File(absolutePath));}loadDi();
}private void loadBean(File baseFile) throws Exception {//判断是否为文件夹if(baseFile.isDirectory()){//获取子资源File[] childFiles = baseFile.listFiles();for (File childFile : childFiles) {//如果与子资源也是一个文件夹,就继续递归调用if(childFile.isDirectory()){loadBean(childFile);}else if(childFile==null||childFile.length()==0){return;}else{//此时为不空的文件资源String absolutePath = childFile.getAbsolutePath();//判断是否为.class文件if(!absolutePath.endsWith(".class")){return;}//拿到类的全名称String classPath = absolutePath.substring(BasePath.length() - 1).replaceAll(".class","").replaceAll("\\\\",".");Class clazz = Class.forName(classPath);//获得非接口带注解的类对象if(clazz.isInterface()){return;}Annotation annotation = clazz.getAnnotation(Bean.class);if(annotation==null){return;}Object o = clazz.getDeclaredConstructor().newInstance();//判断其是否实现有接口,有的话默认以第一个接口作为map的keyClass[] interfaces = clazz.getInterfaces();if(interfaces!=null){beanFactory.put(interfaces[0],o);}else{beanFactory.put(clazz,o);}}}}
}
-
实现依赖注入功能
这个就比较简单了,大体思路是先遍历beanFactory中的对象,再判断每个类对象的属性中是否有
@Di
注解,有则为其赋值
private void loadDi() throws Exception{//遍历map集合for (Map.Entry<Class, Object> beanEntry : beanFactory.entrySet()) {//获取类对象,然后通过对象获得它的class对象,再通过反射获得其所有的字段Class beanClazz= beanEntry.getValue().getClass();for (Field field : beanClazz.getDeclaredFields()) {//判断是否有注解Di di = field.getAnnotation(Di.class);if (di != null) {//设置访问权限field.setAccessible(true);//field.getType()获得该字段的class对象,再通过beanFactory进行赋值field.set(beanEntry.getValue(),beanFactory.get(field.getType()));}}}
}
五、小结
以上便是对spring中ioc容器的一个简易的实现,总的来说还是比较简单,就是利用反射技术。大家可以先理清思路,再写一遍会更加清晰。