從零到一實現SpringMVC
目標:
實現SpringMVC
的基本功能。主要包括:
ChenController
控制類實現ChenService
服務類實現ChenQualifier
自動裝配功能的實現ChenRequestMapping
地址映射功能實現ChenRequestParam
請求參數映射功能實現
實現:
思路:
自定義ChenDispatcherServlet
,完成請求分發的功能。大致步驟如下:
- 首先要根據一個基本包進行掃描,掃描裏面的子包以及子包下的類,將全類名添加到List集和中。
- 其次要把掃描出來的類進行實例化。以全類名爲
key
,class
實例對象爲value
,放入到hashMap
容器中。 - 依賴注入,把
service
層的實例注入到controller
,即初始化controller
層的成員變量。 - 建立
path
與method
的映射關係,保存在hashMap
容器中。
一、前期準備
實現自定義註解,並用自定義註解模擬正常的業務邏輯,實現將用戶發送給服務器的數據回寫給用戶的功能。
1、加入依賴
本項目SpringMVC
是對servlet
的封裝,所以需要引入servlet
的jar
包。源碼如下:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.njust.springmvcchen</groupId>
<artifactId>springmvcchen</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>springmvcchen Maven Webapp</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>springmvcchen</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>8</source>
<target>8</target>
<!--反射獲取方法參數名稱-->
<compilerArgument>-parameters</compilerArgument>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
2、自定義控制層註解
定義ChenController
類,使得該註解的作用範圍在接口或類上 。同時保證註解會在class
字節碼文件中存在,在運行時可以通過反射獲取到。設置value
屬性值,默認爲空。源碼如下:
ChenController .java
package com.njust.springmvc.annotation;
import java.lang.annotation.*;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 16:00
* @description:
*/
@Target({ElementType.TYPE}) //作用範圍:用在接口或類上
@Retention(RetentionPolicy.RUNTIME) // 註解會在class字節碼文件中存在,在運行時可以通過反射獲取到
@Documented//明該註解將被包含在javadoc中
public @interface ChenController {
String value() default "";
}
3、自定義服務層註解
定義ChenService
類,和控制層功能基本一致,定義該註解作用範圍在接口或類上 。同時保證 註解會在class
字節碼文件中存在,在運行時可以通過反射獲取到,設置value
屬性值,默認爲空。源碼如下:
ChenService .java
package com.njust.springmvc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 16:06
* @description:
*/
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenService {
String value() default "";
}
4、自定義依賴注入註解
定義ChenQualifier
類,和控制層功能基本一致,定義該註解作用範圍在字段、枚舉的常量上 。同時保證 註解會在class
字節碼文件中存在,在運行時可以通過反射獲取到,設置value
屬性值,默認爲空。源碼如下:
ChenQualifier .java
package com.njust.springmvc.annotation;
import java.lang.annotation.*;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 16:04
* @description:
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenQualifier {
String value() default "";
}
5、自定義地址映射註解
定義ChenRequestMapping
類,和控制層功能基本一致,定義該註解作用範圍在字段、枚舉的常量以及方法上 。同時保證 註解會在class
字節碼文件中存在,在運行時可以通過反射獲取到,設置value
屬性值,默認爲空。源碼如下:
ChenRequestMapping .java
package com.njust.springmvc.annotation;
import java.lang.annotation.*;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 16:04
* @description:
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenRequestMapping {
//路徑ChenRequestMapping(value)
String value() default "";
}
6、自定義請求參數映射註解
定義ChenRequestParam
類,和控制層功能基本一致,定義該註解作用範圍在方法參數上 。同時保證註解會在class
字節碼文件中存在,在運行時可以通過反射獲取到,設置value
屬性值,默認爲空。源碼如下:
ChenRequestParam .java
package com.njust.springmvc.annotation;
import java.lang.annotation.*;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 16:04
* @description:
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenRequestParam {
String value() default "";
}
7、業務服務接口
定義BaseServiceUse
類,模擬服務的基本操作,此處是查詢、新增、更新的功能。源碼如下:
BaseServiceUse .java
package com.njust.springmvc.service;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 16:08
* @description:
*/
public interface BaseServiceUse {
String query(String name, String age);
String insert(String param);
String update(String param);
}
8、業務服務實現
定義BaseServiceUseImpl
類,該類十分簡單,實現BaseServiceUse
接口裏面的方法,直接返回字符串數據,模擬從數據庫取得的信息。使用自定義註解ChenService
,將該類注入到容器中。源碼如下:
BaseServiceUseImpl.java
package com.njust.springmvc.service.impl;
import com.njust.springmvc.annotation.ChenService;
import com.njust.springmvc.service.BaseServiceUse;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 16:08
* @description:
*/
@ChenService(value = "baseServiceUseImpl")
public class BaseServiceUseImpl implements BaseServiceUse {
@Override
public String query(String name, String age) {
return "{name=" + name + ",age=" + age + "}";
}
@Override
public String insert(String param) {
return "insert successful.....";
}
@Override
public String update(String param) {
return "update successful.....";
}
}
9、業務控制層
定義BaseControllerUse
類,通過自定義註解ChenQualifier
將BaseServiceUse
自動裝配到ChenController
實例中。使用自定義註解ChenController
,將該類注入到容器中。使用自定義註解ChenRequestMapping
,將請求映射到不同的方法實現。其中query()
函數,直接將用戶發送過來的數據回寫給用戶,功能十分簡單。源碼如下:
BaseControllerUse .java
package com.njust.springmvc.controller;
import com.njust.springmvc.annotation.ChenController;
import com.njust.springmvc.annotation.ChenQualifier;
import com.njust.springmvc.annotation.ChenRequestMapping;
import com.njust.springmvc.annotation.ChenRequestParam;
import com.njust.springmvc.service.BaseServiceUse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 16:10
* @description:
*/
@ChenController(value = "baseControllerUse")
//@ChenRequestMapping(value = "/chen")
public class BaseControllerUse {
@ChenQualifier(value = "baseServiceUseImpl")
private BaseServiceUse baseServiceUse;
@ChenRequestMapping(value = "/query")
public void query(HttpServletRequest request, HttpServletResponse response,
@ChenRequestParam(value = "name") String name,
@ChenRequestParam(value = "age") String age) {
try {
PrintWriter writer = response.getWriter();
String result = baseServiceUse.query(name, age);
writer.write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
@ChenRequestMapping("/insert")
public void insert(HttpServletRequest request,
HttpServletResponse response) {
try {
PrintWriter pw = response.getWriter();
String result = baseServiceUse.insert("0000");
pw.write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
@ChenRequestMapping("/update")
public void update(HttpServletRequest request,
HttpServletResponse response, String param) {
try {
PrintWriter pw = response.getWriter();
String result = baseServiceUse.update(param);
pw.write(result);
} catch (IOException e) {
e.printStackTrace();
}
}
}
10、通用工具類
定義CommonUtil
類,實現子類獲取等功能。源碼如下:
CommonUtil .java
package com.njust.springmvc.utils;
/**
* @author Chen
* @version 1.0
* @date 2020/4/1 14:59
* @description:
*/
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
public class CommonUtil {
//獲取某個類的實現類
public static List<Class<?>> getAllAssignedClass(Class<?> cls) throws Exception {
List<Class<?>> classes = new ArrayList<Class<?>>();
for (Class<?> c : getClasses(cls)) {
if (cls.isAssignableFrom(c) && !cls.equals(c)) {
classes.add(c);
}
}
return classes;
}
public static List<Class<?>> getClasses(Class<?> cls) throws Exception {
String pk = cls.getPackage().getName();
String path = pk.replace('.', '/');
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
URL url = classloader.getResource(path);
return getClasses(new File(url.getFile()), pk);
}
//根據路徑獲取
public static List<Class<?>> getClasses(File dir, String pk) throws ClassNotFoundException {
List<Class<?>> classes = new ArrayList<Class<?>>();
if (!dir.exists()) {
return classes;
}
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
classes.addAll(getClasses(f, pk + "." + f.getName()));
}
String name = f.getName();
if (name.endsWith(".class")) {
classes.add(Class.forName(pk + "." + name.substring(0, name.length() - 6)));
}
}
return classes;
}
//動態獲取,根據反射,比如獲取xx.xx.xx.xx.Action 這個所有的實現類。 xx.xx.xx.xx 表示包名 Action爲接口名或者類名
public static List<Class<?>> getAllActionSubClass(String classPackageAndName) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Field field = null;
Vector v = null;
Class<?> cls = null;
List<Class<?>> allSubclass = new ArrayList<Class<?>>();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> classOfClassLoader = classLoader.getClass();
cls = Class.forName(classPackageAndName);
while (classOfClassLoader != ClassLoader.class) {
classOfClassLoader = classOfClassLoader.getSuperclass();
}
field = classOfClassLoader.getDeclaredField("classes");
field.setAccessible(true);
v = (Vector) field.get(classLoader);
for (int i = 0; i < v.size(); ++i) {
Class<?> c = (Class<?>) v.get(i);
if (cls.isAssignableFrom(c) && !cls.equals(c)) {
allSubclass.add((Class<?>) c);
}
}
return allSubclass;
}
}
}
二、請求分發
攔截用戶請求並分發給響應的控制器進行處理。
1、自定義DispatcherServlet
自定義DispatcherServlet
類,實現根據一個基本包進行掃描,掃描裏面的子包以及子包下的類,將全類名添加到List
集和中的功能。其次要把掃描出來的類進行實例化。以全類名爲key
,class
實例對象爲value
,放入到hashMap
容器中。然後實行依賴注入,把service
層的實例注入到controller
,即初始化controller
層的成員變量。最後建立path
與method
的映射關係,保存在hashMap
容器中。由於我們的SpringMVC
是基於servlet
實現的,所以ChenDispatcherServlet
要繼承HttpServlet
。源碼如下:
ChenDispatcherServlet.java
package com.njust.springmvc.servlet;
import com.njust.springmvc.annotation.ChenController;
import com.njust.springmvc.annotation.ChenQualifier;
import com.njust.springmvc.annotation.ChenRequestMapping;
import com.njust.springmvc.annotation.ChenService;
import com.njust.springmvc.controller.BaseControllerUse;
import com.njust.springmvc.handlerAdapter.HandlerAdapterService;
import com.sun.xml.internal.ws.util.StringUtils;
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.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 16:16
* @description:
*/
public class ChenDispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
List<String> classNames = new ArrayList<String>();
Map<String, Object> beans = new HashMap<String, Object>();
Map<String, Object> handlerMap = new HashMap<String, Object>();
// 將具體的方法和controller關聯
Map<String, Object> handlerControllerMap = new HashMap<String, Object>();
Properties prop = null;
private static String HANDLERADAPTER = "jamesHandlerAdapter";
public void init(ServletConfig config) {
// 1、我們要根據一個基本包進行掃描,掃描裏面的子包以及子包下的類
scanPackage("com.njust");
System.out.println("scanPackage(\"com.njust\");");
for (String className : classNames) {
System.out.println(className);
}
// 2、我們肯定是要把掃描出來的類進行實例化
instance();
// 3、依賴注入,把service層的實例注入到controller
ioc();
// 4、建立一個path與method的映射關係
HandlerMapping();
System.out.println("HandlerMapping();");
for (Map.Entry<String, Object> entry : handlerMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
/*
* InputStream is = this.getClass()
* .getResourceAsStream("/config/properties/spring.properties"); prop =
* new Properties(); try { prop.load(is); } catch (IOException e) {
* e.printStackTrace(); }
*/
}
private void scanPackage(String basePackage) {
//掃描編譯好的類路徑下所有的類
URL url = this.getClass().getClassLoader().getResource("/" + replaceTo(basePackage));
String fileStr = url.getFile();
File file = new File(fileStr);
//拿到所有類com.njust 下的 com.njust.springmvc 文件夾
// list()方法是返回某個目錄下的所有文件和目錄的文件名,返回的是String數組
String[] filesStr = file.list();
for (String path : filesStr) {
File filePath = new File(fileStr + path);//掃描com.njust.springmvc下的所有class類
//遞歸調用掃描,如果是路徑,繼續掃描
if (filePath.isDirectory()) {
// com.njust.com.njust.springmvc
scanPackage(basePackage + "." + path);
} else {
classNames.add(basePackage + "." + filePath.getName());//如果是class文件則加入List集合(待生成bean)
}
}
}
private String replaceTo(String basePackage) {
// 英文點號(.)表示任意字符 所以需要轉義
return basePackage.replaceAll("\\.", "/");
}
public ChenDispatcherServlet() {
}
private void instance() {
if (classNames.size() <= 0) {
System.out.println("包掃描失敗!");
return;
}
//遍歷掃描到的class文件,將需要實例化的類(加了註解的類)進行反射創建對象(像註解就不需要實例化)
for (String className : classNames) {
// com.njust.com.njust.springmvc.service.impl.BaseServiceUseImpl.class
String cn = className.replace(".class", "");
try {
Class<?> clazz = Class.forName(cn);//拿到class類,用來實例化
// 將掃描到的類,獲取類名,並判斷是否標記了ChenController註解
// 註釋ChenController是否在此clazz上。如果在則返回true;不在則返回false。
if (clazz.isAnnotationPresent(ChenController.class)) {
//調用構造方法
Object instance = clazz.newInstance();
//獲取對應的請求路徑"/chen"
// ChenRequestMapping requestMapping = clazz
// .getAnnotation(ChenRequestMapping.class);
// String rmvalue = requestMapping.value();//得到"/chen"請求路徑
//用路徑做爲key,對應value爲實例化對象
// beans.put(rmvalue, instance);
// 注意:此處改進版本是使用全限定名
ChenController chenController = clazz.getAnnotation(ChenController.class);
if (chenController.value() == null || chenController.value().equals("")) {
beans.put(instance.getClass().getName(), instance);
} else {
beans.put(chenController.value(), instance);
}
} else if (clazz.isAnnotationPresent(ChenService.class)) {
//獲取當前clazz類的註解(通過這個註解可得到當前service的id) @com.njust.springmvc.annotation.ChenService(value=baseServiceUseImpl)
ChenService service = clazz.getAnnotation(ChenService.class);
Object instance = clazz.newInstance();
//put(baseServiceUseImpl,instance)
// 默認使用全限定名
if (service.value() == null || service.value().equals("")) {
beans.put(instance.getClass().getName(), instance);
} else {
beans.put(service.value(), instance);
}
} else {
continue;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request, response);
}
private void errorDeal(HttpServletResponse response, String message) {
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = null;
try {
writer = response.getWriter();
} catch (IOException e) {
e.printStackTrace();
}
try {
writer.write(new String(message.getBytes("UTF-8"), StandardCharsets.UTF_8));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//獲取到請求路徑 /springmvcchen/chen/query
String uri = request.getRequestURI();
String context = request.getContextPath();
//將 "/springmvcchen/chen/query" 去掉"/springmvcchen"
String path = uri.replace(context, "");
//根據請求路徑來獲取要執行的方法
Method method = (Method) handlerMap.get(path);
if (method == null) {
errorDeal(response, "請檢查路徑是否設置正確!");
return;
}
//拿到控制類
// BaseControllerUse instance = (BaseControllerUse) beans.get("/" + path.split("/")[1]);
// BaseControllerUse instance = (BaseControllerUse) beans.get(path);
Object instance = handlerControllerMap.get(path);
if (instance == null) {
errorDeal(response, "controller 請檢查參數是否設置正確!");
return;
}
//處理器
HandlerAdapterService ha = (HandlerAdapterService) beans.get(HANDLERADAPTER);
/*
* @RequestMapping("/order")
* order(@RequestBody String params, @RequestHeader @RequestParam String param1){//參數有多種類型接收方式
* }
*/
Object[] args = ha.hand(request, response, method, beans);
try {
method.invoke(instance, args);
// method.invoke(instance, new
// Object[]{request,response,null});//拿參數
/*如果有多個參數類型,就得這樣寫了(可用策略模式,省去以下代碼)
* if(ParamType == HttpServletRequest){
}else if(ParamType == @RquestHeader){
}else
*用策略模式實現(把粒度控制得更細),新建 ChenHandlerAdapter
*/
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private void HandlerMapping() {
if (beans.entrySet().size() <= 0) {
System.out.println("沒有類的實例化!");
return;
}
for (Map.Entry<String, Object> entry : beans.entrySet()) {
Object instance = entry.getValue();
Class<?> clazz = instance.getClass();
// 拿所有Controller的類
if (clazz.isAnnotationPresent(ChenController.class)) {
String classPath = "";
//防止用戶在類上沒有寫 ChenRequestMapping
if (clazz.isAnnotationPresent(ChenRequestMapping.class)) {
//@com.njust.springmvc.annotation.ChenRequestMapping(value=/chen)
ChenRequestMapping requestMapping = clazz
.getAnnotation(ChenRequestMapping.class);
// 獲取Controller類上面的ChenRequestMapping註解裏的請求路徑
classPath = requestMapping.value();
}
// 獲取控制類裏的所有方法
Method[] methods = clazz.getMethods();
// 獲取方法上的ChenRequestMapping設置的路徑,與方法名建立映射關係
for (Method method : methods) {
//判斷哪些方法上使用ChenRequestMapping路徑註解
if (method.isAnnotationPresent(ChenRequestMapping.class)) {
//@com.njust.springmvc.annotation.ChenRequestMapping(value=/query)
ChenRequestMapping methodrm = method
.getAnnotation(ChenRequestMapping.class);
String methodPath = methodrm.value();
// 把方法上與路徑建立映射關係( /Chen/query--->public void com.njust.springmvc.controller.ChenController.query )
handlerMap.put(classPath + methodPath, method);
//將具體的路徑和controller實例關聯
handlerControllerMap.put(classPath + methodPath, instance);
} else {
continue;
}
}
}
}
}
// 初始化IOC容器
private void ioc() {
if (beans.entrySet().size() <= 0) {
System.out.println("沒有類的實例化!");
return;
}
//將實例化好的bean遍歷,
for (Map.Entry<String, Object> entry : beans.entrySet()) {
Object instance = entry.getValue();//獲取bean實例
Class<?> clazz = instance.getClass();//獲取類,用來判斷類裏聲明瞭哪些註解(主要是針對控制類裏的判斷,比如使用了@Autowired @Qualifier,對這些註解進行解析)
//判斷該類是否使用了ChenController註解 或ChenService註解,註解類允許注入
if (clazz.isAnnotationPresent(ChenController.class) || clazz.isAnnotationPresent(ChenService.class)) {
Field[] fields = clazz.getDeclaredFields();// 拿到類裏面的屬性
// 判斷是否聲明瞭自動裝配(依賴注入)註解,比如@Autrowired @Qualifier
for (Field field : fields) {
if (field.isAnnotationPresent(ChenQualifier.class)) {
ChenQualifier qualifier = field.getAnnotation(ChenQualifier.class);
//拿到@ChenQualifier("ChenServiceImpl")裏的指定要注入的bean名字"ChenServiceImpl"
String value = qualifier.value();
// 通用化,如果不設置值,則使用默認值
if (value == null || value.equals("")) {
// 使用全類名
value = field.getType().getName();
try {
// 根據全類名獲取class文件
Class<?> aClass = Class.forName(value);
// 判斷該類是否是接口
if (aClass.isInterface()) {
// 查看接口的所有實現類
List<Class<?>> allActionSubClass = CommonUtil.getAllActionSubClass(value);
if (allActionSubClass.size() >= 2 || allActionSubClass.size() <= 0) {
System.exit(0);
throw new RuntimeException("接口沒有實現或接口子類有多個實現!");
}
value = (allActionSubClass.get(0).getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
field.setAccessible(true);
try {
// 從MAP容器中獲取"ChenServiceImpl"對應的bean,並注入實例控制層bean,解決依賴注入
// 給instance對象的field字段賦值 beans.get(value)
field.set(instance, beans.get(value));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
continue;
}
}
} else {
continue;
}
}
}
}
2、web.xml
設置啓動容器時刻初始化ChenDispatcherServlet
,映射路徑爲/
。源碼如下:
web.xml
<!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>
<servlet>
<servlet-name>ChenDispatcherServlet</servlet-name>
<servlet-class>com.njust.springmvc.servlet.ChenDispatcherServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ChenDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3、參數解析
定義ArgumentResolver
接口類,解析方法中的參數,獲取數值並返回給調用者。源碼如下:
ArgumentResolver .java
package com.njust.springmvc.argumentResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 17:52
* @description:
*/
public interface ArgumentResolver {
public boolean support(Class<?> type, int paramIndex, Method method);
//參數解析方法
public Object argumentResolver(HttpServletRequest request,
HttpServletResponse response, Class<?> type,
int paramIndex,//參數索引下座標,有很多註解,你得知道是哪個參數的註解,每個參數的索引順序不一樣
Method method);
}
4、HttpServletRequest參數解析
定義HttpServletRequestArgumentResolver
類,實現ArgumentResolver
接口。support()
主要用來判斷傳進來的類型是否是ServletRequest
類或它的子類。因爲這是一個request
參數解析類,所以argumentResolver()
方法直接返回request
參數給調用者。源代碼如下:
HttpServletRequestArgumentResolver .java
package com.njust.springmvc.argumentResolver;
import com.njust.springmvc.annotation.ChenService;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 17:53
* @description:
*/
@ChenService("httpServletRequestArgumentResolver")
public class HttpServletRequestArgumentResolver implements ArgumentResolver {
//判斷傳進來的參數是否爲request
public boolean support(Class<?> type, int paramIndex, Method method) {
// isAssignableFrom()方法是判斷是否爲某個類的父類
/**
* class1.isAssignableFrom(class2) 判定此 Class1 對象所表示的類或接口與
* 指定的 Class2 參數所表示的類或接口是否相同,
* 或是否是其超類或超接口。
* 如果是則返回 true;否則返回 false。
*/
return ServletRequest.class.isAssignableFrom(type);
}
////如果返回的參數是request,則直接返回
public Object argumentResolver(HttpServletRequest request,
HttpServletResponse response, Class<?> type, int paramIndex,
Method method) {
return request;
}
}
5、HttpServletResponse參數解析
定義HttpServletResponseArgumentResolver
類,同HttpServletRequestArgumentResolver
類似,實現ArgumentResolver
接口。support()
主要用來判斷傳進來的類型是否是ServletResponse
類或它的子類。因爲這是一個response
參數解析類,所以argumentResolver()
方法直接返回response
參數給調用者。源碼如下:
HttpServletResponseArgumentResolver .java
package com.njust.springmvc.argumentResolver;
import com.njust.springmvc.annotation.ChenService;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 17:54
* @description:
*/
@ChenService("httpServletResponseArgumentResolver")
public class HttpServletResponseArgumentResolver implements ArgumentResolver {
//判斷傳進來的參數是否爲response
public boolean support(Class<?> type, int paramIndex, Method method) {
return ServletResponse.class.isAssignableFrom(type);
}
//如果返回的參數是response,則直接返回
public Object argumentResolver(HttpServletRequest request,
HttpServletResponse response, Class<?> type, int paramIndex,
Method method) {
return response;
}
}
6、RequestParam參數解析
定義RequestParamArgumentResolver
類,同HttpServletRequestArgumentResolver
類似,實現ArgumentResolver
接口。support()
主要用來判斷傳進來的類型是否加了ChenRequestParam
註解或它的子類註解。argumentResolver()
方法判斷是否使用了ChenRequestParam
註解,如果沒有使用,直接返回null
,如果使用了,首先判斷value
是否爲空,如果不爲空,直接通過request
獲取參數值並返回,如果爲空,則通過java
反射機制獲取參數名稱,然後再通過request
獲取參數值並返回。這裏注意要判斷jdk
的版本,這個方法只是在JDK1.8
以後才支持,同時在maven
中一定要配置<compilerArgument>-parameters</compilerArgument>
,才能使得該方法生效。源碼如下:
RequestParamArgumentResolver .java
package com.njust.springmvc.argumentResolver;
import com.njust.springmvc.annotation.ChenRequestParam;
import com.njust.springmvc.annotation.ChenService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 17:54
* @description:
*/
@ChenService("requestParamArgumentResolver")
//解析聲明註解爲RequestParam, 獲取註解的值
public class RequestParamArgumentResolver implements ArgumentResolver {
//判斷傳進來的參數是否爲ChenRequestParam
public boolean support(Class<?> type, int paramIndex, Method method) {
Annotation[][] an = method.getParameterAnnotations();
Annotation[] paramAns = an[paramIndex];
for (Annotation paramAn : paramAns) {
//判斷傳進的paramAn.getClass()是不是 ChenRequestParam 類型
if (ChenRequestParam.class.isAssignableFrom(paramAn.getClass())) {
return true;
}
}
return false;
}
//參數解析,並獲取註解的值
public Object argumentResolver(HttpServletRequest request,
HttpServletResponse response, Class<?> type, int paramIndex,
Method method) {
Annotation[][] an = method.getParameterAnnotations();
Annotation[] paramAns = an[paramIndex];
for (Annotation paramAn : paramAns) {
if (ChenRequestParam.class.isAssignableFrom(paramAn.getClass())) {
ChenRequestParam rp = (ChenRequestParam)paramAn;
String value = rp.value();
if (value == null || value.equals("")) {
//如果是JDK1.8以後,使用反射獲取參數名稱
if (System.getProperty("java.version").contains("1.8.")) {
Parameter parameter = method.getParameters()[paramIndex];
String name = parameter.getName();
value = name;
}
}
return request.getParameter(value);
}
}
return null;
}
}
7、請求適配
定義HandlerAdapterService
類,封裝請求處理。源碼如下:
HandlerAdapterService .java
package com.njust.springmvc.handlerAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 17:56
* @description:
*/
public interface HandlerAdapterService {
Object[] hand(HttpServletRequest request,//拿request請求裏的參數
HttpServletResponse response,//
Method method,
Map<String, Object> beans);
}
8、請求適配實現
定義JamesHandlerAdapter
類,實現根據用戶請求的URL和方法,獲取到參數的值並返回。源碼如下:
JamesHandlerAdapter .java
package com.njust.springmvc.handlerAdapter;
import com.njust.springmvc.annotation.ChenService;
import com.njust.springmvc.argumentResolver.ArgumentResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @author Chen
* @version 1.0
* @date 2020/3/29 17:56
* @description:
*/
@ChenService("jamesHandlerAdapter")
public class JamesHandlerAdapter implements HandlerAdapterService {
//對method方法裏的參數進行處理
public Object[] hand(HttpServletRequest request,//需要傳入request,拿請求的參數
HttpServletResponse response, Method method,//執行的方法,可以拿到當前待執行的方法有哪些參數
Map<String, Object> beans) {
//拿到當前待執行的方法有哪些參數類型
Class<?>[] paramClazzs = method.getParameterTypes();
//根據參數的個數,new 一個參數的數組,將方法裏的所有參數賦值到args來
Object[] args = new Object[paramClazzs.length];
//1、要拿到所有實現了ArgumentResolver這個接口的實現類
Map<String, Object> argumentResolvers = getBeansOfType(beans,
ArgumentResolver.class);
int paramIndex = 0;
int i = 0;
//對每一個參數進行循環,每個參數都有特殊處理(比如RequestParam的處理類爲 RequestParamArgumentResolver )
for (Class<?> paramClazz : paramClazzs) {
//哪個參數對應了哪個參數解析類,用策略模式來找
for (Map.Entry<String, Object> entry : argumentResolvers.entrySet()) {
ArgumentResolver ar = (ArgumentResolver) entry.getValue();
if (ar.support(paramClazz, paramIndex, method)) {
args[i++] = ar.argumentResolver(request,
response,
paramClazz,
paramIndex,
method);
}
}
paramIndex++;
}
return args;
}
//獲取實現了ArgumentResolver接口的所有實例(其實就是每個參數的註解實例)
private Map<String, Object> getBeansOfType(Map<String, Object> beans,//所有bean
Class<?> intfType) //類型的實例
{
Map<String, Object> resultBeans = new HashMap<>();
for (Map.Entry<String, Object> entry : beans.entrySet()) {
//拿到實例-->反射對象-->它的接口(接口有多實現,所以爲數組)
Class<?>[] intfs = entry.getValue().getClass().getInterfaces();
if (intfs != null && intfs.length > 0) {
for (Class<?> intf : intfs) {
//接口的類型與傳入進來的類型一樣,把實例加到resultBeans裏來
if (intf.isAssignableFrom(intfType)) {
resultBeans.put(entry.getKey(), entry.getValue());
}
}
}
}
return resultBeans;
}
}
三、測試結果
輸入url:http://localhost:8080/query?name=1&age=2,結果顯示:{name=1,age=2}
。
以上結果表明我們的手寫SpringMVC
已經可以正常工作了。
總結
步驟
自定義SpringMVC
的主要實現步驟如下:
- 首先我們自定義
ChenController
ChenService
ChenQualifier
ChenRequestMapping
ChenRequestParam
註解類,保證註解不僅被保存到class
文件中,而且jvm
加載class
文件之後,仍然存在,以便被spring
反射出實例對象。 - 然後自定義
ChenDispatcherServlet
,完成請求分發的功能。同時在web.xml
中設置啓動容器時刻初始化ChenDispatcherServlet
,映射路徑爲/
。ChenDispatcherServlet
初始化工作主要包括:- 首先要根據一個基本包進行掃描,掃描裏面的子包以及子包下的類,將全類名添加到List集和中。
- 其次要把掃描出來的類進行實例化。以全類名爲
key
,class
實例對象爲value
,放入到hashMap
容器中。 - 依賴注入,把
service
層的實例注入到controller
,即初始化controller
層的成員變量。 - 建立
path
與method
的映射關係,保存在hashMap
容器中。
- 然後
doPost
方法監聽用戶請求。具體流程如下:- 首先根據請求路徑去容器中找到相應的方法,如果沒有直接返回,並告知用戶沒有該路徑。
- 然後根據請求路徑去容器中找到對應的控制類,如果沒有直接返回,並告知用戶沒有該路徑。
- 然後根據策略模式獲取請求參數的實際值。
- 最後通過反射機制執行該方法,並響應給用戶,完成整個業務處理邏輯。
流程圖
服務啓動流程圖
請求響應流程圖
重點及易錯點
1、參數名稱獲取
String value = rp.value();
if (value == null || value.equals("")) {
//如果是JDK1.8以後,使用反射獲取參數名稱
if (System.getProperty("java.version").contains("1.8.")) {
Parameter parameter = method.getParameters()[paramIndex];
String name = parameter.getName();
value = name;
}
}
這段代碼處理ChenRequestParam
沒有設置value()
使用默認值的情況,一定要判斷JDK
的版本,同時在maven
中添加如下配置參數。
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>8</source>
<target>8</target>
<!--反射獲取方法參數名稱-->
<compilerArgument>-parameters</compilerArgument>
</configuration>
</plugin>
2、控制反轉(IOC),實例化成員變量
//拿到@ChenQualifier("ChenServiceImpl")裏的指定要注入的bean名字"ChenServiceImpl"
String value = qualifier.value();
// 通用化,如果不設置值,則使用默認值
if (value == null || value.equals("")) {
// 使用全類名
value = field.getType().getName();
try {
// 根據全類名獲取class文件
Class<?> aClass = Class.forName(value);
// 判斷該類是否是接口
if (aClass.isInterface()) {
// 查看接口的所有實現類
List<Class<?>> allActionSubClass = CommonUtil.getAllActionSubClass(value);
if (allActionSubClass.size() >= 2 || allActionSubClass.size() <= 0) {
System.exit(0);
throw new RuntimeException("接口沒有實現或接口子類有多個實現!");
}
value = (allActionSubClass.get(0).getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
上面的代碼是處理@ChenQualifier
沒有設置value()
使用默認值的情況。如果是正常的類,則直接使用全類名作爲key去容器中找實例對象。如果是接口,則需要注意判斷接口是否實現,或有多個實現。如果有多個實現可以任意選擇一個實例或拋出異常。spring
官方是直接拋出異常,這裏我們也是拋出異常。
3、容器key命名
我查閱相關資料發現,有一些作者將類的名稱作爲key
,而不是全類名,當框架中有多個模塊的時候難免會有類名衝突的問題。所以我建議還是使用全類名,上面的代碼實現也是全類名。
4、小結
註解默認值確實需要考慮太多的東西,同時框架也需要更多的業務邏輯判斷,所以在編程實踐中能明確的就明確,提高程序的運行效率。
寫小輪子太燒腦了,有問題歡迎各位讀者批評指正。
點個贊再走唄!歡迎留言哦!