什么是Spring MVC?
Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc ),但它通常被称为“Spring MVC”。
手写Spring MVC(一)
创建项目
创建父工程 [ 选择9号模板 :site-simple],命名为shop(以商城为例子)
创建子工程 [ 选择10号模板:webapp],命名为shop-web
创建子工程 [ 选择7号模板:quickstart],命名为shop-mvc
依赖准备
servlet-api
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
编译插件
<build>
<finalName>shop_web</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (maybe moved to parent pom) -->
<plugins>
<!--这个插件就是java类生成class的编译插件-->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!--编译参数-->
<compilerArgument>-parameters</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
创建文件
-
在shop-web工程下创建以下目录
-
在mvc工程下创建以下目录
文件详解
mvc工程
-
cn.cnmd.shop.mvc.annotation:主要是注解,包括控制器、路由映射、配置
- cn.cnmd.shop.mvc.annotation.Controller
package cn.cnmd.shop.mvc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Controller { }
- cn.cnmd.shop.mvc.annotation.RequestMapping
package cn.cnmd.shop.mvc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RequestMapping { String value(); }
- cn.cnmd.shop.mvc.annotation.Configuration
package cn.cnmd.shop.mvc.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Configuration { String value(); }
-
cn.cnmd.shop.mvc.canstant:包含了常见的错误码和错误信息
-
cn.cnmd.shop.mvc.canstant.ResponseCodeInterface
package cn.cnmd.shop.mvc.constant; public interface ResponseCodeInterface { int getCode(); String getMessage(); void setCode(int code); void setMessage(String message); }
-
cn.cnmd.shop.mvc.canstant.ResponseCode
package cn.cnmd.shop.mvc.constant; public enum ResponseCode implements ResponseCodeInterface { CONFIG_EXCEPTION(100, "config的配置信息出错"), CONFIGURATION_EXCEPTION(101, "需要配置Configuration这个注解"), CLASS_FILE_EXCEPTION(102, "class文件转换异常"), REQUEST_MAPPING_PATH_EXCEPTION(103, "RequestMapping地址设置有误"), REQUEST_PATH_EXCEPTION(104, "uri映射错误"), EXCEPTION_CONFIG_EXCEPTION(105, "未配置全局异常的路径"), ADVISER_CONFIG_EXCEPTION(106, "未配置处理器的路径"); private int code; private String message; ResponseCode(int code, String message) { this.code = code; this.message = message; } @Override public int getCode() { return code; } @Override public String getMessage() { return message; } @Override public void setCode(int code) { this.code = code; } @Override public void setMessage(String message) { this.message = message; } }
-
-
cn.cnmd.shop.mvc.container:容器,用于存储项目启动之后创建的BeanDefinition对象
-
cn.cnmd.shop.mvc.container.BeanContainer
package cn.cnmd.shop.mvc.container; import cn.cnmd.shop.mvc.model.BeanDefinition; import lombok.Getter; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class BeanContainer { @Getter private static Map<String, BeanDefinition<?>> maps = null; static { maps = new ConcurrentHashMap<>(); } }
-
-
cn.cnmd.shop.mvc.exception:异常类,用于处理异常
-
cn.cnmd.shop.mvc.exception.FrameWorkException
package cn.cnmd.shop.mvc.exception; import lombok.AllArgsConstructor; import lombok.Data; @AllArgsConstructor @Data public class FrameWorkException extends RuntimeException { private int code; private String message; }
-
-
cn.cnmd.shop.mvc.listener:监听器,主要作用是在项目启动时扫描controller下的文件生成BeanDefinition对象
-
cn.cnmd.shop.mvc.listener.ApplicationListener
package cn.cnmd.shop.mvc.listener; import cn.cnmd.shop.mvc.annotation.Configuration; import cn.cnmd.shop.mvc.annotation.RequestMapping; import cn.cnmd.shop.mvc.constant.ResponseCode; import cn.cnmd.shop.mvc.container.BeanContainer; import cn.cnmd.shop.mvc.exception.FrameWorkException; import cn.cnmd.shop.mvc.model.BeanDefinition; import cn.cnmd.shop.mvc.model.MethodDefinition; import cn.cnmd.shop.mvc.model.ParameterDefinition; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import java.io.File; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.List; import java.util.Map; public class ApplicationListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { /* * 扫描web项目的Controller层 * 封装成类描述类的对象 * 添加到容器中 -- Key(父级uri+子级uri) Value(类描述类的对象) */ ServletContext servletContext = sce.getServletContext(); String config = servletContext.getInitParameter("config");//cn.cnmd.config.AppConfig if (config == null) { throw new FrameWorkException(ResponseCode.CONFIG_EXCEPTION.getCode(), ResponseCode.CONFIG_EXCEPTION.getMessage()); } //获取配置类的class对象 Class<?> configurationClass = getConfiguration(config); //通过配置类的class对象拿到 AppConfig注解 中的cn.cnmd.controller String controllerPosition = getControllerPosition(configurationClass); //D:\Desktop\code\apache-tomcat-8.0.49\webapps\ROOT\WEB-INF\classes\cn\cnmd\controller String controllerAbsolutePath = getControllerAbsolutePath(servletContext, controllerPosition); //获取controller文件夹下所有的文件 List<File> fileList = new ArrayList<>(); findFileByPath(fileList, controllerAbsolutePath); //文件对象集合 --> class对象集合 List<Class<?>> classes = transformTo(servletContext, fileList); //封装成类描述类的对象: BeanDefinition -> MethodDefinition -> ParameterDefinition handleController(classes); } /** * 通过web.xml的配置 * <context-param> * <param-name>config</param-name> * <param-value>cn.cnmd.config.AppConfig</param-value> * </context-param> * 参数 [config] 获取的 [cn.cnmd.config.AppConfig] 获取配置文件类的class对象 * * @param config [cn.cnmd.config.AppConfig] * @return class对象 */ public Class<?> getConfiguration(String config) { try { return Class.forName(config); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } /** * 通过配置文件类的class对象获取注解中的controller的类的全限定名 [cn.cnmd.controller] * * @param configurationClass 配置文件类的class对象 * @return controller的类的全限定名 */ public String getControllerPosition(Class<?> configurationClass) { Configuration annotation = configurationClass.getAnnotation(Configuration.class); if (annotation == null) { throw new FrameWorkException(ResponseCode.CONFIGURATION_EXCEPTION.getCode(), ResponseCode.CONFIGURATION_EXCEPTION.getMessage()); } return annotation.value(); } /** * 通过配置文件类 [AppConfig] 的注解信息 [cn.cnmd.controller] 获取controller包的发布路径 * * @param servletContext servlet上下文对象 * @param controllerPosition 配置文件类 [AppConfig] 的注解信息 [cn.cnmd.controller] * @return controller包的发布路径 */ public String getControllerAbsolutePath(ServletContext servletContext, String controllerPosition) { //D:\Desktop\code\apache-tomcat-8.0.49\webapps\ROOT\WEB-INF\classes String absolutePath = servletContext.getRealPath("WEB-INF" + File.separator + "classes"); controllerPosition = controllerPosition.replace(".", File.separator); return absolutePath + File.separator + controllerPosition; } /** * 通过controller包路径找到路径下所有文件对象 * * @param fileList 文件对象集合 * @param controllerAbsolutePath controller包的发布路径 */ public void findFileByPath(List<File> fileList, String controllerAbsolutePath) { File file = new File(controllerAbsolutePath); File[] files = file.listFiles(); if (files != null) { for (File f : files) { if (f.isDirectory()) { findFileByPath(fileList, f.getAbsolutePath()); } else if (f.isFile()) { fileList.add(f); } } } } public List<Class<?>> transformTo(ServletContext servletContext, List<File> fileList) { List<Class<?>> classes = new ArrayList<>(); for (File file : fileList) { //D:\Desktop\code\apache-tomcat-8.0.49\webapps\ROOT\WEB-INF\classes\cn\cnmd\controller\UserController.class String classPath = file.getAbsolutePath(); String absolutePath = servletContext.getRealPath("WEB-INF" + File.separator + "classes"); //cn\cnmd\controller\UserController.class --> cn.cnmd.controller.UserController classPath = classPath.substring(absolutePath.length() + 1).split("\\.")[0].replace("\\", "."); System.out.println(classPath); Class<?> clazz = null; try { clazz = Class.forName(classPath); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } classes.add(clazz); } return classes; } public void handleController(List<Class<?>> classes) { Map<String, BeanDefinition<?>> maps = BeanContainer.getMaps(); for (Class<?> clazz : classes) { try { //父级url requestMappingPath --> requestMapping.value() RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); String requestMappingPath1 = requestMapping.value(); //类名 beanName --> clazz.getName() String beanName = clazz.getName(); //controller的类对象 t --> clazz.newInstance() Object t = clazz.newInstance(); //方法描述对象 Method[] methods = clazz.getMethods(); for (Method method : methods) { method.setAccessible(true); if (!method.isAnnotationPresent(RequestMapping.class)) { continue; } RequestMapping requestMapping1 = method.getAnnotation(RequestMapping.class); //子级url requestMappingPath --> requestMapping1.value() String requestMappingPath2 = requestMapping1.value(); //方法名 methodName --> method.getName() String methodName = method.getName(); //方法对象 method --> method //返回值类型 returnType --> method.getReturnType() Class<?> returnType = method.getReturnType(); //参数描述对象 Parameter[] parameters = method.getParameters(); List<ParameterDefinition> parameterDefinitions = new ArrayList<>(); for (int i = 0; i < parameters.length; i++) { Class<? extends Parameter> paramClass = parameters[i].getClass(); //参数名 parameterName --> paramClass.getName() String parameterName = paramClass.getName(); //参数类型 type --> paramClass //参数下标 index --> i ParameterDefinition parameterDefinition = new ParameterDefinition(parameterName, paramClass, i); parameterDefinitions.add(parameterDefinition); } MethodDefinition methodDefinition = new MethodDefinition(requestMappingPath2, methodName, method, returnType, parameterDefinitions); BeanDefinition<?> beanDefinition = new BeanDefinition<>(requestMappingPath1, beanName, clazz, t, methodDefinition); String route = requestMappingPath1 + File.separator + requestMappingPath2; maps.put(route, beanDefinition); } } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } } }
-
-
cn.cnmd.shop.mvc.model:类描述类,包括参数描述类、方法描述类、类描述类
-
cn.cnmd.shop.mvc.model.ParameterDefinition
package cn.cnmd.shop.mvc.model; import com.sun.istack.internal.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * 参数描述类 */ @NoArgsConstructor @AllArgsConstructor @Data public class ParameterDefinition { private String name;//参数名 private Class<?> type;//参数类型 private int index;//参数下标 }
-
cn.cnmd.shop.mvc.model.MethodDefinition
package cn.cnmd.shop.mvc.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.lang.reflect.Method; import java.util.List; @NoArgsConstructor @AllArgsConstructor @Data public class MethodDefinition { private String requestMappingPath;//子级url private String methodName;//方法名 private Method method;//方法对象 private Class<?> returnType;//返回值类型 private List<ParameterDefinition> parameters;//参数描述列表 }
-
cn.cnmd.shop.mvc.model.BeanDefinition
package cn.cnmd.shop.mvc.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @NoArgsConstructor @AllArgsConstructor @Data public class BeanDefinition<T> { private String requestMappingPath;//父级url private String name;//类名 private Class<?> beanClass;//controller类的class对象 private T t;//controller类对象 private MethodDefinition methodDefinition;//方法描述对象 }
-
web工程
-
cn.cnmd.config
-
cn.cnmd.config.AppConfig
package cn.cnmd.config; import cn.cnmd.shop.mvc.annotation.Configuration; @Configuration("cn.cnmd.controller") public class AppConfig { }
-
-
cn.cnmd.controller:存放各种controller
-
cn.cnmd.controller.UserController
package cn.cnmd.controller; import cn.cnmd.pojo.User; import cn.cnmd.shop.mvc.annotation.Controller; import cn.cnmd.shop.mvc.annotation.RequestMapping; @Controller @RequestMapping("user") public class UserController { @RequestMapping("login1") public void login(String username, String password) { } @RequestMapping("login2") public void login(User user) { } public void method(String name){} }
-
cn.cnmd.controller.back.AdminController
package cn.cnmd.controller.back; import cn.cnmd.shop.mvc.annotation.Controller; import cn.cnmd.shop.mvc.annotation.RequestMapping; @Controller @RequestMapping("admin") public class AdminController { @RequestMapping("login") public void login(String name, String password){ } }
-
-
cn.cnmd.pojo:用于存放web项目中的JavaBean对象
-
cn.cnmd.pojo.User
package cn.cnmd.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @NoArgsConstructor @AllArgsConstructor @Data public class User { String name; int age; String sex; }
-
ApplicationContextListener扫描过程(个人理解)
roller
@RequestMapping(“admin”)
public class AdminController {
@RequestMapping("login")
public void login(String name, String password){
}
}
```
-
cn.cnmd.pojo:用于存放web项目中的JavaBean对象
-
cn.cnmd.pojo.User
package cn.cnmd.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @NoArgsConstructor @AllArgsConstructor @Data public class User { String name; int age; String sex; }
-
ApplicationContextListener扫描过程(个人理解)
[外链图片转存中…(img-wN91i1Ja-1719492618962)]