从零到一实现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、小结
注解默认值确实需要考虑太多的东西,同时框架也需要更多的业务逻辑判断,所以在编程实践中能明确的就明确,提高程序的运行效率。
写小轮子太烧脑了,有问题欢迎各位读者批评指正。
点个赞再走呗!欢迎留言哦!