重新认识SpringMVC框架
1. Spring框架图
2. Spring框架
- Spring 核心功能
- DI(Dependency Injection, 依赖注入)
- AOP(Aspect Oriented Programming, 面向切面编程)
- 框架优点
- 轻量级,没有侵入性,默认单例;
- 使用 IoC 更加容易组合对象之间的关系;
- 面向接口编程,降低耦合;
- AOP 可以更加容易的进行功能扩展;
3. 如何理解SpringMVC
(1)MVC是什么?
MVC 是模型(model)、视图(view)、控制器(controller)的缩写,它是一个设计模式。
(2)Spring是什么?
面向接口的编程思想,解决的是业务逻辑层和其他各层的松耦合问题。(可简单理解为IOC + AOP)
4. SpringMVC运行流程
手写SpringMVC理论基础
思路整理如下
- 1、读取配置
- 2、初始化
- 加载配置文件
- 扫描用户配置包下的类
- 通过反射机制实例化包下的类,并且放到 IOC 容器中
- 初始化 HandlerMapping
- 3、运行
- 异常拦截
- 获取请求传入的参数并处理参数
- 通过初始化完的 handlerMapping 中拿出 url 对应的方法名,反射调用
代码实现
1. web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>StudyMVC</servlet-name>
<servlet-class>com.ming.mvc.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>StudyMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2. pom文件引入servlet
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
3. 注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
@Target(value = {ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value();
}
4. MyDispatcherServlet
package com.ming.mvc.servlet;
import com.ming.mvc.annotation.Controller;
import com.ming.mvc.annotation.RequestMapping;
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.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public class MyDispatcherServlet extends HttpServlet {
private Properties properties = new Properties();
private List<String> classNames = new ArrayList<String>();
private Map<String, Object> ioc = new HashMap<String, Object>();
private Map<String, Method> handlerMapping = new HashMap<String, Method>();
private Map<String, Object> controllerMap = new HashMap<String, Object>();
@Override
public void init(ServletConfig config) throws ServletException {
// 1、加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation").replaceAll("classpath\\*:", ""));
// 2、扫描的类
doScanner(properties.getProperty("scanPackage"));
// 3、拿到扫描的类后,通过反射机制实例化,并放到ioc容器中
doInstance();
// 4、初始化handlerMapping(url{login} -> method{login()})
initHandlerMapping();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("欢迎使用手写的SpringMVC框架");
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
resp.getWriter().write("500!! Server Exception");
}
}
private void doLoadConfig(String location) {
System.out.println("这是调用了doLoadConfig方法");
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void doScanner(String packageName) {
System.out.println("这是调用了doScanner方法");
URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
doScanner(packageName + "." + file.getName());
} else {
String className = packageName + "." + file.getName().replace(".class", "");
classNames.add(className);
System.out.println("Spring容器扫描到类有:" + packageName + "." + file.getName());
}
}
}
private void doInstance() {
System.out.println("这是调用了doInstance方法");
for (String className : classNames) {
Class<?> clazz = null;
try {
clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Controller.class)) {
ioc.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
} else {
continue;
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
private void initHandlerMapping(){
System.out.println("这是调用了initHandlerMapping方法");
try {
for (Map.Entry entry : ioc.entrySet()){
Class<?> clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(Controller.class)){
continue;
}
String baseUrl = "/spring-mvc-demo/";
if (clazz.isAnnotationPresent(RequestMapping.class)){
RequestMapping annotation = clazz.getAnnotation(RequestMapping.class);
baseUrl = baseUrl + annotation.value();
}
Method[] methods = clazz.getMethods();
for (Method method : methods){
if (!method.isAnnotationPresent(RequestMapping.class)){
continue;
}
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
String url = annotation.value();
url = (baseUrl + "/" + url).replaceAll("/+", "/"); // 解析路径有有多个"//"转换为"/"
handlerMapping.put(url, method);
controllerMap.put(url, clazz.newInstance());
System.out.println(url + "," + method);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
if (handlerMapping.isEmpty()){
return;
}
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url.replace(contextPath, "").replaceAll("/+","/");
if (!this.handlerMapping.containsKey(url)){
resp.getWriter().write("404 NOT FOUND!");
return;
}
Method method = this.handlerMapping.get(url);
// 获取方法的参数列表
Class<?>[] parameterTypes = method.getParameterTypes();
// 获取请求的参数
Map<String, String[]> parameterMap = req.getParameterMap();
Object[] paramValues = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
// 根据参数名称,做某些处理
String requestParam = parameterTypes[i].getSimpleName();
if (requestParam.equals("HttpServletRequest")){
paramValues[i] = req;
continue;
}
if (requestParam.equals("HttpServletResponse")){
paramValues[i] = resp;
continue;
}
if (requestParam.equals("String")){
for (Map.Entry<String, String[]> param : parameterMap.entrySet()){
String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
paramValues[i] = value;
}
}
}
try {
method.invoke(this.controllerMap.get(url), paramValues);
} catch (Exception e){
e.printStackTrace();
}
}
private String toLowerFirstWord(String name){
char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
}
5. application.properties
scanPackege=com.xquant.mvc
6. Controller测试
package com.ming.mvc.controller;
import com.ming.mvc.annotation.Controller;
import com.ming.mvc.annotation.RequestMapping;
import com.ming.mvc.annotation.RequestParam;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Controller
@RequestMapping("/study")
public class StudyController {
@RequestMapping("/doTest")
public void test1(HttpServletRequest request, HttpServletResponse response,
@RequestParam("param") String param) {
System.out.println(param);
try {
response.getWriter().write("doTest method success! param:" + param);
} catch (IOException e) {
e.printStackTrace();
}
}
}
7. 测试
"C:\Program Files\Java\jdk1.8.0_151\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:62439,suspend=y,server=n -Dmaven.multiModuleProjectDirectory=F:\dev_code\springboot\spring-mvc-demo -DWORKING_PATH=F:\dev_code\springboot\spring-mvc-demo\src\main "-Dmaven.home=C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\plugins\maven\lib\maven3" "-Dclassworlds.conf=C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\plugins\maven\lib\maven3\bin\m2.conf" "-Dmaven.ext.class.path=C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\plugins\maven\lib\maven-event-listener.jar" -javaagent:C:\Users\1\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\plugins\maven\lib\maven3\boot\plexus-classworlds-2.6.0.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar" org.codehaus.classworlds.Launcher -Didea.version2019.3.1 -s E:\apache-maven-3.0.4\conf\settings.xml jetty:run
Connected to the target VM, address: '127.0.0.1:62439', transport: 'socket'
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< org.example:spring-mvc-demo >---------------------
[INFO] Building spring-mvc-demo 1.0-SNAPSHOT
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] >>> jetty-maven-plugin:7.6.15.v20140411:run (default-cli) > test-compile @ spring-mvc-demo >>>
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ spring-mvc-demo ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ spring-mvc-demo ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ spring-mvc-demo ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory F:\dev_code\springboot\spring-mvc-demo\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ spring-mvc-demo ---
[INFO] No sources to compile
[INFO]
[INFO] <<< jetty-maven-plugin:7.6.15.v20140411:run (default-cli) < test-compile @ spring-mvc-demo <<<
[INFO]
[INFO]
[INFO] --- jetty-maven-plugin:7.6.15.v20140411:run (default-cli) @ spring-mvc-demo ---
[INFO] Configuring Jetty for project: spring-mvc-demo
[INFO] webAppSourceDirectory not set. Defaulting to F:\dev_code\springboot\spring-mvc-demo\src\main\webapp
[INFO] Reload Mechanic: automatic
[INFO] Classes = F:\dev_code\springboot\spring-mvc-demo\target\classes
[INFO] Context path = /spring-mvc-demo
[INFO] Tmp directory = F:\dev_code\springboot\spring-mvc-demo\target\tmp
[INFO] Web defaults = org/eclipse/jetty/webapp/webdefault.xml
[INFO] Web overrides = none
[INFO] web.xml file = file:/F:/dev_code/springboot/spring-mvc-demo/src/main/webapp/WEB-INF/web.xml
[INFO] Webapp directory = F:\dev_code\springboot\spring-mvc-demo\src\main\webapp
[INFO] jetty-7.6.15.v20140411
[INFO] No Transaction manager found - if your webapp requires one, please configure one.
[INFO] started o.m.j.p.JettyWebAppContext{/spring-mvc-demo,file:/F:/dev_code/springboot/spring-mvc-demo/src/main/webapp/},file:/F:/dev_code/springboot/spring-mvc-demo/src/main/webapp/
这是调用了doLoadConfig方法
这是调用了doScanner方法
这是调用了doScanner方法
Spring容器扫描到类有:com.ming.mvc.annotation.Controller.class
Spring容器扫描到类有:com.ming.mvc.annotation.RequestMapping.class
Spring容器扫描到类有:com.ming.mvc.annotation.RequestParam.class
这是调用了doScanner方法
Spring容器扫描到类有:com.ming.mvc.controller.HelloController.class
Spring容器扫描到类有:com.ming.mvc.controller.StudyController.class
这是调用了doScanner方法
Spring容器扫描到类有:com.ming.mvc.servlet.DispatcherServlet.class
这是调用了doInstance方法
这是调用了initHandlerMapping方法
/spring-mvc-demo/study/doTest,public void com.ming.mvc.controller.StudyController.test1(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.lang.String)
/spring-mvc-demo/study/login,public void com.ming.mvc.controller.StudyController.login(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.lang.String,java.lang.String) throws javax.servlet.ServletException,java.io.IOException
/spring-mvc-demo/,public void com.ming.mvc.controller.HelloController.hello(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws javax.servlet.ServletException,java.io.IOException
[WARNING] !RequestLog
[INFO] Started SocketConnector@0.0.0.0:8001
[INFO] Started Jetty Server
欢迎使用手写的SpringMVC框架