跟我猜Spring-boot:依赖注入
葛云飞 人气:0
# 依赖注入
## 引&目标
本篇是《跟我猜Spring-Boot》系列的第二篇(Oh,我竟然已经写了10篇了,真不容易)。
在上一篇中,我们实现了*Bean*的创建,但是仅仅是创建而已,并没有真正的实现Bean的注入。那么在今天这篇中,我们要去实现bean的自动注入。
我们之前已经在工程中定义了`SimpleService`和`SimpleController`这两个类,那么这篇文章,我们要把`SimpleService`自动注入到`SimpleController`中;
**SimpleController.java**
```java
@Service
public class SimpleController {
@Autowired
private SimpleService simpleService;
public SimpleController(){
System.out.println("the controller is created!");
}
}
```
因为目前只是一个控制台程序,没办法进行真正的调用和展示,所以我给`SimpleService`加一个输出,用来表示这个类的的唯一性。
这样,`SimpleService`就变成了这样:
**SimpleService.java**
```java
@Service
public class SimpleService {
private String serviceId ;
public SimpleService(){
serviceId= UUID.randomUUID().toString();
System.out.println("the service :"+serviceId+"is created!");
}
public String getServiceId(){
return this.serviceId;
}
}
```
现在虽然有了调用,但是还是没有办法去验证我们的想法。
干脆,将计就计,我们再加上一个`PostContruct`的注解吧 :)
**SimpleController.java**
```java
public class SimpleController{
// other code
@PostContruct
public void init(){
System.out.println("the service id is :"+this.simpleService.getServiceId());
}
}
```
## 依赖注入的需求分析
由我们目标程序的更改,可以看出我们这次对`mini-boot`的更改主要在如下几点:
1. 定义`PostConstruct`和`Autowired`
2. 以`Autowired`为标记,实现依赖注入
3. 以`PostContruct`为标记,实现Bean在创建的自动初始化
以3条是我们这次要实现的直观目标,然而,由于我们之间**过于简单**的设计,我们有一个问题要解决,即:
我们需要有地方可以存储,查找已经已经生的bean !
那么这个问题显然要比1,2,3条更重要一些,于是
> 前文挖坑,后文填坑
我们的目标变成了:
0. **实现Bean的存储和管理**
1. 定义`PostConstruct`和`Autowired`
2. 以`Autowired`为标记,实现依赖注入
3. 以`PostContruct`为标记,实现Bean在创建的自动初始化
## Step 0 Bean的存储和管理
在Spring中,从外部得到一个bean 方式下:
1. 通过依赖注入或其他方式得到`ApplicationContext`
2. 通过`ApplicationContext.getBean`来得到相应的bean.
通过这两条,显而易见:
1. 我们也照般一个`ApplicationContext`
2. 而通过类名或类拿到bean这种逻辑,显然是一个map.这样,我们的`ApplicationContext`变得很好实现:
**ApplicationContext.java**
```java
package com.github.yfge.miniboot.context;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
public class ApplicationContext {
private Map beanMap ;
public ApplicationContext(){
beanMap=new LinkedHashMap<>();
}
public void addBean( Object ob){
this.beanMap.put(ob.getClass().getName(),ob);
}
public Object getBean(String beanName){
return this.beanMap.get(beanName);
}
public Object getBean(Class beanClass){
return this.beanMap.get(beanClass.getName());
}
}
```
嗯,有了`ApplicationContext`之后,我们就可以在生成bean的同时用一个applicationContext把所有的bean都保存起来。
这时,我们的`Application.loadBeans`的函数有了一点点变化:
**Application.loadBeans**
```java
public class Application{
// other code
private static void LoadBeans(Class source) {
ClassUtils util = new ClassUtils();
List classNames = util.loadClass(source);
/** 实例化一个context **/
ApplicationContext applicationContext = new ApplicationContext();
for (String name : classNames) {
try {
var classInfo = Class.forName(name);
/**
* 检查是否声明了@Service
**/
if (classInfo.getDeclaredAnnotation(Service.class) != null) {
/**
* 得到默认构造函数
*/
var constructor = classInfo.getConstructor();
if (constructor != null) {
/**
* 创建实例
*/
var obj = constructor.newInstance();
/** 保存bean**/
applicationContext.addBean(obj);
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
//other code
}
```
可以看到,这时的变化还很小,我们只是在开始初始化了一个context ,然后在bean生成后,将bean保存在context中
OK,保存beean的功能完成了(只是把前人的坑填了而已),下一步要开始我们正式的工作了。
## Step1 定义Annotation
之前已经分析了,我们需要定义`Autowired`和`PostContruct`两个注解,都简单的很,做一下声明即可:
**Autowired.java**
```java
package com.github.yfge.miniboot.autoconfigure;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}
```
**PostConstruct.java**
```java
package com.github.yfge.miniboot.autoconfigure;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface PostConstruct {
}
```
同样注意,我们在这里要加入`@Retention(RetentionPolicy.RUNTIME)`这一行,具体的功能和区别,咱会找机会详解(又在挖坑,不挖不舒服基)
## Step2 以@Autowired为标记,实现依赖注入
嗯,我们要进行最激动人心的部分了,实现依赖注入,即我们要将`SimpleService`自动**注入**到`SimpleController`中。
But ....
一切已经非常水到渠成了,又双叒叕有什么好激动的?我们要做的无非就是:
1. 把所有的bean从context里取出来;
2. 用反射得到bean的每一个字段;
3. 检查这个字段有没有加autowird注解;
4. 如果有,检查这个字段类型是不是一个bean;
5. 如果是,取出来用反射进行赋值;
因为我们用的是Map进行的存储,所以4,5两步可以合并为:
4-5. 按这个字段类型取到bean,如果不为空,就赋值。
因为想的清楚,代码自然一气呵成,即在`Application.loadBeans`后面增加这个注入的逻辑:
**Application.loadBeans**
```java
public class Application {
/**
* 加载相应的bean(Service)
*
* @param source
*/
private static void LoadBeans(Class source) {
//other code
//上面是之前的逻辑
/** 注入bean **/
for (Object ob : applicationContext.getAllBeans()) {
var classInfo = ob.getClass();
for (var field : classInfo.getDeclaredFields()) {
if (field.getDeclaredAnnotation(Autowired.class) != null) {
field.setAccessible(true);
var filedBean = applicationContext.getBean(field.getType());
if (filedBean != null) {
try {
field.set(ob, filedBean);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
//other code
}
```
这里,我们为ApplicationContext加了一个新的方法,用来得到所有的bean
**ApplicationContext.java**
```java
public class ApplicationContext {
//other code
public Collection
加载全部内容