[TOC]
在完全掌握 Spring 系统结构,实现原理,在理解设计模式的基础上,自己动手写一个高仿真版本的 Spring 框架,以达彻理解 Spring 的目的,感受作者创作意图
1. 从 Servlet 到 Applicationcontext
在 300 行代码提炼 Spring 设计精华的课程中我们已经了解 SpringMVC 的入口是 DispatcherSerlvet,我们实现了 DispatcherServlet 的 init() 方法.在 init() 方法中完成了 IOC 容器的初始化,而在我们使用 Spring 的经验中,我们见得最多的是 Applicationcontext,似乎 Spring 托管的所有实例 Bean 都可以通过调用 getBean() 方法枚得.那么 Applicationcontext 又是从何而来的呢?从 Spring 源码中我们可以看到,DispatcherServlet 的类图如下
DispatcherServlet 继承了 FrameworkServlet , FrameworkServlet 继承了 HttpServletBean ,HttpServletBean 继承了 HttpServlet.在 HttpServletBean 的 init() 方法中调用了 FrameworkServlet 的 initServletBean() 方法,在 initServletBean() 方法中初始化 WebApplicationContext 实例.茬 initServletBean() 方法中调用了 DispatcherServlet 重写的 onRefresh() 方法.在 DispatcherServlet 的 onRefresh() 方法中又调用了 initStrategies() 方法,初始化 SpringMVC 的九大组件
其实,上面复杂的调用关系,我们可以简单的得出一个结论:就是在 Servlet 的 init() 方法中初始化了 IOC 容器和 SpringMVC 所依赖的九大组件
2. 项目环境搭建
2.1 application.properties 配置
还是先从 application.properties 文件开始,用 application.properties 来代替 application.xml,具体配置如下∶
#托管的类扫描包路径#
scanPackage=com.gupaoedu.vip.demo
2.2 pom.Xml 配置
接下来看 pom.xml 的配置,主要关注 jar 依赖∶
2.4
javax.servlet
servlet-api
${servlet.api.version)
provided
2.3 web.xml 配置
Gupao Web Application
gpmvc
com.gupaoedu.vip.spring.framework.webmvc.servlet.GPDispatcherServlet
contextConfigLocation
application.properties
1
gpmvc
/*
2.4 GPDispatcherServlet 实现
/**
* 委派模式
* 职责:负责任务调度,请求分发
*/
public class GPDispatcherServlet extends HttpServlet {
private GPApplicationContext applicationContext;
//IoC容器,key默认是类名首字母小写,value就是对应的实例对象
private Map ioc = new HashMap();
private Map handlerMapping = new HashMap();
@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 {
//6、委派,根据URL去找到一个对应的Method并通过response返回
try {
doDispatch(req,resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("500 Exception,Detail : " + Arrays.toString(e.getStackTrace()));
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replaceAll(contextPath,"").replaceAll("/+","/");
if(!this.handlerMapping.containsKey(url)){
resp.getWriter().write("404 Not Found!!!");
return;
}
Map params = 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 paramterType = parameterTypes[i];
if(paramterType == HttpServletRequest.class){
paramValues[i] = req;
}else if(paramterType == HttpServletResponse.class){
paramValues[i] = resp;
}else if(paramterType == String.class){
//通过运行时的状态去拿到你
Annotation[] [] pa = method.getParameterAnnotations();
for (int j = 0; j < pa.length ; j ++) {
for(Annotation a : pa[i]){
if(a instanceof GPRequestParam){
String paramName = ((GPRequestParam) a).value();
if(!"".equals(paramName.trim())){
String value = Arrays.toString(params.get(paramName))
.replaceAll("\\[|\\]","")
.replaceAll("\\s+",",");
paramValues[i] = value;
}
}
}
}
}
}
//暂时硬编码
String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
//赋值实参列表
method.invoke(ioc.get(beanName),paramValues);
}
@Override
public void init(ServletConfig config) throws ServletException {
//初始化Spring核心IoC容器
applicationContext = new GPApplicationContext(config.getInitParameter("contextConfigLocation"));
//==============MVC部分==============
//5、初始化HandlerMapping
doInitHandlerMapping();
System.out.println("GP Spring framework is init.");
}
private void doInitHandlerMapping() {
if(ioc.isEmpty()){ return;}
for (Map.Entry entry : ioc.entrySet()) {
Class clazz = entry.getValue().getClass();
if(!clazz.isAnnotationPresent(GPController.class)){ continue; }
//相当于提取 class上配置的url
String baseUrl = "";
if(clazz.isAnnotationPresent(GPRequestMapping.class)){
GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class);
baseUrl = requestMapping.value();
}
//只获取public的方法
for (Method method : clazz.getMethods()) {
if(!method.isAnnotationPresent(GPRequestMapping.class)){continue;}
//提取每个方法上面配置的url
GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class);
// //demo//query
String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+","/");
handlerMapping.put(url,method);
System.out.println("Mapped : " + url + "," + method);
}
}
}
//自己写,自己用
private String toLowerFirstCase(String simpleName) {
char [] chars = simpleName.toCharArray();
// if(chars[0] > )
chars[0] += 32;
return String.valueOf(chars);
}
}
3. IOC 顶层结构设计
3.1 annotation(自定义配置) 模块
Annotation 的代码实现我们还是沿用 mini 版本的不变,复制过来便可
3.1.1 @GPService 注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPService{
String value() default"";
}
3.1.2 @GPAutowired 注解
@Target({ElementType. FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPAutowired{
String value() default"";
}
3.1.3 @GPController 注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPController{
String value() default"";
}
3.1.4 @GPRequestMapping 注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestMapping {
String value() default"";
}
3.1.5 @GPRequestParam 注解
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestParam {
String value() default"";
}
3.2 beans(配置封装) 模块
3.2.1 GPBeanDefinition
public class GPBeanDefinition {
private String factoryBeanName;
private String beanClassName;
public String getFactoryBeanName() {
return factoryBeanName;
}
public void setFactoryBeanName(String factoryBeanName) {
this.factoryBeanName = factoryBeanName;
}
public String getBeanClassName() {
return beanClassName;
}
public void setBeanClassName(String beanClassName) {
this.beanClassName = beanClassName;
}
}
3.2.2 GPBeanWrapper
public class GPBeanWrapper {
private Object wrapperInstance;
private Class wrappedClass;
public GPBeanWrapper(Object instance) {
this.wrapperInstance = instance;
this.wrappedClass = instance.getClass();
}
public Object getWrapperInstance() {
return wrapperInstance;
}
public Class getWrappedClass() {
return wrappedClass;
}
}
3.3 context(IOC 容器) 模块
3.3.1 GPApplicationContext
/**
* 职责:完成Bean的创建和DI
*/
public class GPApplicationContext {
private GPBeanDefinitionReader reader;
private Map beanDefinitionMap = new HashMap();
private Map factoryBeanInstanceCache = new HashMap();
private Map factoryBeanObjectCache = new HashMap();
public GPApplicationContext(String... configLocations) {
//1、加载配置文件
reader = new GPBeanDefinitionReader(configLocations);
try {
//2、解析配置文件,封装成BeanDefinition
List beanDefinitions = reader.loadBeanDefinitions();
//3、把BeanDefintion缓存起来
doRegistBeanDefinition(beanDefinitions);
doAutowrited();
}catch (Exception e){
e.printStackTrace();
}
}
private void doAutowrited() {
//调用getBean()
//这一步,所有的Bean并没有真正的实例化,还只是配置阶段
for (Map.Entry beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
String beanName = beanDefinitionEntry.getKey();
getBean(beanName);
}
}
private void doRegistBeanDefinition(List beanDefinitions) throws Exception {
for (GPBeanDefinition beanDefinition : beanDefinitions) {
if(this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())){
throw new Exception("The " + beanDefinition.getFactoryBeanName() + "is exists");
}
beanDefinitionMap.put(beanDefinition.getFactoryBeanName(),beanDefinition);
beanDefinitionMap.put(beanDefinition.getBeanClassName(),beanDefinition);
}
}
//Bean的实例化,DI是从而这个方法开始的
public Object getBean(String beanName){
//1、先拿到BeanDefinition配置信息
GPBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
//2、反射实例化newInstance();
Object instance = instantiateBean(beanName,beanDefinition);
//3、封装成一个叫做BeanWrapper
GPBeanWrapper beanWrapper = new GPBeanWrapper(instance);
//4、保存到IoC容器
factoryBeanInstanceCache.put(beanName,beanWrapper);
//5、执行依赖注入
populateBean(beanName,beanDefinition,beanWrapper);
return beanWrapper.getWrapperInstance();
}
private void populateBean(String beanName, GPBeanDefinition beanDefinition, GPBeanWrapper beanWrapper) {
//可能涉及到循环依赖?
//A{ B b}
//B{ A b}
//用两个缓存,循环两次
//1、把第一次读取结果为空的BeanDefinition存到第一个缓存
//2、等第一次循环之后,第二次循环再检查第一次的缓存,再进行赋值
Object instance = beanWrapper.getWrapperInstance();
Class clazz = beanWrapper.getWrappedClass();
//在Spring中@Component
if(!(clazz.isAnnotationPresent(GPController.class) || clazz.isAnnotationPresent(GPService.class))){
return;
}
//把所有的包括private/protected/default/public 修饰字段都取出来
for (Field field : clazz.getDeclaredFields()) {
if(!field.isAnnotationPresent(GPAutowired.class)){ continue; }
GPAutowired autowired = field.getAnnotation(GPAutowired.class);
//如果用户没有自定义的beanName,就默认根据类型注入
String autowiredBeanName = autowired.value().trim();
if("".equals(autowiredBeanName)){
//field.getType().getName() 获取字段的类型
autowiredBeanName = field.getType().getName();
}
//暴力访问
field.setAccessible(true);
try {
if(this.factoryBeanInstanceCache.get(autowiredBeanName) == null){
continue;
}
//ioc.get(beanName) 相当于通过接口的全名拿到接口的实现的实例
field.set(instance,this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
} catch (IllegalAccessException e) {
e.printStackTrace();
continue;
}
}
}
//创建真正的实例对象
private Object instantiateBean(String beanName, GPBeanDefinition beanDefinition) {
String className = beanDefinition.getBeanClassName();
Object instance = null;
try {
Class clazz = Class.forName(className);
//2、默认的类名首字母小写
instance = clazz.newInstance();
this.factoryBeanObjectCache.put(beanName, instance);
}catch (Exception e){
e.printStackTrace();
}
return instance;
}
public Object getBean(Class beanClass){
return getBean(beanClass.getName());
}
}
3.3.2 GPBeanDefinitionReader
public class GPBeanDefinitionReader{
private List registyBeanClasses = new ArraylList();
private Properties config = new Properties();
//固定配置文件中的 key,相对于 xml 的规范
private final String SCAN_PACKAGE ="scanPackage";
public GPBeanDefinitionReader(String... locations) {
//通过 URL 定位找到其所对应的文件,然后转换为文件流
try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(locations[].replace("clspath:",""))) {
config.load(is);
} catch(IOException e){
e.printStackTrace();
}
doScanner(config.getProperty(SCAN_PACKAGE));
}
private void doScanner(String scanPackage) {
//转换为文件路径,实际上就是把.替换为/就 OK 了
URL url=this.getClass().getClassLoader().getResource("/"+scanPackage.replaceAll("\\.","/"));
File classPath = new File(url.getFile());
for (File file:classPath.listFiles()) {
if (file.isDirectory()) {
doScanner(scanPackage + "." + file.gtNlame();
} else {
if (!file.getName().endswith(".class") {
continue;
}
String className = (scanPackage + "." + file.getName().replace(".class",""));
registyBeanClasses.add(className);
}
}
}
public Properties getConfig(){
return this.config;
}
//把配置文件中扫描到的所有的配置信息转换为 GPBeanDefinition 对象,以便于之后 IOC 操作方便
public List loadBeanDefinitions() {
List result = new ArrayList();
try {
for (String className: registyBeanClasses) {
Class beanClass = Class.forName(className);
//如果是一个接口,是不能实例化的/用它实现类来实例化
if (beanClass.isInterface()) {
continue;
}
//beanName 有三种情况∶
//1、默认是类名首字母小写
//2、自定义名字
//3、接口注入
result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getsSimpleName()), 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
private GPBeanDefinition doCreateBeanDefinition(String factoryBeanName, String beanClassName) {
GPBeanDefinition beanDefinition = new GPBeanDefinition();
beanDefinition.setBeanClassName(beanClassName);
beanDefinition.setFactoryBeanName(factoryBeanName);
return beanDefinition;
}
//如果类名本身是小写字母,确实会出问题
//但是我要说明的是∶这个方法是我自己用,private 的
//传值也是自己传,类也都遵循了驼峰命名法
//默认传入的值,存在首字母小写的情况,也不可能出现非字母的情况
//为了简化程序逻辑,就不做其他判断了,大家了解就 OK
//其实用写注释的时间都能够把逻辑写完了
private String tolLowerFirstCase(String simpleName) {
char [] chars = simpleName.toCharArray();
//之所以加,是因为大小写字母的 ASCIT 码相差 32,
//而且大写字母的 ASCII 码要小于小写字母的 ASCII 码
//在 Java 中,对 char 做算学运算,实际上就是对 ASCII 码做算学运算
chars[0] += 32;
return String.valueOf(chars);
}
}