作为经典手动音量调节思想的春天实现,它能够帮我们开发灵活的JavaWeb应用。今天我们就来对它动刀,看看它的内部是怎么实现的,我们能不能仿写一份呢?
首先我们通过一张时序图来看一下springMVC的运行流程
在我们的流程中DispatcherServlet领导=前端控制器映射处理器
好了明确了我们要搭的任务,现在建哥来手把手教学,开搞!
详细步骤
1.新建webApp骨架的maven工程
2.在pox.xml中引入依赖
! 引入servlet jar
属国
groupIdjavax.servlet/groupId
artifactidjavax。servlet-API/artifactId
版本3 .1 .0/版本
提供的范围/示波器
/依赖性
! 引入反射冲突包
属国
groupIdorg.reflections/groupId
artifactId反射/artifactId
版本0 .9 .11/版本
/依赖性
3.新建包如图所示
4.编写配置文件
在资源目录下编写配置文件:
applicationContext.properties,内容为:指定扫描路径包装,我们在这里指定控制器所在的包
package=com。云智慧。控制器
5.更新web.xml文件
骨架用的还是2.0版本,我们在这里更新为4.0的。
并且注册我们的领导MyDispatcherServlet并为其指定配置文件所在位置contextConfigLocation,我们的领导凡事亲力亲为,在这里让他拦截所有请求。
?可扩展标记语言版本='1.0 '编码='UTF-8 '?
web-app xmlns=' http://xmlns。JCP。' org/XML/ns/javaee '
xmlns : xsi=' http://www。w3。org/2001/XMLSchema-instance '
xsi :架构位置=' http://xmlns。JCP。http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd'
版本='4.0 '
显示名称原型创建的网络应用程序/显示名称
!-配置我们自己的前端控制器,MyDispatcherServlet就是一个servlet,拦截前端发送的请求-
小型应用程序
servlet-namexxx/servlet-name
servlet类com。云智慧。servlet。mydispatchersvlet/servlet类
初始化参数
param-name contextconfiglocation/param-name
param-valueapplicationContext.properties/p
aram-value> </init-param> </servlet> <servlet-mapping> <servlet-name>xxx</servlet-name> <!-- 拦截所有请求--> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>6.自定义注解
注解在这里的作用就相当于给类/方法加上一个小尾巴,我们通过不同的尾巴辨识不同的Controller和Method
我们定义两个注解
@MyController
package com.cloudwise.annotition;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Teacher 陈
* @creat 2021-02-22-13:04
* @describ 我的Controller注解,用于模仿spring中的@Controller
* 能够作用于类上,标识该类是一个Controller
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
/**
* 没有用,但为了模仿spring中的@Controller,我们还是把它加上
* 我们的简单版采用默认的id:首字母小写的类名
*/
String value() default "";
}
@MyRequestMapping
package com.cloudwise.annotition;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Teacher 陈
* @creat 2021-02-22-13:11
* @describ 用于模仿spring中的@RequestMapping
* 能够作用于类和方法上,用于通过url指定对应的Controller和 Method
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
/**
* 简单版,域名只能有一段,只能是/controllerName/methodName
*/
String value() default "";
}
好了上面的就是一些准备性的工作,如果说把仿写springMVC看成是组成一个团队的话,上面的工作相当于给团队找工作场地,下面就是对人物的刻画了,首先有请我们的领导MyDispatcherServlet
编写前端控制器
编写前端控制器(一个Servlet),并重写init和service方法
MyDispatcherServlet
总览
整个过程围绕两个重写的方法而展开,其中init()是重点。
MyDispatcherServlet要做的事,用一句话来说:看前端的访问地址,然后调用匹配的处理器(Controller)的对应方法(method)
要完成这些,我们需要通过注解,为Controller和method绑定上一定的字符串,然后通过分析前端传过来的Url中的字符串,找到两者相同的,以此完成匹配。反射在此过程中发挥了巨大作用,不论是找到类头上的注解,还是找到注解中的值等诸多动作都需要反射。
具体流程
Init
Service 注:在此处Handler = controller + method
代码(分步)
创建一个dispatcherServlet继承httpservlet 并重写两个方法
public class MyDispatcherServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
接下来就是填充两个方法了,首先是init()方法
它大概可以分为4步
- 加载配置文件
- 扫描controller包
- 初始化controller
- 初始化Handler映射器(Handler = controller + method)
那我们开始吧,写加载配置文件的代码
1.加载配置文件
首先,我们在这里选用properties文件的形式进行配置,因此,需要有一个properties对象
/**
* 我们将需要扫描的包放在一个.properties文件中
* 需要在初始化的时候读取它
*/
private Properties properties = new Properties();
再写一个工具性的方法
/**
* 加载配置文件
* @param fileName
*/
private void loadConfigfile(String fileName) throws IOException {
//以流的方式获取资源
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(fileName);
properties.load(resourceAsStream);
resourceAsStream.close();
}
之后,我们在init()中调用该方法
@Override
public void init(ServletConfig config) throws ServletException {
//1. 加载配置文件
//这里我们在web.xml中配置的初始化参数contextConfigLocation就起到效果了
String initParameter = config.getInitParameter("contextConfigLocation");
try {
loadConfigfile(initParameter);
} catch (IOException e) {
e.printStackTrace();
}
}
那么至此,我们的第一步加载配置文件部分的代码就写完啦
另外三步采用同样的思路
2.扫描controller包
定义所需属性
/**
* 我们需要一个Set,将所有能够响应的Controller存起来
*/
private Set<Class<?>> classSet = new HashSet<>();
写工具性方法
/**
* 扫描包,获取所有带MyController的类
* @param packageName
*/
private void scanPackage(String packageName){
Reflections reflections = new Reflections(packageName);
classSet = reflections.getTypesAnnotatedWith(MyController.class);
}
在init()中调用
@Override
public void init(ServletConfig config) throws ServletException {
//1. 加载配置文件
//这里我们在web.xml中配置的初始化参数contextConfigLocation就起到效果了
String initParameter = config.getInitParameter("contextConfigLocation");
try {
loadConfigfile(initParameter);
} catch (IOException e) {
e.printStackTrace();
}
//2. 扫描controller包,存储所有能够响应的Controller
scanPackage(properties.getProperty("package"));
}
3.初始化controller
定义所需属性
/**
* 类spring-mvc容器,存储Controller对象
*/
private Map<String,Object> mySpringMVCContext = new HashMap<>();
写工具性方法
/**
* 初始化Controller
*/
private void initController() throws IllegalAccessException, InstantiationException {
if(classSet.isEmpty()){
return;
}
for (Class<?> controller : classSet) {
mySpringMVCContext.put(firstWordToLowCase(controller.getSimpleName()),controller.newInstance());
}
}
/**
* 首字母转小写
* @param string
* @return 首字母为小写的String
*/
private String firstWordToLowCase(String string){
char[] chars = string.toCharArray();
//将大写转成小写
chars[0]+=32;
return String.valueOf(chars);
}
在init()中调用
@Override
public void init(ServletConfig config) throws ServletException {
//1. 加载配置文件
String initParameter = config.getInitParameter("contextConfigLocation");
try {
loadConfigfile(initParameter);
} catch (IOException e) {
e.printStackTrace();
}
//2. 扫描controller包,存储所有能够响应的Controller
scanPackage(properties.getProperty("package"));
//3. 初始化controller
try {
initController();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
4.初始化Handler映射器
(Handler = controller + method)
定义所需属性
/**
* 存储所有方法的Map<url:method>
*/
private Map<String,Method> methodMap = new HashMap<>();
/**
* 存储所有Controller的Map
*/
private Map<String,Object> controllerMap = new HashMap<>();
具体实现方法
private void initHandlerMapping() {
if (mySpringMVCContext.isEmpty()){
return;
}
for (Map.Entry<String, Object> entry : mySpringMVCContext.entrySet()) {
Class<?> entryClass = entry.getValue().getClass();
if (!entryClass.isAnnotationPresent(MyController.class)){
continue;
}
//Controller类上的requestMapping值,如果有则获取
String baseUrl = "";
if (entryClass.isAnnotationPresent(MyRequestMapping.class)){
MyRequestMapping annotation = entryClass.getAnnotation(MyRequestMapping.class);
baseUrl = annotation.value();
}
//获取所有方法
Method[] methods = entryClass.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MyRequestMapping.class)){
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
String url = annotation.value();
url = baseUrl + url;
//将该方法放入方法集
methodMap.put(url,method);
//将该controller方法处理器集
controllerMap.put(url,entry.getValue());
//至此,初始化完成,后端整装待发
}
}
}
}
在init()中调用
@Override
public void init(ServletConfig config) throws ServletException {
//1. 加载配置文件
String initParameter = config.getInitParameter("contextConfigLocation");
try {
loadConfigfile(initParameter);
} catch (IOException e) {
e.printStackTrace();
}
//2. 扫描controller包,存储所有能够响应的Controller
scanPackage(properties.getProperty("package"));
//3. 初始化controller
try {
initController();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
//4. 初始化Handler映射器
initHandlerMapping();
}
好了至此,我们的领导MyDispatcherServlet 的初始化部分就写完了,现在他已经对自己的团队成员:众多业务员们(Controller)已经了如指掌了(有同学可能会问:陈老师,你还没定义Controller呢!这个先不急)下面,我们就重写他的service()方法,让他能够到外面接活
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (methodMap.isEmpty()){
return;
}
String uri = req.getRequestURI();
String contextPath = req.getContextPath();
//获取有效url
String url = uri.replace(contextPath,"");
//如果没有对应的url,返回404
if (!methodMap.containsKey(url)){
resp.getWriter().println("404");
}else {
//有的话,通过invoke方法执行对应controller的method
Method method = methodMap.get(url);
Object controller = controllerMap.get(url);
try {
method.invoke(controller);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
至此,MyDispatcherServlet的所有代码都已经完成了,他也能够成为一个合格的领导了。
上面部分代码写的较为分散,文末放上MyDispatcherServlet的完整代码供同学们参考
最后,编写一个Controller进行测试
package com.cloudwise.controller;/**
* @author Teacher 陈
* @creat 2021-02-22-14:57
* @describ
*/
import com.cloudwise.annotition.MyController;
import com.cloudwise.annotition.MyRequestMapping;
/**
* @author :Teacher 陈
* @date :Created in 2021/2/22 14:57
* @description:我的实验性Controller
* @modified By:
* @version:
*/
@MyController
@MyRequestMapping(value = "/test")
public class MyFirstController {
@MyRequestMapping(value = "/test1")
public void test1(){
System.out.println("test1被调用了");
}
@MyRequestMapping(value = "/test2")
public void test2(){
System.out.println("test2被调用了");
}
@MyRequestMapping(value = "/test3")
public void test3(){
System.out.println("test3被调用了");
}
}
测试
1.为本项目配置tomcat
2.运行
3.1浏览器地址栏输入对应网址
控制台成功打印日志信息
3.2浏览器地址栏输入无效网址,页面返回404
至此,今天的手写springMVC就全部完成了。
当然本项目还有很多待提升的地方,诸如不能返回json数据,controller不能有参数,等等。但是我们不可能一朝一夕建成罗马,应该一步一个脚印,通过这个项目掌握springMVC的运行流程,为以后更难的项目打下点基础。
代码(总览)
package com.cloudwise.servlet;/**
* @author Teacher 陈
* @creat 2021-02-22-13:44
* @describ
*/
import com.cloudwise.annotition.MyController;
import com.cloudwise.annotition.MyRequestMapping;
import org.reflections.Reflections;
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.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author :Teacher 陈
* @date :Created in 2021/2/22 13:44
* @description:我的前端控制器dispather
* @modified By:
* @version:
*/
public class MyDispatcherServlet extends HttpServlet {
/**
* 我们将需要扫描的包放在一个.properties文件中
* 需要在初始化的时候读取它
*/
private Properties properties = new Properties();
/**
* 我们需要一个Set,将所有能够响应的Controller存起来
*/
private Set<Class<?>> classSet = new HashSet<>();
/**
* 类spring-mvc容器,存储Controller对象
*/
private Map<String,Object> mySpringMVCContext = new HashMap<>();
/**
* 存储所有方法的Map<url:method>
*/
private Map<String,Method> methodMap = new HashMap<>();
/**
* 存储所有Controller的Map
*/
private Map<String,Object> controllerMap = new HashMap<>();
/**
* @description: 初始化,要做什么事呢?
* 0. 接收到请求之后,首先将后端环境初始化好
* 1. 加载配置文件
* 2. 扫描controller包
* 3. 初始化controller
* 4. 初始化Controller映射器
* @create by: Teacher 陈
* @create time: 2021/2/22 13:47
* @param config
* @return void
*/
@Override
public void init(ServletConfig config) throws ServletException {
//1. 加载配置文件
String initParameter = config.getInitParameter("contextConfigLocation");
try {
loadConfigfile(initParameter);
} catch (IOException e) {
e.printStackTrace();
}
//2. 扫描controller包,存储所有能够响应的Controller
scanPackage(properties.getProperty("package"));
//3. 初始化controller
try {
initController();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
//4. 初始化Controller映射器
initHandlerMapping();
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (methodMap.isEmpty()){
return;
}
String uri = req.getRequestURI();
String contextPath = req.getContextPath();
String url = uri.replace(contextPath,"");
if (!methodMap.containsKey(url)){
resp.getWriter().println("404");
}else {
Method method = methodMap.get(url);
Object controller = controllerMap.get(url);
try {
method.invoke(controller);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
/**
* 以下为工具性函数
*/
/**
* 加载配置文件
* @param fileName
*/
private void loadConfigfile(String fileName) throws IOException {
//以流的方式获取资源
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(fileName);
properties.load(resourceAsStream);
resourceAsStream.close();
}
/**
* 扫描包,获取所有带MyController的类
* @param packageName
*/
private void scanPackage(String packageName){
Reflections reflections = new Reflections(packageName);
classSet = reflections.getTypesAnnotatedWith(MyController.class);
}
/**
* 初始化Controller
*/
private void initController() throws IllegalAccessException, InstantiationException {
if(classSet.isEmpty()){
return;
}
for (Class<?> controller : classSet) {
mySpringMVCContext.put(firstWordToLowCase(controller.getSimpleName()),controller.newInstance());
}
}
/**
* 首字母转小写
* @param string
* @return 首字母为小写的String
*/
private String firstWordToLowCase(String string){
char[] chars = string.toCharArray();
//将大写转成小写
chars[0]+=32;
return String.valueOf(chars);
}
private void initHandlerMapping() {
if (mySpringMVCContext.isEmpty()){
return;
}
for (Map.Entry<String, Object> entry : mySpringMVCContext.entrySet()) {
Class<?> entryClass = entry.getValue().getClass();
if (!entryClass.isAnnotationPresent(MyController.class)){
continue;
}
//Controller类上的requestMapping值,如果有则获取
String baseUrl = "";
if (entryClass.isAnnotationPresent(MyRequestMapping.class)){
MyRequestMapping annotation = entryClass.getAnnotation(MyRequestMapping.class);
baseUrl = annotation.value();
}
//获取所有方法
Method[] methods = entryClass.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MyRequestMapping.class)){
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
String url = annotation.value();
url = baseUrl + url;
//将该方法放入方法集
methodMap.put(url,method);
//将该controller方法处理器集
controllerMap.put(url,entry.getValue());
//至此,初始化完成,后端整装待发
}
}
}
}
}