一切开始之前,先了解下 JavaBean 是什么,它是一种标准和约定。
一个 JavaBean:
- 所有的属性都是 private(通过 getter/setter 进行读写)
- 有一个 public 无参数的构造器
- 实现了 Serializable(序列化,注意 Serializable 接口没有方法也没有字段,更多的是一种规范和约定)
1. Spring 是什么
Srping 是自动化管理 Java 对象和其中依赖关系的容器。
- Java世界中Web容器的事实标准
- Spring 容器 - 一个 IoC 容器
- Spring MVC - 基于 Spring 和 Servlet 的 Web 应用框架
- Spring Boot - 集成度和自动化程度更高(内嵌了 Servlet 容器)
- Spring MVC - 基于 Spring 和 Servlet 的 Web 应用框架
-let 词根,“小”的意思,servlet 小服务器(service + let)。
Servlet 将网络请求封装成对象,交给上层 WebApp(spring容器、mvc、boot)。
Servlet <-> HttpServletRequest/HttpServletResponse <-> WebApp。
常见的 servlet 有:
- tomcat
- jetty
没有 Spring 怎么办?
**
- 一个 main 程序走天下:轻量简单,但是规模大了后难以维护。
- 拆分并且手动管理:优点方便测试、共同开发、维护,但缺点还是规模更大后,依赖关系太复杂。
Spring 的出现解放了我们的双手。
2. Spring 最简单的用法
通过配置一种“上古”的 xml 来使用。
xml 配置文件中声明了两个 Bean, 而这两个 Bean 之间存在依赖关系,因此为 OrderService 中的成员属性 OrderDao 添加了注解,用于对 OrderDao 自动装配。
- @AutoWired(以此为例)
- @Resource
- ...
在这里,容器就是 BeanFactory。有了容器之后,向容器索要 Bean。
// maven 依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
package spring.demo;
public class OrderDao {
public void select() {
System.out.println("select!");
}
}
package spring.demo;
import org.springframework.beans.factory.annotation.Autowired;
public class OrderService {
@Autowired // 加了这个注解才会自动装配 OrderDao,否则 OrderService 对象中的 orderDao 是 null
private OrderDao orderDao;
public void doSomething() {
orderDao.select();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config /> <!-- 这句不加的话,即使写了上面的注解也不生效 -->
<!-- bean definitions here -->
<bean id="orderDao" class="spring.demo.OrderDao"/>
<bean id="orderService" class="spring.demo.OrderService"/>
</beans>
package spring.demo;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringMain {
public static void main(String[] args) {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:spring/config.xml");
OrderService orderService = (OrderService)beanFactory.getBean("orderService");
OrderDao orderDao = (OrderDao) beanFactory.getBean("orderDao");
System.out.println(orderService);
orderService.doSomething();
System.out.println(orderDao);
}
}
现在没有写 new,却拿到了 OrderService 对象(一个Bean),默认是单例模式,也就是 OrderService 对象中的 OrderDao 和再次通过 getBean 得到的 OrderDao 对象是同一个对象。
假如没有使用 Spring,那么可能要自己手动创建,涉及到的各种对象的各种 new。
3. Spring 容器核心概念
- Bean
容器中的最小工作单元,通常为一个 Java 对象
- BeanFactory/ApplicationContext
容器本身对应的 Java 对象
- 依赖注入(DI)
容器负责注入所有的依赖
- 控制反转(IoC)
用户将控制权交给了 Spring 容器来进行自动装配
4. 手写一个简单的 IoC 容器
- 定义 Bean
- 加载 Bean 的定义
- 实例化 Bean
- 查找依赖,实现依赖注入
- 要什么 Bean 就设置成什么 Bean
目录结构:
使用方法是在字段是声明注解(部分代码不再罗列,这里仅供举例):
import org.springframework.beans.factory.annotation.Autowired;
public class OrderService {
@Autowired private OrderDao orderDao;
@Autowired private UserService userService;
public void createOrder() {
orderDao.createOrder(userService.getCurrentLoginUser());
}
}
具体实现:
Java 从初代就支持了 .properties
格式的配置文件,该文件用来存储简单的基于 key-value pairs 的参数。该配置文件处于被编译的代码之外。
- 先写一个简单的 beans.properties 配置文件,定义了 Bean 的名字和对应的实现类:
# Bean 名字 和 Bean 的全限定类名
orderDao=com.github.hcsp.ioc.OrderDao
userDao=com.github.hcsp.ioc.UserDao
userService=com.github.hcsp.ioc.UserService
orderService=com.github.hcsp.ioc.OrderService
- MyIoCContainer 容器:
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MyIoCContainer {
// 实现一个简单的IoC容器,使得:
// 1. 从beans.properties里加载bean定义
// 2. 自动扫描bean中的@Autowired注解并完成依赖注入
// 定义一个容器! 存放 bean 的名字到 bean 实例对象的映射
private Map<String, Object> beans = new HashMap<>();
/**
* 依赖注入
*
* @param beanInstance bean 的 实例
*/
private void dependencyInject(Object beanInstance) {
// 拿到带有 @AutoWired 注解的 fields
List<Field> fieldsToBeAutoWired = Stream.of(beanInstance.getClass().getDeclaredFields())
.filter(field -> field.getAnnotation(Autowired.class) != null)
.collect(Collectors.toList());
// 为当前 bean 对象的需要依赖的字段注入依赖(设置字段值)
fieldsToBeAutoWired.forEach(field -> {
String fieldName = field.getName(); // 加了 @AutoWired 的字段名即是所要依赖的 bean 的名字
Object dependencyBeanInstance = beans.get(fieldName); // 所依赖的 bean 实例
try {
field.setAccessible(true); // 设置为 true 用来压制针对被反射对象的访问检查
field.set(beanInstance, dependencyBeanInstance); // 从而可以在这里设置当前 bean 的私有字段
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
}
/**
* 启动该容器
*/
public void start() {
// bean 的初始化
Properties properties = new Properties();
// 从 InputStream 中读取属性列表(键值对)
try {
properties.load(MyIoCContainer.class.getResourceAsStream("/beans.properties"));
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println(properties);
properties.forEach((beanName, beanClassName) -> {
try {
// 通过反射拿到 bean 的实例并放入容器中
Class<?> klass = Class.forName((String) beanClassName);
Object beanInstance = klass.getConstructor().newInstance();
beans.put((String) beanName, beanInstance);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
// 使用反射,处理依赖关系,注入依赖
beans.forEach((beanName, beanInstance) -> dependencyInject(beanInstance));
}
/**
* 从容器中获取一个bean
*
* @param beanName bean 的名字
* @return 返回 bean 的实例
*/
public Object getBean(String beanName) {
return beans.get(beanName);
}
public static void main(String[] args) {
MyIoCContainer container = new MyIoCContainer();
container.start();
OrderService orderService = (OrderService) container.getBean("orderService");
orderService.createOrder();
}
}
即使是两个依赖字段存在循环依赖也没关系,因为在创造期间,会先各自创建出实例对象。相关依赖字段此时是 null,不影响 bean 的创建。而创建完之后,再回头对字段注入所依赖的 bean。
当然,以上只是个简单的实现,实际的 Spring 中还可以在构造器上使用 @Autowired 注解,而不推荐在私有字段上使用。这样即使不是用 Spring,比如写测试代码的时候,还是可以方便的 new 一个实例,否则,私有字段还要通过反射一顿操作才能创建实例进行测试。
另外,实际应用中 @Autowired 也不推荐使用了,更推荐 @Inject 注解,这样除了 Spring 之外,还可能受到其他类似框架的识别。
所谓的 Spring 只不过是在以上基础上扩充了无穷无尽的功能,比如支持 xml 中配置依赖,支持构造器注入(调用哪个构造器,传递哪些参数)等等,从一个很简单的思想不断扩充成庞大的体系。
5. Spring 启动过程浅析
- 在 xml 里中定义 Bean
- BeanDefinition 的加载和解析
- Bean 的实例化和依赖注入
- 对外提供服务
建议 debug Spring 源码的时候,要带着目前来进行,不要在无关细节里浪费太多时间。