前言
又鸽了辣么久,失策,失策,那么今天的这个东西呢,其实是对前面写的那个东西(手写那个mvc5个注解的那篇文章的优化)。当然这个内容也是来自咱们《Spring5核心原理与30个类手写实战 》这本书。强推,有时间背面试题不如看这本书,咱们把全流程走一下。 我们先大体说一说流程,然后在讨论一些细节。
流程回顾
咱们先回顾一下咱们原来写的流程。
原来我们是把所有的方法都放在了这个一个类里面,HUDispatcherServlet 这个上帝类里面。 这样显然是不行的,所以咱们得优化一下下。不过这里咱们目前是只优化这个IOC,DI 的内容。后面咱们优化 AOP,MVC的内容。
项目结构
那么接下来咱们得重新修改一下咱们的项目了。
配置文件
首先这个配置文件和上一次的一样,不会发生改变。
项目结构
光看这个咱们可能有点迷糊,那么咱们来说说这个流程。 这个IOC和DI的启动流程。
IOC/DI流程
这里我解释一下
ApplicationContext
这个呢,是咱们整个IOC的入口,其实在Spring里面有一个BeanFactory这个是顶层接口,然后那个ApplicationContext是那个接口的实现。这里没有搞那么复杂,不过流程大体流程是这样子的。
这个ApplicationContext提供了一个非常重要的方法,就是getBean()方法。这个方法就可以帮我们把那个容器里面的对象拿出来。
BeanDefinitionReader
这个是一个解析器嘛,负责解析各种配置文件,比如xml,yaml,properites之类的。 解析之后,要做的是把这个配置文件对应的内容给解析出来,然后把东西封装到BeanDefinition里面
BeanDefinition
这个玩意呢,是封装的我们的类的(被扫描出来)的信息。比如 我们在配置文件里面写的玩意是 我们要扫描这个包下面的所有类,那么这个扫描首先是reader读取了要扫描包的信息,然后reader去扫描 之后把扫描结果,类名,全包名封装起来。下面是它的代码,可以看到。
package com.huterox.spring.framework.beans.config;
public class HUBeanDefinition {
private String factoryBeanName;
private String beanClassName;
public void setFactoryBeanName(String factoryBeanName) {
this.factoryBeanName = factoryBeanName;
}
public void setBeanClassName(String beanClassName) {
this.beanClassName = beanClassName;
}
public String getFactoryBeanName() {
return factoryBeanName;
}
public String getBeanClassName() {
return beanClassName;
}
}
BeanWrapper
这玩意呢,其实是实例化后的对象,同样我们也是把这玩意封装起来了。 然后我们可以再看到这个代码
package com.huterox.spring.framework.beans;
public class HUBeanWrapper {
private Object wrapperInstance;
private Class<?> wrapperClass;
public HUBeanWrapper(Object instance) {
this.wrapperInstance = instance;
this.wrapperClass = this.wrapperInstance.getClass();
}
public Object getWrapperInstance() {
return wrapperInstance;
}
public Class<?> getWrapperClass() {
return wrapperClass;
}
}
getBean()方法
最后就是我们的这个方法,这个方法呢,是我们的核心 我们的依赖注入就是在这里完成的,同时也是在这里面会产生这个ABA问题嘛 下面这个就是别人博客里面的什么三级缓存 我们接下来会好好说说这个。 这个一级缓存到底是啥,二级是啥,三级是啥 看到这里咱们也用了
IOC启动流程(HUApplicationContext)
启动/调用
接下来咱们好好聊聊这个IOC 首先是咱们的这个MVC部分,咱们是做了一个拆分,把IOC独立了 所以我们可以看到这里,这里的话就被启动了,调用了 这个MVC部分呢,调用了咱们的容器,然后我们的容器开始启动工作。
流程
OK到了咱们这个流程了 一来进入咱们的构造方法
获取Definition
先获取到咱们的Definition。这里有两步嘛。 主要是reader读取配置文件,然后扫描包封装信息嘛。 这里我把这个reader的代码给出来
package com.huterox.spring.framework.beans.suports;
import com.huterox.spring.framework.beans.config.HUBeanDefinition;
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.Objects;
import java.util.Properties;
public class HUBeanDefinitionReader {
private Properties contextConfig = new Properties();
private List<String> registryBeanClass = new ArrayList<>();
public HUBeanDefinitionReader(String[] configLocations) {
doLoadConfig(configLocations[0]);
doScanner(contextConfig.getProperty("scanPackage"));
doLoadBeanDefinitions();
}
public List<HUBeanDefinition> doLoadBeanDefinitions() {
List<HUBeanDefinition> result = new ArrayList<>();
try {
for (String className:registryBeanClass){
Class<?> beanClass = Class.forName(className);
if(beanClass.isInterface()) continue;
result.add(doCreateBeanDefition(toLowerFirstCase(beanClass.getSimpleName()), beanClass.getName()));
for(Class<?> InfClass:beanClass.getInterfaces()){
result.add(doCreateBeanDefition(InfClass.getSimpleName(), beanClass.getName()));
}
}
}catch (Exception e){
e.printStackTrace();
}
return result;
}
private HUBeanDefinition doCreateBeanDefition(String factoryBeanName, String beanClassName) {
HUBeanDefinition beanDefinition = new HUBeanDefinition();
beanDefinition.setFactoryBeanName(factoryBeanName);
beanDefinition.setBeanClassName(beanClassName);
return beanDefinition;
}
private void doLoadConfig(String contextproperties) {
InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextproperties);
try {
contextConfig.load(is);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void doScanner(String scanPackage){
URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
assert url != null;
File classDir = new File(url.getFile());
for(File file: Objects.requireNonNull(classDir.listFiles())){
if(file.isDirectory()){
this.doScanner(scanPackage+"."+file.getName());
}else {
if (!file.getName().endsWith(".class")) continue;
String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));
registryBeanClass.add(clazzName);
}
}
}
private String toLowerFirstCase(String simpleName){
char[] chars = simpleName.toCharArray();
if(67<=(int)chars[0]&&(int)chars[0]<=92)
chars[0] +=32;
return String.valueOf(chars);
}
}
BeanDefinition的代码上面给了,咱们就不重复了(所以好好看看本文,代码是在每一步给你的,不是最后面全部贴出来滴!不过方向,绝对不会让读者手打代码,毕竟我也要回过来看呀)
然后这里注意的是,此时我们把这个BeanDefinition给放在了我们的Map里面
这个存储的是所有的类(test包下面)的信息,对比刚刚被人博客里面的三级缓存的图
你发现这个一级缓存的初始容量很大,那咱们的definition你会发现这个其实存的东西是最多的。 再来对比一下给出的spring里面的那个关于三级缓存的解释:
1. 一级缓存-singletonObjects是用来存放就绪状态的Bean。保存在该缓存中的Bean所实现Aware子接口的方法已经回调完毕,自定义初始化方法已经执行完毕,也经过BeanPostProcessor实现类的postProcessorBeforeInitialization、postProcessorAfterInitialization方法处理;
2. 二级缓存-earlySingletonObjects是用来存放早期曝光的Bean,一般只有处于循环引用状态的Bean才会被保存在该缓存中。保存在该缓存中的Bean所实现Aware子接口的方法还未回调,自定义初始化方法未执行,也未经过BeanPostProcessor实现类的postProcessorBeforeInitialization、postProcessorAfterInitialization方法处理。如果启用了Spring AOP,并且处于切点表达式处理范围之内,那么会被增强,即创建其代理对象。 这里额外提一点,普通Bean被增强(JDK动态代理或CGLIB)的时机是在AbstractAutoProxyCreator实现的BeanPostProcessor的postProcessorAfterInitialization方法中,而处于循环引用状态的Bean被增强的时机是在AbstractAutoProxyCreator实现的SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法中。
3. 三级缓存-singletonFactories是用来存放创建用于获取Bean的工厂类-ObjectFactory实例。在IoC容器中,所有刚被创建出来的Bean,默认都会保存到该缓存中。
不过在咱们这里由于还没涉及到那么多复杂的东西,所以我们这边只是简单处理,这个一级缓存就是我们首次扫描到的Bean
创建容器
这里的话咱们是一个方法嘛 doCreateBean(); 这个doCreateBean()其实调用的是getBean()
getBean()方法(重点)
这个getBean方法就是做了好几步骤
instanceBean
这个方法是负责实例化咱们的对象 你会发现此时我们又是把那个第一次没有DI注入的对象给存进去了 这里
private Map<String, Object> factoryBeanObjectCache = new HashMap<>();
对比刚刚这句话
2. 二级缓存-earlySingletonObjects是用来存放早期曝光的Bean,一般只有处于循环引用状态的Bean才会被保存在该缓存中。保存在该缓存中的Bean所实现Aware子接口的方法还未回调,自定义初始化方法未执行,也未经过BeanPostProcessor实现类的postProcessorBeforeInitialization、postProcessorAfterInitialization方法处理。如果启用了Spring AOP,并且处于切点表达式处理范围之内,那么会被增强,即创建其代理对象。 这里额外提一点,普通Bean被增强(JDK动态代理或CGLIB)的时机是在AbstractAutoProxyCreator实现的BeanPostProcessor的postProcessorAfterInitialization方法中,而处于循环引用状态的Bean被增强的时机是在AbstractAutoProxyCreator实现的SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法中。
当然咱们现在没有AOP哈,不过这个算不算是我们早期暴露出来的Bean
populateBean
最后就是完成我们的这个注入 这个的话和我们以前的那一套一样
然后加入我们最后一级缓存
HUApplicationContext完整代码
package com.huterox.spring.framework.context;
import com.huterox.spring.framework.annotation.HUAutowired;
import com.huterox.spring.framework.annotation.HUController;
import com.huterox.spring.framework.annotation.HUService;
import com.huterox.spring.framework.beans.HUBeanWrapper;
import com.huterox.spring.framework.beans.suports.HUBeanDefinitionReader;
import com.huterox.spring.framework.beans.config.HUBeanDefinition;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class HUApplicationContext {
private String[] configLocations;
private HUBeanDefinitionReader reader;
private Map<String, HUBeanDefinition> beanDefinitionMap = new HashMap<>();
private Map<String, HUBeanWrapper> factoryBeanInstanceCache = new HashMap<>();
private Map<String, Object> factoryBeanObjectCache = new HashMap<>();
public HUApplicationContext(String... configLocations) {
this.configLocations = configLocations;
this.reader = new HUBeanDefinitionReader(this.configLocations);
List<HUBeanDefinition> beanDefinitions = this.reader.doLoadBeanDefinitions();
try {
doRegistryBeanDefintition(beanDefinitions);
} catch (Exception e) {
e.printStackTrace();
}
doCreateBean();
}
private void doCreateBean() {
for (Map.Entry<String, HUBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
String beanName = beanDefinitionEntry.getKey();
getBean(beanName);
}
}
private void doRegistryBeanDefintition(List<HUBeanDefinition> beanDefinitions) throws Exception {
for (HUBeanDefinition beanDefinition : beanDefinitions) {
if (this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
throw new Exception("The " + beanDefinition.getFactoryBeanName() + " is exists!");
}
this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
this.beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
}
}
public Object getBean(String beanName) {
HUBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
Object instance = instanceBean(beanName, beanDefinition);
if (instance == null) return null;
HUBeanWrapper beanWrapper = new HUBeanWrapper(instance);
this.factoryBeanInstanceCache.put(beanName, beanWrapper);
populateBean(beanName, beanDefinition, beanWrapper);
return this.factoryBeanInstanceCache.get(beanName).getWrapperInstance();
}
private void populateBean(String beanName, HUBeanDefinition beanDefinition, HUBeanWrapper beanWrapper) {
Object instance = beanWrapper.getWrapperInstance();
Class<?> clazz = beanWrapper.getWrapperClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (!(field.isAnnotationPresent(HUAutowired.class))) continue;
HUAutowired autowired = field.getAnnotation(HUAutowired.class);
String autowiredBeanName = autowired.value().trim();
if ("".equals(autowiredBeanName)) {
autowiredBeanName = field.getType().getName();
}
field.setAccessible(true);
try {
if (!this.factoryBeanInstanceCache.containsKey(autowiredBeanName)) continue;
field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
private Object instanceBean(String beanName, HUBeanDefinition beanDefinition) {
String className = beanDefinition.getBeanClassName();
Object instance = null;
try {
Class<?> clazz = Class.forName(className);
if (!(clazz.isAnnotationPresent(HUService.class) || clazz.isAnnotationPresent(HUController.class))) {
return null;
}
instance = clazz.newInstance();
this.factoryBeanObjectCache.put(beanName, instance);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return instance;
}
public Object getBean(Class className) {
return getBean(className.getName());
}
public int getBeanDefinitionCount() {
return this.beanDefinitionMap.size();
}
public String[] getBeanDefinitionNames() {
return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
}
}
MVC调用
之后就是给咱的MVC干活了,这个时候原来MVC部分的代码都不要了。
package com.huterox.spring.framework.webmvc.servlet;
import com.huterox.spring.framework.annotation.*;
import com.huterox.spring.framework.context.HUApplicationContext;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
public class HUDispatcherServlet extends HttpServlet {
private Map<String,Method> handlerMapping = new HashMap<>();
private HUApplicationContext context = null;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
System.out.println("hello this request will be processed by mymvcframeword ");
this.doDispatch(req,resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("服务器异常500:"+Arrays.toString(e.getStackTrace()));
}
}
@Override
public void init(ServletConfig config) throws ServletException {
context = new HUApplicationContext(config.getInitParameter("Myproperties"));
doInitHandlerMapping();
}
private void doInitHandlerMapping() {
if(this.context.getBeanDefinitionCount()==0) return;
String [] beanNames = this.context.getBeanDefinitionNames();
for (String beanName:beanNames) {
Object instance = this.context.getBean(beanName);
if(instance==null){
continue;
}
Class<?> clazz = instance.getClass();
if(!clazz.isAnnotationPresent(HUController.class)) continue;
String baseURl="";
if(clazz.isAnnotationPresent(HURequestMapping.class)){
HURequestMapping requestMapping = clazz.getAnnotation(HURequestMapping.class);
baseURl = requestMapping.value();
}
for (Method method : clazz.getMethods()) {
if(!method.isAnnotationPresent(HURequestMapping.class))continue;
HURequestMapping requestMapping = method.getAnnotation(HURequestMapping.class);
String url =("/" + baseURl +"/"+ requestMapping.value()).replaceAll("/+","/");
handlerMapping.put(url,method);
}
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath,"").replaceAll("/+","/");
System.out.println(url);
if(!this.handlerMapping.containsKey(url)){
resp.getWriter().write("404 Not Found!");
return;
}
Map<String,String[]> paramsMap = req.getParameterMap();
Method method = this.handlerMapping.get(url);
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] paramValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
if(parameterType==HttpServletRequest.class){
paramValues[i] = req;
continue;
}else if(parameterType==HttpServletResponse.class){
paramValues[i] =resp;
continue;
}else{
Annotation[][] pa = method.getParameterAnnotations();
for(int j=0;j<pa.length;j++){
for(Annotation a:pa[i]){
if(a instanceof HURequestParam){
String paramName = ((HURequestParam) a).value();
if(!"".equals(paramName)){
String strings =Arrays.toString(paramsMap.get(paramName))
.replaceAll("\\[|\\]","")
.replaceAll("\\s","");
if(parameterType==String.class){
paramValues[i] = strings;
}else if(parameterType==Integer.TYPE ||parameterType==Integer.class ){
paramValues[i] = Integer.valueOf(strings);
}else if(parameterType==Double.TYPE ||parameterType==Double.class ){
paramValues[i] = Double.valueOf(strings);
}else if(parameterType==Float.TYPE ||parameterType==Float.class ){
paramValues[i] = Float.valueOf(strings);
}
}
}
}
}
}
}
method.invoke(this.context.getBean(method.getDeclaringClass()),paramValues);
}
}
这样就完成了工作
测试
一切正常
其他(复现此项目)
接下来最后说说,这个没给出的东西,代码哪里获取。首先这个项目就是在原来的这个 Spring之手写SpringMVC5个注解里面,不同的地方都给出了了,其他压根没动。比如项目配置
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>gpmvc</servlet-name>
<servlet-class>com.huterox.spring.framework.webmvc.servlet.HUDispatcherServlet</servlet-class>
<init-param>
<param-name>Myproperties</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>gpmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
根本没变。
总结
这个东西还是很有意思的,后面咱们在优化~
|