深入剖析Spring(五):IOC核心思想(代码篇)
在上一篇文章中,我们粗略的对Spring源码IOC这块过了一遍,那么这篇文章来简单写一个IOC的过程。由于理论性的东西都在上一篇解释过了,这篇咱就直接在代码中理解。
一、准备工作
1.1本文所用到的依赖包:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
1.2 web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<display-name>MVC Web Application</display-name>
<servlet>
<servlet-name>CcMvc</servlet-name>
<!-- 指定我们自定义的DispatcherServlet的路径-->
<servlet-class>com.ccc.spring2.webmvc.XHDispatcherServlet</servlet-class>
<init-param>
<!--指定spring的xml的配置文件,我在这里用properties文件代替。-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.properties</param-value>
</init-param>
<!-- 让tomcat启动时即加载时便初始化servlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<!-- url-->
<servlet-name>CcMvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
1.3 建立项目结构
我们模仿spring的项目结构
1.3.1 创建BeanFactory接口
在包beans下创建:
/** * @author chenxh * @date 2020/3/23 20:28 * @Description: 单例工厂顶层 * @modify: * @modifyDate: * @Description: */
public interface XHBeanFactory {
/** * 根据beanName从IOC容器中获取一个实例Bean * spring中使用单例有利于管理和维护 * */
Object getBean(String beanName) throws Exception;
Object getBean(Class<?> beanClass) throws Exception;
}
1.3.2 创建BeanDefinition
在之前说道过在Spring容器启动的过程中,会将Bean解析成BeanDefinition结构。因此我们也创建一个,模仿spring的包名创建一个config包:
@Data
public class XHBeanDefinition {
private String beanClassName; //全包名
private boolean isLazyInit = false; //是否懒加载
private String factoryBeanName; //类名首字母小写
private boolean isSingleton = true; //是否单例
}
1.3.3 BeanDefinitionReader
这个方法主要作用是解析配置文件并将其封装成BeanDefinition对象供IOC操作,此方法在上几篇文章MVC中写过,这里就不在解释了。
package com.ccc.spring2.beans.support;
import com.ccc.spring2.beans.config.XHBeanDefinition;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/** * 加载配置文件的类 */
public class XHBeanDefinitionReader {
private Properties config = new Properties();
//定义配置文件,暂时写死
private final String SCAN_PACKAGE = "scanPackage";
//存储所有的类名
private List<String> registyBeanClasses = new ArrayList<String>(); //全包名
public XHBeanDefinitionReader(String... locations) {
//通过URL定位找到对应的文件,转为文件流读取
try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(locations[0].replace("classpath:", ""))) {
config.load(is);
} catch (IOException e) {
e.printStackTrace();
}
//进行扫描
doScanner(config.getProperty(SCAN_PACKAGE));
}
private void doScanner(String scanPackage) {
//转换为文件路径,实际上就是把.替换为/就OK了
URL url = this.getClass().getResource("/" + scanPackage.replaceAll("\\.","/"));
File classPath = new File(url.getFile());
for (File file : classPath.listFiles()) {
if (file.isDirectory()) {
doScanner(scanPackage + "." + file.getName());
} else {
if (!file.getName().endsWith(".class")) {
continue;
}
String className = (scanPackage + "." + file.getName().replace(".class", ""));
registyBeanClasses.add(className);
}
}
}
public Properties getConfig() {
return this.config;
}
//扫描配置信息并内部封装成XHBeanDefinition对象,便于IOC操作
public List<XHBeanDefinition> loadBeanDefinitions(String... locations) {
List<XHBeanDefinition> result = new ArrayList<XHBeanDefinition>();
try {
System.out.println("registyBeanClasses:"+registyBeanClasses);
for (String className : registyBeanClasses) {
Class<?> beanClass = Class.forName(className);
//如果是一个接口,是不能实例化的
//用它实现类来实例化
if(beanClass.isInterface()) { continue; }
//beanName有三种情况:
//1、默认是类名首字母小写
//2、自定义名字
//3、接口注入
result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getSimpleName()),beanClass.getName()));
result.add(doCreateBeanDefinition(beanClass.getName(),beanClass.getName()));
Class<?> [] interfaces = beanClass.getInterfaces();
for (Class<?> i : interfaces) {
//如果是多个实现类,只能覆盖
//为什么?因为Spring没那么智能,就是这么傻
//这个时候,可以自定义名字
result.add(doCreateBeanDefinition(i.getName(),beanClass.getName()));
}
}
}catch (Exception e){
e.printStackTrace();
}
return result;
}
//把配置信息解析成BeanDefinition
//把每一个配信息解析成一个BeanDefinition
private XHBeanDefinition doCreateBeanDefinition(String factoryBeanName,String beanClassName){
XHBeanDefinition beanDefinition = new XHBeanDefinition();
beanDefinition.setBeanClassName(beanClassName);
beanDefinition.setFactoryBeanName(factoryBeanName);
return beanDefinition;
}
/** * 首字母小写 */
private String toLowerFirstCase(String simpleName) {
char [] chars = simpleName.toCharArray();
chars[0] += (1<<5);
return String.valueOf(chars);
}
}
1.3.4 BeanWrapper
BeanWrapper是对Bean的包装,其接口中所定义的功能很简单包括设置获取被包装的对象,获取被包装bean的属性描述器.在包beans下:
public class XHBeanWrapper {
private Object wrappedInstance; //类名,首字母小写
private Class<?> wrappedClass; //对象
public XHBeanWrapper(Object wrappedInstance){
this.wrappedInstance = wrappedInstance;
}
public Object getWrappedInstance(){
return this.wrappedInstance;
}
// 返回代理以后的Class
// 可能会是这个 $Proxy0
public Class<?> getWrappedClass(){
return this.wrappedInstance.getClass();
}
}
1.3.5 IOC容器AbstractApplicationContext
创建包名context在子包support下创建(support包名的意思在java中大多数是扩展的意思。)
在上篇文章中提到过IOC初始化真正的逻辑是在refresh() 中,而该又是实际上又是一个模板方法,只是规定了IOC启动的流程,具体的逻辑还是由其子类来实现的。
public abstract class XHAbstractApplicationContext {
//规范方法,提供重写
public void refresh() throws Exception { };
}
1.3.6 DefaultListableBeanFactory
是否还记得这个类,在这稍微解释一下spring中BeanFactory定义了一系列规范都由子类去实现,例如表示可序列化的Bean,有继承关系的bean等等,这些Bean在spring中都是分开管理的而DefaultListableBeanFactory这个类就是这些子类最终的实现类。也就是这些类最终都由它来实现了。
在这里我们简单的定义一下。理解一下它的思想:在beans包下创建support
//我们简单就实现一个IOC父类
public class XHDefaultListableBeanFactory extends XHAbstractApplicationContext {
//存储注册信息的BeanDefinition,伪IOC容器 //key是类名小写,value是beanDefinition对象
protected final Map<String, XHBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, XHBeanDefinition>();
}
1.3.7 ApplicationContext
这个应该不陌生了。
public class XHApplicationContext extends XHDefaultListableBeanFactory implements XHBeanFactory {
//配置文件
private String[] configLocations;
//加载配置文件的类
private XHBeanDefinitionReader reader;
//单例IOC容器
private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
//通用IOC容器
private Map<String, XHBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>();
public XHApplicationContext(String... configLocations) throws Exception {
this.configLocations = configLocations;
refresh();
}
/** * 重写AbstractApplicationContext的refresh方法 * 此方法是一个模板方法,规定了IOC容器的运行流程, * 调用的是父类AbstractApplicationContext 的方法. * 真正的载入也是从这个时候开始的。 */
@Override
public void refresh() throws Exception {
//IOC
//1.定位,找到配置文件
reader = new XHBeanDefinitionReader(this.configLocations);
//2.加载配置文件,扫描相关的类,把它们封装成BeanDefinition
List<XHBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
//3.注册,把配置信息放到容器里(伪IOC容器)
doRegisterBeanDefinition(beanDefinitions);
//4.把非懒加载的类提前初始化
doAutowired();
}
}
手动创建以上方法。到此准备工作结束了。
2. 填坑
既然我们主路拉通了,那么现在就开始来填坑。
关注refresh()这个方法是IOC容器初始化的入口。其实在之前的文章中就讲过这个过程。在这里1、2两步就不说啦,代码也贴出来了也注释啦,有兴趣的朋友可以去看看小编这个专栏下的MVC篇。里面提到了这些。
2.1 doRegisterBeanDefinition(beanDefinitions)
此方法将BeanDefinitionReader对象解析出来对象进行迭代并存放到Map中。
private void doRegisterBeanDefinition(List<XHBeanDefinition> beanDefinitions) {
//IOC加载
for (XHBeanDefinition beanDefinition : beanDefinitions) {
//父类XHDefaultListableBeanFactory中定义的map。这个Map不是真正意义上的IOC容器
super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
}
}
2.2 doAutowired()
遍历上一步中存入Map中的集合,并判断对象中isLazyInit属性是否为懒加载,如果不是懒加载的就进行getBean()DI注入操作。
//处理非延时加载
private void doAutowired() throws Exception {
//遍历定位
for (Map.Entry<String, XHBeanDefinition> beanDefinitionEntry : super.beanDefinitionMap.entrySet()) {
String beanName = beanDefinitionEntry.getKey();
if (!beanDefinitionEntry.getValue().isLazyInit()) {
getBean(beanName);
}
}
}
IOC的简单步骤到此就介绍的差不多了,我们发现不管是之前MVC中介绍的,还是本文章介绍的内容,我们都能发现Spring在处理过程中,都是一步步去解析的,例如之前在解析配置文件的时候,会先加载->在找到所有class类->在判断是否@Service、@Controller这些类注解->在判断是否@Autowried等注解。
对于IOC 整理就以上这些就介绍这么多,如果发现有补充的地方,我会继续添加,同时希望各位看过的伙伴们如果发现了问题能够及时批评指正,在此感谢。下一篇我会记录DI的过程。
上一篇:深入剖析Spring(四):IOC核心思想(源码分析篇)
还没有评论,来说两句吧...