最近学习了Spring的自动注入机制,纸上谈来终觉浅,打算自己来实现一个简单的自动注入机制,加深理解。这篇博客一方面用于反思所学的知识,另一方面希望能给各位朋友带来一些启发。如有错误/问题,欢迎评论区指出。
每一章都会在前一版本的基础上进行功能的完善与改进,如果有想法的话,也可以在评论区提出来。
在使用lombok、假设所注入目标全为多例(prototype )的情况下,采用实现自动注入的简单情况。包含以下功能:
注解 | 作用 |
---|---|
@Autowird | 在类中自动注入 |
@Component | 把bean进行注册 |
@ComponentScan | 确认扫描bean的路径 |
实现完成后,做到可以使得如下程序正常运行。
Main.java
public class Main {
public static void main(String[] args) throws Exception {
//todo 获取注册完成后,储存的bean的信息
ApplicationContext applicationContext=
new ApplicationContext(AppConfig.class);
//todo 获取我们的注册文件
Test test=(Test) applicationContext.getBean("test");
//测试
test.output();
test.setUserService(test.getUserService().setUsername("Han Meme"));
test.output();
}
}
Test.java
@Component("test")
public class Test {
@Autowired
private UserService userService;
public Test() {
userService = null;
}
public void output(){
assert userService != null;
if (userService.getUsername()==null) {
userService.setUsername("Li Ming");
}
System.out.println(userService);
}
//此处省略Setter、Getter、toString方法
}
UserService.java
@Component("userService")
public class UserService {
private String username=null;
public UserService() {
}
@Override
public String toString() {
return "UserService{" +
"username='" + username + '\'' +
'}';
}
//此处省略Setter、Getter、toString方法
}
│ Main.java
│
├─Config
│ AppConfig.java
│
├─Bean
│ │ Test.java
│ │ UserService.java
│
└─Ioc
│ ApplicationContext.java
│ Autowired.java
│ BeanDefinition.java
│ Component.java
│ ComponentScan.java
文件名 | 作用 |
---|---|
ApplicationContext.java | 包含对所有Bean的扫描、注册、注入、反射等功能 |
AppConfig.java | 对自动注入机制的相关配置 |
BeanDefinition.java | 储存Bean的单元 |
这里采用注解的方式对配置类(AppConfig.java)进行配置。在本文中,我们所需配置的内容仅为Bean的扫描路径。
AppConfig.java
@ComponentScan("com/IocV1/Ioc")
public class AppConfig {
}
ComponentScan.java
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE})
public @interface ComponentScan {
String value() default "";
}
BeanDefinition.java
public class BeanDefinition {
private String scope;
private Class<?> beanClass;
//此处省略Setter、Getter、toString方法
}
获取的流程为:
具体到代码上即为:
private File position(Class<?> Config) {
//判断有没有对应的注解
if (Config.isAnnotationPresent(ComponentScan.class)) {
//获取扫描路径
ComponentScan componentScan = Config.getAnnotation(ComponentScan.class);
String scanPath = componentScan.value();
//转化为标准路径格式
scanPath = scanPath.replace('.', '/');
//获取File
ClassLoader classLoader = ApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(scanPath);
assert resource != null;
return new File(resource.getFile());
}else {
return null;
}
}
扫描的流程为:
具体到代码上即为:
private List<Class<?>> scan(File file) {
//建立储存的数组
List<Class<?>> classList = new ArrayList<>();
ClassLoader classLoader = ApplicationContext.class.getClassLoader();
//获取该目录下所有文件
File[] files = file.listFiles();
assert files != null;
for (File f : files) {
if(f.isDirectory()){
//如果是目录,就进行递归
classList.addAll(scan(f));
}else {
//如果不是目录,先判断这是不是class文件,若不是则跳过
if(!f.getName().endsWith("class"))
continue;
//获取该文件绝对路径
String absolutePath = f.getAbsolutePath();
absolutePath = absolutePath
.substring(
absolutePath.indexOf("com"),
absolutePath.indexOf(".class")
).replace("\\", ".");
try {
//根据路径获取该文件的Class并储存
Class<?> clazz = classLoader.loadClass(absolutePath);
if (clazz.isAnnotationPresent(Component.class)) {
classList.add(clazz);
}
} catch (Exception ignored) {
}
}
}
return classList;
}
初始化过程为:
private ConcurrentHashMap<String, BeanDefinition> beanDefinitionHashMap =
new ConcurrentHashMap<>();
//ApplicationContext构造器
public ApplicationContext(Class<?> Config){
//获取路径
File BeanPath=position(Config);
//获取扫描结果
assert BeanPath != null;
List<Class<?>> classList=scan(BeanPath);
//进行注册
for(Class<?> clazz:classList){
BeanDefinition beanDefinition=new BeanDefinition();
//确认是否为多例
if (clazz.isAnnotationPresent(Scope.class)) {
beanDefinition.setScope(clazz.getAnnotation(Scope.class).value());
}else {
beanDefinition.setScope("prototype");
}
beanDefinition.setBeanClass(clazz);
beanDefinitionHashMap.put(
clazz.getAnnotation(Component.class).value(),beanDefinition);
}
}
获取流程:
public Object getBean(String beanName) throws Exception {
//获得beanName对应的beanDefinition
BeanDefinition beanDefinition=beanDefinitionHashMap.get(beanName);
Object object=null;
//获取该beanDefinition中的Class
if ("prototype".equals(beanDefinition.getScope())) {
try {
object=beanDefinition.getBeanClass().getConstructor().newInstance();
}catch (Exception ignored){
}
}
//对该示例的内部进行自动注入
assert object != null;
attriAssign(object);
return object;
}
private void attriAssign(Object object) throws Exception {
//获取所有的内部变量
Class<?> classInfo = object.getClass();
Field[] declaredFields = classInfo.getDeclaredFields();
//对所有变量进行获取
for (Field field : declaredFields) {
Autowired extResource = field.getAnnotation(Autowired.class);
//如果这个变量需要进行自动注入
if (extResource != null) {
String beanId = field.getName();
Object bean = getBean(beanId);
if (bean != null) {
//设定该属性可外部访问
field.setAccessible(true);
//注入
field.set(object, bean);
}
}
}
}
我在这里可以写两个名称为test和userService的bean
@Component("test")
public class Test {
@Autowired
private UserService userService;
//其余内部结构见开头
}
@Component("userService")
public class UserService {
//内部结构见开头
}
然后在测试时,可以先对自动注入进行初始化,然后获取对应的bean,在获取的同时对该bean内部完成自动注入。获取成功后,可以对该bean进行各种正常操作。
public class Main {
public static void main(String[] args) throws Exception {
//获取我们的注册文件
ApplicationContext applicationContext=
new ApplicationContext(AppConfig.class);
//获取注册完成后,储存的bean的信息
Test test=(Test) applicationContext.getBean("test");
//测试
test.output();
}
}
ok,到这里,你已经从无到有的实现了一个自动注入机制,虽然它还有很多明显的缺陷,但还是个不错的开头(好歹能用)
到这里,我们已经自己完成了一个自动注入机制的简单实现,他还有很多问题,比如:
我会在该系列下一节中,继续完善这个机制。
(各位朋友如果感觉有收获的话,点个赞+关注一下呗)