前言
本文是自己实现 SpringMVC 底层机制的第二篇之完成实现任务阶段 2- 完成客户端浏览器可以请求控制层
个人主页:尘觉主页
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TabAxc96-1692497234107)(https://picgoowyx.oss-cn-guangzhou.aliyuncs.com/imags/202308152043025.png)]
个人简介:大家好,我是尘觉,希望我的文章可以帮助到大家,您的满意是我的动力
在csdn获奖荣誉: csdn城市之星2名
Java全栈群星计划top前5
端午大礼包获得者
欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,感谢大家的观看
如果文章有什么需要改进的地方还请大佬不吝赐教 先在次感谢啦
public class MonsterController {
public void listMonsters(HttpServletRequest request, HttpServletResponse
response) {
response.setContentType("text/html;charset=utf-8");
try {
PrintWriter printWriter = response.getWriter();
printWriter.write("妖怪列表
");
} catch (IOException e) {
e.printStackTrace();
}
}
}
annotation\Controller.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
annotation\RequestMapping
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
在该文件指定,我们的 springmvc 要扫描的包
完成功能说明: -编写 XMLParser 工具类, 可以解析 wyxringmvc.xml, 得到要扫描的包-如图
package com.wyxdu.wyxspringmvc.xml;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
/**
* XMLParser 用于解析spring配置文件
*/
public class XMLParser {
public static String getBasePackage(String xmlFile) {
//这个解析的过程,是前面讲过的
SAXReader saxReader = new SAXReader();
//通过得到类的加载路径-》获取到spring配置文件.[对应的资源流]
InputStream inputStream =
XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);
try {
//得到xmlFile文档
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
Element componentScanElement =
rootElement.element("component-scan");
Attribute attribute = componentScanElement.attribute("base-package");
String basePackage = attribute.getText();
return basePackage;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
public class HspSpringMvcTest {
@Test
public void readXML() {
String basePackage = XMLPaser.getbasePackage("wyxspringmvc.xml");
System.out.println("basePackage= " + basePackage);
}
}
完成的功能说明
- 把指定的目录包括子目录下的 java 类的全路径扫描到集合中,比如 ArrayList -如图[对 java 基础知识的使用
classFullPathList=[com.wyxdu.controller.MonsterController, com.wyxedu.service.impl.MonsterServiceImpl, com.wyxedu.service.MonsterService]
public class WyxWebApplicationContext {
private ArrayList<String> classFullPathList = new ArrayList
public void init() {
//这里是写的固定的spring容器配置文件.?=>做活
String basePackage = XMLParser.getBasePackage("wyxspringmvc.xml");
//这时basePackage => com.wyxdu.controller,com.wyxdu.service
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);//扫描
}
}
System.out.println("扫描后的= classFullPathList=" + classFullPathList);
}
public void scanPackage(String pack) {
//得到包所在的工作路径[绝对路径]
//下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
//比如 "com.hspedu.controller" => url 是 D:\hspedu_springmvc\hsp-springmvc\target\hsp-springmvc\WEB-INF\classes\com\hspedu\controller
//细节说明: 1. 不要直接使用Junit测试, 否则 url null
// 2. 启动tomcat来吃测试
URL url =
this.getClass().getClassLoader()
.getResource("/" + pack.replaceAll("\\.", "/"));
System.out.println("urlss=" + url);
//根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path= " + path);
//在io中,把目录,视为一个文件
File dir = new File(path);
//遍历dir[文件/子目录]
for (File f : dir.listFiles()) {
if (f.isDirectory()) {//如果是一个目录,需要递归扫描
scanPackage(pack + "." + f.getName());
} else {
//说明:这时,你扫描到的文件,可能是.class, 也可能是其它文件
//就算是.class, 也存在是不是需要注入到容器
//目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
String classFullPath =
pack + "." + f.getName().replaceAll(".class", "");
classFullPathList.add(classFullPath);
}
}
}
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//创建自己的spring容器
wyxWebApplicationContext =
new WyxWebApplicationContext(configLocation);
wyxWebApplicationContext.init();
}
看看扫描是否成功. 需要使用 Tomcat 启动方式完成测试,直接用 Junit 测试 URL 是null
15:03:30.297 信 息 [RMI TCP Connection(3)-127.0.0.1]
org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet
contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were
scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can
improve startup time and JSP compilation time.
.扫描后
classFullPathList=[com.hspedu.controller.MonsterController, com.wyxdu.service.impl.MonsterServiceImpl, com.wyxdu.service.MonsterService]
public void init() {
String basePackage = XMLParser.getBasePackage("wyxspringmvc.xml");
//这时basePackage => com.wyxdu.controller,com.wyxdu.service
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);//扫描
}
}
System.out.println("扫描后的= classFullPathList=" + classFullPathList);
//将扫描到的类, 反射到ico容器
executeInstance();
System.out.println("扫描后的 ioc容器= " + ioc);
}
public void executeInstance() {
//判断是否扫描到类
if (classFullPathList.size() == 0) {//说明没有扫描到类
return;
}
try {
//遍历classFullPathList,进行反射
for (String classFullPath : classFullPathList) {
Class<?> clazz = Class.forName(classFullPath);
//说明当前这个类有@Controller
if (clazz.isAnnotationPresent(Controller.class)) {
//得到类名首字母小写
String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() +
clazz.getSimpleName().substring(1);
ioc.put(beanName, clazz.newInstance());
} //如果有其它的注解,可以扩展 , 来处理@Service
else if()
}
} catch (Exception e) {
e.printStackTrace();
}
}
(提示: 启动 tomcat 来加载 WyxDispatcherServlet 的方式测试
扫描后的 ioc容器= {goodsController=com.wyxdu.controller.xx.GoodsController@29e91c96,
-如图
handlerList初始化的结果= [WyxHandler{url=‘/order/list’, controller=com.wyxdu.controller.OrderController@79b8c11d, method=public void com.wyxdu.controller.OrderController.listOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
public class WyxHandler {
private String url;
private Object controller;
private Method method;
public WyxHandler(String url, Object controller, Method method) {
this.url = url;
this.controller = controller;
this.method = method;
}
public WyxHandler() {
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
return "WyxHandler{" +
"url='" + url + '\'' +
", controller=" + controller +
", method=" + method +
'}';
}
}
public class WyxDispatcherServlet extends HttpServlet {
//定义属性 handlerList , 保存HspHandler[url和控制器方法的映射]
private List<WyxHandler> handlerList =
new ArrayList<>();
//定义属性 wyxWebApplicationContext,自己的spring容器
WyxWebApplicationContext wyxWebApplicationContext = null;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//创建自己的spring容器
wyxWebApplicationContext =
new WyxWebApplicationContext();
wyxWebApplicationContext.init();
//调用 initHandlerMapping , 完成url和控制器方法的映射
initHandlerMapping();
//输出handlerList
System.out.println("handlerList初始化的结果= " + handlerList);
}
private void initHandlerMapping() {
if(wyxWebApplicationContext.ioc.isEmpty()) {
throw new RuntimeException("spring ioc 容器为空");
}
for(Map.Entry<String,Object> entry:
wyxWebApplicationContext.ioc.entrySet()) {
//先取出注入的Object的clazz对象
Class<?> clazz = entry.getValue().getClass();
//如果注入的Bean是Controller
if(clazz.isAnnotationPresent(Controller.class)) {
//取出它的所有方法
Method[] declaredMethods = clazz.getDeclaredMethods();
//遍历方法
for (Method declaredMethod : declaredMethods) {
//判断该方法是否有@RequestMapping
if(declaredMethod.isAnnotationPresent(RequestMapping.class)) {
//取出@RequestMapping值->就是映射路径
RequestMapping requestMappingAnnotation =
declaredMethod.getAnnotation(RequestMapping.class);
//这里小伙伴可以把工程路径+url
//getServletContext().getContextPath()
// /springmvc/monster/list
String url = requestMappingAnnotation.value();
handlerList.add(new
WyxHandler(url,entry.getValue(),declaredMethod));
}
}
}
}
}
(启动 Tomcat , 加载 HspDispatcherServlet 方式), 测试结果前面已经展示了
当用户发出请求,根据用户请求 url 找到对应的控制器-方法, 并反射调用
- 如果用户请求的路径不存在,返回 404
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//System.out.println("--WyxDispatcherServlet--doPost---");
//调用方法,完成分发请求
executeDispatch(req, resp);
}
private void executeDispatch(HttpServletRequest req,
HttpServletResponse response) {
WyxHandler wyxHandler = getWyxHandler(req);
try {
if (null == wyxHandler) {//没有匹配的 Handler
response.getWriter().print("404 NOT FOUND
");
} else {//有匹配的 Handler, 就调用
wyxHandler.getMethod().invoke(wyxHandler.getController(), req,
response);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private WyxHandler getWyxHandler(HttpServletRequest request) {
//1.先获取的用户请求的uri 比如http://localhost:8080/springmvc/monster/list
// uri = /springmvc/monster/list
//2. 这里要注意得到uri 和 保存url 是有一个工程路径的问题
// 两个方案解决 =>第一个方案: 简单 tomcat 直接配置 application context =>/
// 第二个方案 保存 hsphandler对象 url 拼接 getServletContext().getContextPath()
String requestURI = request.getRequestURI();
//遍历handlerList
for (WyxHandler wyxHandler : handlerList) {
if (requestURI.equals(wyxHandler.getUrl())) {//说明匹配成功
return wyxHandler;
}
}
return null;
}
private void initHandlerMapping() {
if(wyxWebApplicationContext.ioc.isEmpty()) {
throw new RuntimeException("spring ioc 容器为空");
}
for(Map.Entry<String,Object> entry:
wyxWebApplicationContext.ioc.entrySet()) {
//先取出注入的Object的clazz对象
Class<?> clazz = entry.getValue().getClass();
//如果注入的Bean是Controller
if(clazz.isAnnotationPresent(Controller.class)) {
//取出它的所有方法
Method[] declaredMethods = clazz.getDeclaredMethods();
//遍历方法
for (Method declaredMethod : declaredMethods) {
//判断该方法是否有@RequestMapping
if(declaredMethod.isAnnotationPresent(RequestMapping.class)) {
//取出@RequestMapping值->就是映射路径
RequestMapping requestMappingAnnotation =
declaredMethod.getAnnotation(RequestMapping.class);
//这里小伙伴可以把工程路径+url
//getServletContext().getContextPath()
// /springmvc/monster/list
String url = requestMappingAnnotation.value();
handlerList.add(new
WyxHandler(url,entry.getValue(),declaredMethod));
}
}
}
}
}
@Controller
public class OrderController {
@RequestMapping(value = "/order/list")
public void listOrder(HttpServletRequest request,
HttpServletResponse response) {
//设置编码和返回类型
response.setContentType("text/html;charset=utf-8");
//获取writer返回信息
try {
PrintWriter printWriter = response.getWriter();
printWriter.write("订单列表信息
");
} catch (IOException e) {
e.printStackTrace();
}
}
@RequestMapping(value = "/order/add")
public void addOrder(HttpServletRequest request,
HttpServletResponse response) {
//设置编码和返回类型
response.setContentType("text/html;charset=utf-8");
//获取writer返回信息
try {
PrintWriter printWriter = response.getWriter();
printWriter.write("添加订单...
");
} catch (IOException e) {
e.printStackTrace();
}
}
}
本篇完成了任务阶段 2- 完成客户端浏览器可以请求控制层下一篇为实现任务阶段 3- 从 web.xml动态获取 wyxspringmvc.xml
自己实现 SpringMVC 底层机制 核心分发 控制器+ Controller 和 Service 注入容器 + 对象自动装配 + 控制器 方法获取参数 + 视图解析 + 返回 JSON 格式数系列
第一篇->自己实现 SpringMVC 底层机制 系列之搭建 SpringMVC 底层机制开发环境和开发 WyxDispatcherServlet_springmvc分发器
热门专栏推荐
想学习vue的可以看看这个
java基础合集
数据库合集
redis合集
nginx合集
linux合集
等等等还有许多优秀的合集在主页等着大家的光顾感谢大家的支持
欢迎大家加入我的社区 尘觉社区
文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论
希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力