[TOC]
在完全掌握 Spring 系統(tǒng)結構,實現(xiàn)原理,在理解設計模式的基礎上,自己動手寫一個高仿真版本的 Spring 框架,以達徹理解 Spring 的目的,感受作者創(chuàng)作意圖
1. 從 Servlet 到 Applicationcontext
在 300 行代碼提煉 Spring 設計精華的課程中我們已經(jīng)了解 SpringMVC 的入口是 DispatcherSerlvet,我們實現(xiàn)了 DispatcherServlet 的 init() 方法.在 init() 方法中完成了 IOC 容器的初始化,而在我們使用 Spring 的經(jīng)驗中,我們見得最多的是 Applicationcontext,似乎 Spring 托管的所有實例 Bean 都可以通過調(diào)用 getBean() 方法枚得.那么 Applicationcontext 又是從何而來的呢?從 Spring 源碼中我們可以看到,DispatcherServlet 的類圖如下
DispatcherServlet 繼承了 FrameworkServlet , FrameworkServlet 繼承了 HttpServletBean ,HttpServletBean 繼承了 HttpServlet.在 HttpServletBean 的 init() 方法中調(diào)用了 FrameworkServlet 的 initServletBean() 方法,在 initServletBean() 方法中初始化 WebApplicationContext 實例.茬 initServletBean() 方法中調(diào)用了 DispatcherServlet 重寫的 onRefresh() 方法.在 DispatcherServlet 的 onRefresh() 方法中又調(diào)用了 initStrategies() 方法,初始化 SpringMVC 的九大組件
其實,上面復雜的調(diào)用關系,我們可以簡單的得出一個結論:就是在 Servlet 的 init() 方法中初始化了 IOC 容器和 SpringMVC 所依賴的九大組件
2. 項目環(huán)境搭建
2.1 application.properties 配置
還是先從 application.properties 文件開始,用 application.properties 來代替 application.xml,具體配置如下∶
#托管的類掃描包路徑#
scanPackage=com.gupaoedu.vip.demo
2.2 pom.Xml 配置
接下來看 pom.xml 的配置,主要關注 jar 依賴∶
<properties>
<!-- dependency versions -->
<servlet.api.version>2.4</servlet.api.version>
</properties>
<dependencies>
<!-- requied start-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet.api.version)</version>
<scope>provided</scope>
</dependency>
<!--requied end -->
</dependencies>
2.3 web.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/j2ee"xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Gupao Web Application</display-name>
<servlet>
<servlet-name>gpmvc</servlet-name>
<servlet-class>com.gupaoedu.vip.spring.framework.webmvc.servlet.GPDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</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>
2.4 GPDispatcherServlet 實現(xiàn)
/**
* 委派模式
* 職責:負責任務調(diào)度,請求分發(fā)
*/
public class GPDispatcherServlet extends HttpServlet {
private GPApplicationContext applicationContext;
//IoC容器,key默認是類名首字母小寫,value就是對應的實例對象
private Map<String,Object> ioc = new HashMap<String,Object>();
private Map<String,Method> handlerMapping = new HashMap<String, Method>();
@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、委派,根據(jù)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<String,String[]> 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){
//通過運行時的狀態(tài)去拿到你
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<String,Object> 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 的代碼實現(xiàn)我們還是沿用 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的創(chuàng)建和DI
*/
public class GPApplicationContext {
private GPBeanDefinitionReader reader;
private Map<String,GPBeanDefinition> beanDefinitionMap = new HashMap<String, GPBeanDefinition>();
private Map<String,GPBeanWrapper> factoryBeanInstanceCache = new HashMap<String, GPBeanWrapper>();
private Map<String,Object> factoryBeanObjectCache = new HashMap<String, Object>();
public GPApplicationContext(String... configLocations) {
//1、加載配置文件
reader = new GPBeanDefinitionReader(configLocations);
try {
//2、解析配置文件,封裝成BeanDefinition
List<GPBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
//3、把BeanDefintion緩存起來
doRegistBeanDefinition(beanDefinitions);
doAutowrited();
}catch (Exception e){
e.printStackTrace();
}
}
private void doAutowrited() {
//調(diào)用getBean()
//這一步,所有的Bean并沒有真正的實例化,還只是配置階段
for (Map.Entry<String,GPBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
String beanName = beanDefinitionEntry.getKey();
getBean(beanName);
}
}
private void doRegistBeanDefinition(List<GPBeanDefinition> 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、執(zhí)行依賴注入
populateBean(beanName,beanDefinition,beanWrapper);
return beanWrapper.getWrapperInstance();
}
private void populateBean(String beanName, GPBeanDefinition beanDefinition, GPBeanWrapper beanWrapper) {
//可能涉及到循環(huán)依賴?
//A{ B b}
//B{ A b}
//用兩個緩存,循環(huán)兩次
//1、把第一次讀取結果為空的BeanDefinition存到第一個緩存
//2、等第一次循環(huán)之后,第二次循環(huán)再檢查第一次的緩存,再進行賦值
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,就默認根據(jù)類型注入
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) 相當于通過接口的全名拿到接口的實現(xiàn)的實例
field.set(instance,this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
} catch (IllegalAccessException e) {
e.printStackTrace();
continue;
}
}
}
//創(chuàng)建真正的實例對象
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<String> registyBeanClasses = new ArraylList<sString>();
private Properties config = new Properties();
//固定配置文件中的 key,相對于 xml 的規(guī)范
private final String SCAN_PACKAGE ="scanPackage";
public GPBeanDefinitionReader(String... locations) {
//通過 URL 定位找到其所對應的文件,然后轉(zhuǎn)換為文件流
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) {
//轉(zhuǎn)換為文件路徑,實際上就是把.替換為/就 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;
}
//把配置文件中掃描到的所有的配置信息轉(zhuǎn)換為 GPBeanDefinition 對象,以便于之后 IOC 操作方便
public List<GPBeanDefinition> loadBeanDefinitions() {
List<GPBeanDefinition> result = new ArrayList<GPBeanDefinition>();
try {
for (String className: registyBeanClasses) {
Class<?> beanClass = Class.forName(className);
//如果是一個接口,是不能實例化的/用它實現(xiàn)類來實例化
if (beanClass.isInterface()) {
continue;
}
//beanName 有三種情況∶
//1、默認是類名首字母小寫
//2、自定義名字
//3、接口注入
result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getsSimpleName()), beanClass.getName()));
Class<?>[]interfaces= beanClass.getInterfaces();
for(Class<?> i : interfaces){
//如果是多個實現(xiàn)類,只能覆蓋
//為什么?因為 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 的
//傳值也是自己傳,類也都遵循了駝峰命名法
//默認傳入的值,存在首字母小寫的情況,也不可能出現(xiàn)非字母的情況
//為了簡化程序邏輯,就不做其他判斷了,大家了解就 OK
//其實用寫注釋的時間都能夠把邏輯寫完了
private String tolLowerFirstCase(String simpleName) {
char [] chars = simpleName.toCharArray();
//之所以加,是因為大小寫字母的 ASCIT 碼相差 32,
//而且大寫字母的 ASCII 碼要小于小寫字母的 ASCII 碼
//在 Java 中,對 char 做算學運算,實際上就是對 ASCII 碼做算學運算
chars[0] += 32;
return String.valueOf(chars);
}
}