实现SpringMVC底层机制
搭建开发环境
1.创建Maven项目



2.配置 pom.xml
将1.7改成1.8
<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>
3.创建main和test相关的原码目录和资源目录和测试目录
4.引入需要的基本jar包
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--引入原生servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!--
1. scope标签表示引入的jar的作用范围
2. provided:表示该项目在打包放到生产环境的时候,不需要带上servlet-api.jar
3. 因为tomcat本身有servlet的jar,到时直接使用tomcat本身的servlet-api.jar,防止版本冲突
-->
<scope>provided</scope>
</dependency>
<!--引入dom4j,解析xml-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--引入常用工具类的jar,包含很多常用类的jar-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
阶段1-开发LzwDispatherServlet
说明
编写 LzwDispatcherServlet 充当原生的 DispatcherServlet(即核心控制器)

代码实现
创建 LzwDispatcherServlet.java
package com.lzw.lzwspringmvc.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author LiAng
* 1. LzwDispatcherServlet 充当原生DispatcherServlet
* 2. 本质是一个Servlet,继承HttpServlet
* 3. 提示:需要使用到 JavaWeb中的servlet
*/
public class LzwDispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
创建 lzwspringmvc.xml

修改 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>
<display-name>Archetype Created Web Application</display-name>
<!--配置LzwDispatcherServlet,作为自己的前端控制器-->
<servlet>
<servlet-name>LzwDispatcherServlet</servlet-name>
<servlet-class>com.lzw.lzwspringmvc.servlet.LzwDispatcherServlet</servlet-class>
<!--给LzwDispatcherServlet配置参数,指定要操作的spring容器配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:lzwspringmvc.xml</param-value>
</init-param>
<!--tomcat启动时,自动加载LzwDispatcherServlet-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>LzwDispatcherServlet</servlet-name>
<!--因为LzwDispatcherServlet作为前端控制器,所以需要拦截所有请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置Tomcat完成测试
1.配置Tomcat
可以参考第一章配置。
2.修改 LzwDispatcherServlet.java
public class LzwDispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("--LzwDispatcherServlet--doGet()--");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("--LzwDispatcherServlet--doPost()--");
}
}
3.启动tomcat
4.完成测试
http://localhost:8080/lzw_springmvc/abc

阶段2-完成客户端/浏览器可以请求控制层

1.创建 自己的 Controller 和自定义注解
创建 @Controller.java
package com.lzw.lzwspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author LiAng
* 注解用于标识一个控制器组件
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
创建 @RequestMapping.java
package com.lzw.lzwspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author LiAng
* RequestMapping 注解用于指定控制器-方法的映射路径
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
创建 MonsterController.java
package com.lzw.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author LiAng
*/
public class MonsterController {
//编写方法,可以列出妖怪列表
//springmvc 是支持原生的servlet api,为了看到底层级制
//这里设计两个参数
@RequestMapping(value = "/list/monster")
public void listMonster(HttpServletRequest request, HttpServletResponse response){
//设置编码和返回类型
response.setContentType("text/html;charset=utf-8");
try {
//获取writer返回信息
PrintWriter printWriter = response.getWriter();
printWriter.write("<h1>妖怪列表信息</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 配置 lzwspringmvc.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<!--指定要扫描的基本包-->
<component-scan base-package="com.lzw.controller"></component-scan>
</beans>
package com.lzw.controller;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author LiAng
*/
@Controller
public class MonsterController {
//编写方法,可以列出妖怪列表
//springmvc 是支持原生的servlet api,为了看到底层级制
//这里设计两个参数
@RequestMapping(value = "/list/monster")
public void listMonster(HttpServletRequest request, HttpServletResponse response){
//设置编码和返回类型
response.setContentType("text/html;charset=utf-8");
try {
//获取writer返回信息
PrintWriter printWriter = response.getWriter();
printWriter.write("<h1>妖怪列表信息</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.编写 XMLParser 工具类
可以解析 lzwspringmvc.xml
package com.lzw.lzwspringmvc.xml;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
/**
* @author LiAng
* 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 "";
}
}
创建 lzw-spring\src\test\java\com\lzw\test\LzwSpringMVCTest.java
完成测试
package com.lzw.test;
import com.lzw.lzwspringmvc.xml.XMLParser;
import org.junit.Test;
/**
* @author LiAng
*/
public class LzwSpringMVCTest {
@Test
public void readXML(){
String basePackage = XMLParser.getBasePackage("lzwspringmvc.xml");
System.out.println(basePackage);
}
}

4.开发 LzwWebApplicationContext.java
充当 Spring 容器-得到扫描类的全路径列表
把指定的目录包括子目录下的 java 类的全路径扫描到集合中,比如 ArrayList

创建 LzwWebApplicationContext.java
package com.lzw.lzwspringmvc.context;
import com.lzw.lzwspringmvc.xml.XMLParser;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* @author LiAng
* LzwWebApplicationContext 表示我们自己的spring容器
*/
public class LzwWebApplicationContext {
//定义属性 classFullPathList,保存扫描包/子包的类的全路径
private List<String> classFullPathList = new ArrayList<>();
//编写方法,完成自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage("lzwspringmvc.xml");
scanPackage(basePackage);
}
//创建方法,完成对包的扫描-> IO/容器/字符串
/**
*
* @param pack 要扫描的包,比如 "com.lzw.controller"
*/
public void scanPackage(String pack){
//得到包所在的工作路径[绝对路径]
//下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
//比如 "com.lzw.controller" => url 是 E:/VIP/AllDemo/SpringMVC/lzw-springmvc/target/lzw-springmvc/WEB-INF/classes/com/lzw/controller/
//细节说明: 1. 不要直接使用Junit测试, 否则 url = null
// 2. 启动tomcat来测试
URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.","/"));
System.out.println("url = " + url);
}
}
修改 LzwDispatcherServlet.java
package com.lzw.lzwspringmvc.servlet;
import com.lzw.lzwspringmvc.context.LzwWebApplicationContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author LiAng
* 1. LzwDispatcherServlet 充当原生DispatcherServlet
* 2. 本质是一个Servlet,继承HttpServlet
* 3. 提示:需要使用到 JavaWeb中的servlet
*/
public class LzwDispatcherServlet extends HttpServlet {
@Override
public void init() throws ServletException {
LzwWebApplicationContext lzwWebApplicationContext = new LzwWebApplicationContext();
lzwWebApplicationContext.init();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("--LzwDispatcherServlet--doGet()--");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("--LzwDispatcherServlet--doPost()--");
}
}
启动Tomcat完成测试

5.完善 LzwWebApplicationContext
充当 Spring 容器-实例化对象到容器中
修改 LzwWebApplicationContext.java
package com.lzw.lzwspringmvc.context;
import com.lzw.lzwspringmvc.xml.XMLParser;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* @author LiAng
* LzwWebApplicationContext 表示我们自己的spring容器
*/
public class LzwWebApplicationContext {
//定义属性 classFullPathList,保存扫描包/子包的类的全路径
private List<String> classFullPathList = new ArrayList<>();
//编写方法,完成自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage("lzwspringmvc.xml");
scanPackage(basePackage);
System.out.println("classFullPathList = " + classFullPathList);
}
//创建方法,完成对包的扫描-> IO/容器/字符串
/**
*
* @param pack 要扫描的包,比如 "com.lzw.controller"
*/
public void scanPackage(String pack){
//得到包所在的工作路径[绝对路径]
//下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
//比如 "com.lzw.controller" => url 是 E:/VIP/AllDemo/SpringMVC/lzw-springmvc/target/lzw-springmvc/WEB-INF/classes/com/lzw/controller/
//细节说明: 1. 不要直接使用Junit测试, 否则 url = null
// 2. 启动tomcat来测试
URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.","/"));
//System.out.println("url = " + url);
//根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path= " + path);
//在io中,把目录,视为一个文件
File dir = new File(path);
for (File f : dir.listFiles()) {
if(f.isDirectory()){//如果是一个目录,需要递归扫描
scanPackage(pack + "." + f.getName());
}else{
//这时,你扫描到的文件,可能是.class, 也可能是其它文件
//就算是.class, 也存在是不是需要注入到容器
//目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
String classFullName = pack + "." + f.getName().replaceAll(".class","");
classFullPathList.add(classFullName);
}
}
}
}
再创建两个Controller测试

完成测试

修改 lzwspringmvc.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<!--指定要扫描的基本包-->
<component-scan base-package="com.lzw.controller,com.lzw.service"></component-scan>
</beans>
创建 MonsterService.java和 MonsterServiceImpl.java
package com.lzw.service;
/**
* @author LiAng
*/
public interface MonsterService {
}
package com.lzw.service.impl;
import com.lzw.service.MonsterService;
/**
* @author LiAng
*/
public class MonsterServiceImpl implements MonsterService {
}
修改 LzwWebApplicationContext.java
//编写方法,完成自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage("lzwspringmvc.xml");
String[] basePackages = basePackage.split(",");
if(basePackages.length > 0){
for (String pack : basePackages) {
scanPackage(pack);
}
}
System.out.println("扫描后的classFullPathList = " + classFullPathList);
}
进行测试

完善 LzwWebApplicationContext
将扫描到的类,在满足条件的情况下(即有相应的注解@Controller @Service...),反射注入到 ioc 容器

修改 LzwWebApplicationContext.java
package com.lzw.lzwspringmvc.context;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.xml.XMLParser;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author LiAng
* LzwWebApplicationContext 表示我们自己的spring容器
*/
public class LzwWebApplicationContext {
//定义属性 classFullPathList,保存扫描包/子包的类的全路径
private List<String> classFullPathList = new ArrayList<>();
//定义属性ioc, 存放反射生成的Bean对象 /Controller/Service
public ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();
//编写方法,完成自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage("lzwspringmvc.xml");
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);
}
}
System.out.println("扫描后的classFullPathList = " + classFullPathList);
}
//创建方法,完成对包的扫描-> IO/容器/字符串
/**
*
* @param pack 要扫描的包,比如 "com.lzw.controller"
*/
public void scanPackage(String pack){
//得到包所在的工作路径[绝对路径]
//下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
//比如 "com.lzw.controller" => url 是 E:/VIP/AllDemo/SpringMVC/lzw-springmvc/target/lzw-springmvc/WEB-INF/classes/com/lzw/controller/
//细节说明: 1. 不要直接使用Junit测试, 否则 url = null
// 2. 启动tomcat来测试
URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.","/"));
//System.out.println("url = " + url);
//根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path= " + path);
//在io中,把目录,视为一个文件
File dir = new File(path);
for (File f : dir.listFiles()) {
if(f.isDirectory()){//如果是一个目录,需要递归扫描
scanPackage(pack + "." + f.getName());
}else{
//这时,你扫描到的文件,可能是.class, 也可能是其它文件
//就算是.class, 也存在是不是需要注入到容器
//目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
String classFullName = pack + "." + f.getName().replaceAll(".class","");
classFullPathList.add(classFullName);
}
}
}
//编写方法,将扫描到的类, 在满足条件的情况下,反射到ioc容器
public void executeInstance() {
//判断是否扫描到类
if(classFullPathList.size() == 0){//说明没有扫描到类
return;
}
try {
//遍历classFullPathList,进行反射
for (String classFullPath : classFullPathList) {
Class<?> clazz = Class.forName(classFullPath);
if(clazz.isAnnotationPresent(Controller.class)){
//得到类名首字母小写
String beanName = clazz.getSimpleName().substring(0,1).toLowerCase() + clazz.getSimpleName().substring(1);
ioc.put(beanName ,clazz.newInstance());
}//如果有其它的注解,可以扩展 , 来处理@Service
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
完成测试

6.完成请求 URL 和控制器方法的映射关系
将配置的@RequestMapping 的 url 和 对应的 控制器-方法 映射关系保存到集合中

创建 LzwHandler.java
package com.lzw.lzwspringmvc.handler;
import java.lang.reflect.Method;
/**
* @author LiAng
* LzwHandler 用于保存请求的url和控制器方法的映射关系
*/
public class LzwHandler {
private String url;
private Object controller;
private Method method;
public LzwHandler(String url, Object controller, Method method) {
this.url = url;
this.controller = controller;
this.method = method;
}
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 "LzwHandler{" +
"url='" + url + '\'' +
", controller=" + controller +
", method=" + method +
'}';
}
}
修改 LzwDispatcherServlet.java
package com.lzw.lzwspringmvc.servlet;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.annotation.RequestMapping;
import com.lzw.lzwspringmvc.context.LzwWebApplicationContext;
import com.lzw.lzwspringmvc.handler.LzwHandler;
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.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author LiAng
* @time 2022/6/22 10:10
*/
/**
* 1. LzwDispatcherServlet 充当原生DispatcherServlet
* 2. 本质是一个Servlet,继承HttpServlet
* 3. 提示:需要使用到 JavaWeb中的servlet
*/
public class LzwDispatcherServlet extends HttpServlet {
//定义属性 handlerList,保存LzwHandler[url和控制器方法的映射]
private List<LzwHandler> handlerList = new ArrayList<>();
//定义属性 lzwWebApplicationContext,自己的spring容器
LzwWebApplicationContext lzwWebApplicationContext = null;
@Override
public void init() throws ServletException {
lzwWebApplicationContext = new LzwWebApplicationContext();
lzwWebApplicationContext.init();
//调用 initHandlerMapping(),完成url和控制器方法的映射
initHandlerMapping();
//输出 handlerList
System.out.println("handlerList初始化的结果 = " + handlerList);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("--LzwDispatcherServlet--doGet()--");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("--LzwDispatcherServlet--doPost()--");
}
//编写方法,完成url和控制器方法的映射
private void initHandlerMapping(){
if(lzwWebApplicationContext.ioc.isEmpty()){
//判断ioc容器是否为空
return;
}
//遍历ioc容器的bean对象,然后进行url映射处理
for (Map.Entry<String, Object> entry : lzwWebApplicationContext.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);
String url = requestMappingAnnotation.value();
//创建LzwHandler对象->就是一个映射关系
LzwHandler lzwHandler = new LzwHandler(url, entry.getValue(), declaredMethod);
//放入到handlerList
handlerList.add(lzwHandler)
}
}
}
}
}
}
完成测试

7.完成 LzwDispatcherServlet 分发请求到对应控制器方法
当用户发出请求,根据用户请求 url 找到对应的控制器-方法,并反射调用

修改 LzwDispatcherServlet.java
package com.lzw.lzwspringmvc.servlet;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.annotation.RequestMapping;
import com.lzw.lzwspringmvc.context.LzwWebApplicationContext;
import com.lzw.lzwspringmvc.handler.LzwHandler;
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.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author LiAng
* 1. LzwDispatcherServlet 充当原生DispatcherServlet
* 2. 本质是一个Servlet,继承HttpServlet
* 3. 提示:需要使用到 JavaWeb中的servlet
*/
public class LzwDispatcherServlet extends HttpServlet {
//定义属性 handlerList,保存LzwHandler[url和控制器方法的映射]
private List<LzwHandler> handlerList = new ArrayList<>();
//定义属性 lzwWebApplicationContext,自己的spring容器
LzwWebApplicationContext lzwWebApplicationContext = null;
@Override
public void init() throws ServletException {
lzwWebApplicationContext = new LzwWebApplicationContext();
lzwWebApplicationContext.init();
//调用 initHandlerMapping(),完成url和控制器方法的映射
initHandlerMapping();
//输出 handlerList
System.out.println("handlerList初始化的结果 = " + handlerList);
}
@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("--LzwDispatcherServlet--doPost()--");
//调用方法,完成分发请求
executeDispatch(req, resp);
}
//编写方法,完成url和控制器方法的映射
private void initHandlerMapping(){
if(lzwWebApplicationContext.ioc.isEmpty()){
//判断ioc容器是否为空
return;
}
//遍历ioc容器的bean对象,然后进行url映射处理
for (Map.Entry<String, Object> entry : lzwWebApplicationContext.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
// /springmvc/monster/list
String url = requestMappingAnnotation.value();
//创建LzwHandler对象->就是一个映射关系
LzwHandler lzwHandler = new LzwHandler(url, entry.getValue(), declaredMethod);
//放入到handlerList
handlerList.add(lzwHandler);
}
}
}
}
}
//编写方法,通过request对象,返回HspHandler对象
//如果没有,就返回null
private LzwHandler getLzwHandler(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 (LzwHandler lzwHandler : handlerList) {
if(requestURI.equals(lzwHandler.getUrl())){//说明匹配上了
return lzwHandler;
}
}
return null;
}
//编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
LzwHandler lzwHandler = getLzwHandler(request);
try {
if(null == lzwHandler){//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
}else{//匹配成功, 反射调用控制器的方法
lzwHandler.getMethod().invoke(lzwHandler.getController(), request, response);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

修改 OrderController.java 用于测试
package com.lzw.controller;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author LiAng
*/
@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("<h1>订单列表信息</h1>");
} 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("<h1>添加订单...</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
完成测试
阶段3-从 web.xml 动态获取 lzwspringmvc.xml
LzwDispatcherServlet 在创建并初始化 LzwWebApplicationContext,动态的从 web.xml 中获 取到配置文件

修改 LzwWebApplicationContext.java
package com.lzw.lzwspringmvc.context;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.xml.XMLParser;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author LiAng
* LzwWebApplicationContext 表示我们自己的spring容器
*/
public class LzwWebApplicationContext {
//定义属性 classFullPathList,保存扫描包/子包的类的全路径
private List<String> classFullPathList = new ArrayList<>();
//定义属性ioc, 存放反射生成的Bean对象 /Controller/Service
public ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();
//无参构造器
public LzwWebApplicationContext() {
}
private String configLocation;//属性,表示spirng容器配置文件
public LzwWebApplicationContext(String configLocation) {
this.configLocation = configLocation;
}
//编写方法,完成自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);
}
}
System.out.println("扫描后的classFullPathList = " + classFullPathList);
//将扫描到的类,反射到ioc容器
executeInstance();
System.out.println("扫描后的 ioc容器 = " + ioc);
}
//创建方法,完成对包的扫描-> IO/容器/字符串
/**
*
* @param pack 要扫描的包,比如 "com.lzw.controller"
*/
public void scanPackage(String pack){
//得到包所在的工作路径[绝对路径]
//下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
//比如 "com.lzw.controller" => url 是 E:/VIP/AllDemo/SpringMVC/lzw-springmvc/target/lzw-springmvc/WEB-INF/classes/com/lzw/controller/
//细节说明: 1. 不要直接使用Junit测试, 否则 url = null
// 2. 启动tomcat来测试
URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.","/"));
//System.out.println("url = " + url);
//根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path= " + path);
//在io中,把目录,视为一个文件
File dir = new File(path);
for (File f : dir.listFiles()) {
if(f.isDirectory()){//如果是一个目录,需要递归扫描
scanPackage(pack + "." + f.getName());
}else{
//这时,你扫描到的文件,可能是.class, 也可能是其它文件
//就算是.class, 也存在是不是需要注入到容器
//目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
String classFullName = pack + "." + f.getName().replaceAll(".class","");
classFullPathList.add(classFullName);
}
}
}
//编写方法,将扫描到的类, 在满足条件的情况下,反射到ioc容器
public void executeInstance() {
//判断是否扫描到类
if(classFullPathList.size() == 0){//说明没有扫描到类
return;
}
try {
//遍历classFullPathList,进行反射
for (String classFullPath : classFullPathList) {
Class<?> clazz = Class.forName(classFullPath);
if(clazz.isAnnotationPresent(Controller.class)){
//得到类名首字母小写
String beanName = clazz.getSimpleName().substring(0,1).toLowerCase() + clazz.getSimpleName().substring(1);
ioc.put(beanName ,clazz.newInstance());
}//如果有其它的注解,可以扩展 , 来处理@Service
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
修改 LzwDispatcherServlet.java
package com.lzw.lzwspringmvc.servlet;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.annotation.RequestMapping;
import com.lzw.lzwspringmvc.context.LzwWebApplicationContext;
import com.lzw.lzwspringmvc.handler.LzwHandler;
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.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author LiAng
* 1. LzwDispatcherServlet 充当原生DispatcherServlet
* 2. 本质是一个Servlet,继承HttpServlet
* 3. 提示:需要使用到 JavaWeb中的servlet
*/
public class LzwDispatcherServlet extends HttpServlet {
//定义属性 handlerList,保存LzwHandler[url和控制器方法的映射]
private List<LzwHandler> handlerList = new ArrayList<>();
//定义属性 lzwWebApplicationContext,自己的spring容器
LzwWebApplicationContext lzwWebApplicationContext = null;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//获取到web.xml中的 contextConfigLocation
/*
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:lzwspringmvc.xml</param-value>
</init-param>
*/
String configLocation = servletConfig.getInitParameter("contextConfigLocation");
lzwWebApplicationContext = new LzwWebApplicationContext(configLocation);
lzwWebApplicationContext.init();
//调用 initHandlerMapping(),完成url和控制器方法的映射
initHandlerMapping();
//输出 handlerList
System.out.println("handlerList初始化的结果 = " + handlerList);
}
@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("--LzwDispatcherServlet--doPost()--");
//调用方法,完成分发请求
executeDispatch(req, resp);
}
//编写方法,完成url和控制器方法的映射
private void initHandlerMapping(){
if(lzwWebApplicationContext.ioc.isEmpty()){
//判断ioc容器是否为空
return;
}
//遍历ioc容器的bean对象,然后进行url映射处理
for (Map.Entry<String, Object> entry : lzwWebApplicationContext.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
// /springmvc/monster/list
String url = requestMappingAnnotation.value();
//创建LzwHandler对象->就是一个映射关系
LzwHandler lzwHandler = new LzwHandler(url, entry.getValue(), declaredMethod);
//放入到handlerList
handlerList.add(lzwHandler);
}
}
}
}
}
//编写方法,通过request对象,返回HspHandler对象
//如果没有,就返回null
private LzwHandler getLzwHandler(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 (LzwHandler lzwHandler : handlerList) {
if(requestURI.equals(lzwHandler.getUrl())){//说明匹配上了
return lzwHandler;
}
}
return null;
}
//编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
LzwHandler lzwHandler = getLzwHandler(request);
try {
if(null == lzwHandler){//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
}else{//匹配成功, 反射调用控制器的方法
lzwHandler.getMethod().invoke(lzwHandler.getController(), request, response);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
完成测试

阶段4-完成自定义@Service 注解功能
如果给某个类加上@Service,则可以将其注入到我们的 Spring 容器

创建 Monster.java
package com.lzw.entity;
/**
* @author LiAng
*/
public class Monster {
private Integer id;
private String name;
private String skill;
private Integer age;
public Monster(Integer id, String name, String skill, Integer age) {
this.id = id;
this.name = name;
this.skill = skill;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Monster{" +
"id=" + id +
", name='" + name + '\'' +
", skill='" + skill + '\'' +
", age=" + age +
'}';
}
}
创建 @Service.java
package com.lzw.lzwspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author LiAng
* Service 注解,用于标识一个Service对象,并注入到spring容器
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
创建 MonsterService.java
前面已经创建过
package com.lzw.service;
import com.lzw.entity.Monster;
import java.util.List;
/**
* @author LiAng
*/
public interface MonsterService {
//增加方法-返回monster列表
public List<Monster> listMonster();
}
创建 MonsterServiceImpl.java
package com.lzw.service.impl;
import com.lzw.entity.Monster;
import com.lzw.lzwspringmvc.annotation.Service;
import com.lzw.service.MonsterService;
import java.util.ArrayList;
import java.util.List;
/**
* @author LiAng
* MonsterServiceImpl/对象 作为service注入到spring容器
*/
@Service
public class MonsterServiceImpl implements MonsterService {
@Override
public List<Monster> listMonster() {
//模拟数据库DB
List<Monster> monsters = new ArrayList<>();
monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 500));
monsters.add(new Monster(200, "蜘蛛精", "吐口水", 200));
return monsters;
}
}
修改 lzwspringmvc.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<!--指定要扫描的基本包-->
<component-scan base-package="com.lzw.controller,com.lzw.service"></component-scan>
</beans>
修改 LzwWebApplicationContext.java
package com.lzw.lzwspringmvc.context;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.annotation.Service;
import com.lzw.lzwspringmvc.xml.XMLParser;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author LiAng
* LzwWebApplicationContext 表示我们自己的spring容器
*/
public class LzwWebApplicationContext {
//定义属性 classFullPathList,保存扫描包/子包的类的全路径
private List<String> classFullPathList = new ArrayList<>();
//定义属性ioc, 存放反射生成的Bean对象 /Controller/Service
public ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();
//无参构造器
public LzwWebApplicationContext() {
}
private String configLocation;//属性,表示spirng容器配置文件
public LzwWebApplicationContext(String configLocation) {
this.configLocation = configLocation;
}
//编写方法,完成自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);
}
}
System.out.println("扫描后的classFullPathList = " + classFullPathList);
//将扫描到的类,反射到ioc容器
executeInstance();
System.out.println("扫描后的 ioc容器 = " + ioc);
}
//创建方法,完成对包的扫描-> IO/容器/字符串
/**
*
* @param pack 要扫描的包,比如 "com.lzw.controller"
*/
public void scanPackage(String pack){
//得到包所在的工作路径[绝对路径]
//下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
//比如 "com.lzw.controller" => url 是 E:/VIP/AllDemo/SpringMVC/lzw-springmvc/target/lzw-springmvc/WEB-INF/classes/com/lzw/controller/
//细节说明: 1. 不要直接使用Junit测试, 否则 url = null
// 2. 启动tomcat来测试
URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.","/"));
//System.out.println("url = " + url);
//根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path= " + path);
//在io中,把目录,视为一个文件
File dir = new File(path);
for (File f : dir.listFiles()) {
if(f.isDirectory()){//如果是一个目录,需要递归扫描
scanPackage(pack + "." + f.getName());
}else{
//这时,你扫描到的文件,可能是.class, 也可能是其它文件
//就算是.class, 也存在是不是需要注入到容器
//目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
String classFullName = pack + "." + f.getName().replaceAll(".class","");
classFullPathList.add(classFullName);
}
}
}
//编写方法,将扫描到的类, 在满足条件的情况下,反射到ioc容器
public void executeInstance() {
//判断是否扫描到类
if(classFullPathList.size() == 0){//说明没有扫描到类
return;
}
try {
//遍历classFullPathList,进行反射
for (String classFullPath : classFullPathList) {
Class<?> clazz = Class.forName(classFullPath);
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(clazz.isAnnotationPresent(Service.class)){//如果类有@Service
//先获取到Service的value值
Service serviceAnnotation = clazz.getAnnotation(Service.class);
String beanName = serviceAnnotation.value();
if("".equals(beanName)){//说明没有指定value, 我们就使用默认的机制注入Service
//可以通过接口名/类名[首字母小写]来注入ioc容器
//1.得到所有接口的名称=>反射
Class<?>[] interfaces = clazz.getInterfaces();
Object instance = clazz.newInstance();
//2. 遍历接口,然后通过多个接口名来注入
for (Class<?> anInterface : interfaces) {
//接口名->首字母小写
String beanName2 = anInterface.getSimpleName().substring(0, 1).toLowerCase() + anInterface.getSimpleName().substring(1);
ioc.put(beanName2, instance);
}
}else{//如果有指定名称,就使用该名称注入即可
ioc.put(beanName, clazz.newInstance());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
完成测试

阶段5-完成 Spring 容器对象的自动装配 -@Autowried
加入@AutoWired 注解,进行对象属性的装配

创建 @AutoWired.java
package com.lzw.lzwspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author LiAng
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoWired {
String value() default "";
}
修改 MonsterController.java
package com.lzw.controller;
import com.lzw.entity.Monster;
import com.lzw.lzwspringmvc.annotation.AutoWired;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.annotation.RequestMapping;
import com.lzw.service.MonsterService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
/**
* @author LiAng
*/
@Controller
public class MonsterController {
//@AutoWired表示要完成属性的装配
@AutoWired
private MonsterService monsterService;
//编写方法,可以列出妖怪列表
//springmvc 是支持原生的servlet api,为了看到底层级制
//这里设计两个参数
@RequestMapping(value = "/monster/list")
public void listMonster(HttpServletRequest request, HttpServletResponse response){
//设置编码和返回类型
response.setContentType("text/html;charset=utf-8");
//返回模拟界面
StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");
//调用monsterService方法
List<Monster> monsters = monsterService.listMonster();
content.append("<table border='1px' width='500px' style='border-collapse:collapse'>");
for (Monster monster : monsters) {
content.append("<tr><td>"+ monster.getId()+"</td>" +
"<td>"+monster.getName()+"</td>" +
"<td>"+monster.getAge()+"</td>" +
"<td>"+monster.getSkill()+"</td></tr>");
}
content.append("</table>");
try {
//获取writer返回信息
PrintWriter printWriter = response.getWriter();
printWriter.write(content.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
修改 LzwWebApplicationContext.java
package com.lzw.lzwspringmvc.context;
import com.lzw.lzwspringmvc.annotation.AutoWired;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.annotation.Service;
import com.lzw.lzwspringmvc.xml.XMLParser;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author LiAng
* LzwWebApplicationContext 表示我们自己的spring容器
*/
public class LzwWebApplicationContext {
//定义属性 classFullPathList,保存扫描包/子包的类的全路径
private List<String> classFullPathList = new ArrayList<>();
//定义属性ioc, 存放反射生成的Bean对象 /Controller/Service
public ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();
//无参构造器
public LzwWebApplicationContext() {
}
private String configLocation;//属性,表示spirng容器配置文件
public LzwWebApplicationContext(String configLocation) {
this.configLocation = configLocation;
}
//编写方法,完成自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);
}
}
System.out.println("扫描后的classFullPathList = " + classFullPathList);
//将扫描到的类,反射到ioc容器
executeInstance();
System.out.println("扫描后的 ioc容器 = " + ioc);
}
//创建方法,完成对包的扫描-> IO/容器/字符串
/**
* @param pack 要扫描的包,比如 "com.lzw.controller"
*/
public void scanPackage(String pack) {
//得到包所在的工作路径[绝对路径]
//下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
//比如 "com.lzw.controller" => url 是 E:/VIP/AllDemo/SpringMVC/lzw-springmvc/target/lzw-springmvc/WEB-INF/classes/com/lzw/controller/
//细节说明: 1. 不要直接使用Junit测试, 否则 url = null
// 2. 启动tomcat来测试
URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.", "/"));
//System.out.println("url = " + url);
//根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path= " + path);
//在io中,把目录,视为一个文件
File dir = new File(path);
for (File f : dir.listFiles()) {
if (f.isDirectory()) {//如果是一个目录,需要递归扫描
scanPackage(pack + "." + f.getName());
} else {
//这时,你扫描到的文件,可能是.class, 也可能是其它文件
//就算是.class, 也存在是不是需要注入到容器
//目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
String classFullName = pack + "." + f.getName().replaceAll(".class", "");
classFullPathList.add(classFullName);
}
}
}
//编写方法,将扫描到的类, 在满足条件的情况下,反射到ioc容器
public void executeInstance() {
//判断是否扫描到类
if (classFullPathList.size() == 0) {//说明没有扫描到类
return;
}
try {
//遍历classFullPathList,进行反射
for (String classFullPath : classFullPathList) {
Class<?> clazz = Class.forName(classFullPath);
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 (clazz.isAnnotationPresent(Service.class)) {//如果类有@Service
//先获取到Service的value值
Service serviceAnnotation = clazz.getAnnotation(Service.class);
String beanName = serviceAnnotation.value();
if ("".equals(beanName)) {//说明没有指定value, 我们就使用默认的机制注入Service
//可以通过接口名/类名[首字母小写]来注入ioc容器
//1.得到所有接口的名称=>反射
Class<?>[] interfaces = clazz.getInterfaces();
Object instance = clazz.newInstance();
//2. 遍历接口,然后通过多个接口名来注入
for (Class<?> anInterface : interfaces) {
//接口名->首字母小写
String beanName2 = anInterface.getSimpleName().substring(0, 1).toLowerCase() + anInterface.getSimpleName().substring(1);
ioc.put(beanName2, instance);
}
} else {//如果有指定名称,就使用该名称注入即可
ioc.put(beanName, clazz.newInstance());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
//编写方法,完成属性的自动装配
public void executeAutoWired() {
//判断ioc有没有要装配的对象
if (ioc.isEmpty()) {
return; //你也可以抛出异常 throw new RuntimeException("ioc 容器没有bean对象")
}
//遍历ioc容器中的所有注入的bean对象, 然后获取到bean的所有字段/属性,判断是否需要装配
/**
* entry => <String,Object > String 就是你注入对象时名称 Object就是bean对象
*/
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
String key = entry.getKey();
Object bean = entry.getValue();
//得到bean的所有字段/属性
Field[] declaredFields = bean.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
//判断当前这个字段,是否有@AutoWired
if (declaredField.isAnnotationPresent(AutoWired.class)) {//有@AutoWired
//得到当前这个字段的@AutoWired
AutoWired autoWiredAnnotation = declaredField.getAnnotation(AutoWired.class);
String beanName = autoWiredAnnotation.value();
if ("".equals(beanName)) {//如果没有设置value,按照默认规则
//即得到字段类型的名称的首字母小写,作为名字来进行装配
Class<?> type = declaredField.getType();
beanName = type.getSimpleName().substring(0, 1).toLowerCase() + type.getSimpleName().substring(1);
}
//如果设置value, 直接按照beanName来进行装配
//从ioc容器中获取到bean
if (null == ioc.get(beanName)) {//说明你指定的名字对应的bean不在ioc容器
throw new RuntimeException("ioc容器中不存在你要装配的bean");
}
//防止属性是private, 我们需要暴力破解
declaredField.setAccessible(true);
try {
declaredField.set(bean, ioc.get(beanName));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
完成测试


阶段6-完成控制器方法获取参数-@RequestParam
1.功能说明
自定义@RequestParam 和方法参数名获取参数
@RequestMapping(value = "/monster/find")
public void findMonstersByName(HttpServletRequest request,HttpServletResponse response, @RequestParam(value = "name") String name) {
//代码....
}

2.将方法的 HttpServletRequest 和 HttpServletResponse 参数封装到参数数组,进行反射调用
修改 LzwDispatcherServlet.java
修改 executeDispatch()方法
//编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
LzwHandler lzwHandler = getLzwHandler(request);
try {
if(null == lzwHandler){//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
}else{//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class<?>[] parameterTypes = lzwHandler.getMethod().getParameterTypes();
//2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时,会使用到
Object[] params = new Object[parameterTypes.length];
//3. 遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for(int i = 0; i < parameterTypes.length; i++){
//取出每一个形参类型
Class<?> parameterType = parameterTypes[i];
//如果这个形参是HttpServletRequest, 将request填充到params
//在原生SpringMVC中,是按照类型来进行匹配
if("HttpServletRequest".equals(parameterType.getSimpleName())){
params[i] = request;
}else if("HttpServletResponse".equals(parameterType.getSimpleName())){
params[i] = response;
}
}
/**
* 1. 下面这样写法,其实是针对目标方法是 m(HttpServletRequest request , HttpServletResponse response)
* 2. 这里老师准备将需要传递给目标方法的 实参=>封装到参数数组=》然后以反射调用的方式传递给目标方法
* 3. public Object invoke(Object obj, Object... args)..
*/
//lzwHandler.getMethod().invoke(lzwHandler.getController(), request, response);
lzwHandler.getMethod().invoke(lzwHandler.getController(), params);
}
} catch (Exception e) {
e.printStackTrace();
}
}
完成测试

3.在方法参数指定 @RequestParam 的参数封装到参数数组,进行反射调用

创建 @RequestParam.java
package com.lzw.lzwspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author LiAng
* RequestParam 注解标注在目标方法的参数上,表示对应http请求的参数
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "";
}
修改 MonsterService.java
package com.lzw.service;
import com.lzw.entity.Monster;
import java.util.List;
/**
* @author LiAng
*/
public interface MonsterService {
//增加方法-返回monster列表
public List<Monster> listMonster();
//增加方法,通过传入的name,返回monster列表
public List<Monster> findMonsterByName(String name);
}
修改 MonsterServiceImpl.java
package com.lzw.service.impl;
import com.lzw.entity.Monster;
import com.lzw.lzwspringmvc.annotation.Service;
import com.lzw.service.MonsterService;
import java.util.ArrayList;
import java.util.List;
/**
* @author LiAng
* MonsterServiceImpl/对象 作为service注入到spring容器
*/
@Service
public class MonsterServiceImpl implements MonsterService {
@Override
public List<Monster> listMonster() {
//模拟数据库DB
List<Monster> monsters = new ArrayList<>();
monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 500));
monsters.add(new Monster(200, "蜘蛛精", "吐口水", 200));
return monsters;
}
@Override
public List<Monster> findMonsterByName(String name) {
//模拟数据库DB
List<Monster> monsters = new ArrayList<>();
monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 500));
monsters.add(new Monster(200, "蜘蛛精", "吐口水", 200));
monsters.add(new Monster(300, "大象精", "运木头", 100));
monsters.add(new Monster(400, "黄袍怪", "吐烟雾", 300));
monsters.add(new Monster(500, "白骨精", "美人计", 800));
//创建集合返回查询到的monster集合
List<Monster> findMonsters = new ArrayList<>();
//遍历 monsters,返回满足条件的
for (Monster monster : monsters) {
if(monster.getName().contains(name)){
findMonsters.add(monster);
}
}
return findMonsters;
}
}
修改 MonsterController.java
package com.lzw.controller;
import com.lzw.entity.Monster;
import com.lzw.lzwspringmvc.annotation.AutoWired;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.annotation.RequestMapping;
import com.lzw.lzwspringmvc.annotation.RequestParam;
import com.lzw.service.MonsterService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
/**
* @author LiAng
*/
@Controller
public class MonsterController {
//@AutoWired表示要完成属性的装配
@AutoWired
private MonsterService monsterService;
//编写方法,可以列出妖怪列表
//springmvc 是支持原生的servlet api,为了看到底层级制
//这里设计两个参数
@RequestMapping(value = "/monster/list")
public void listMonster(HttpServletRequest request, HttpServletResponse response){
//设置编码和返回类型
response.setContentType("text/html;charset=utf-8");
//返回模拟界面
StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");
//调用monsterService方法
List<Monster> monsters = monsterService.listMonster();
content.append("<table border='1px' width='500px' style='border-collapse:collapse'>");
for (Monster monster : monsters) {
content.append("<tr><td>"+ monster.getId()+"</td>" +
"<td>"+monster.getName()+"</td>" +
"<td>"+monster.getAge()+"</td>" +
"<td>"+monster.getSkill()+"</td></tr>");
}
content.append("</table>");
try {
//获取writer返回信息
PrintWriter printWriter = response.getWriter();
printWriter.write(content.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
//增加方法,通过name返回对应的monster集合
@RequestMapping(value = "/monster/find")
public void findMonsterByName(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "name") String name){
//设置编码和返回类型
response.setContentType("text/html;charset=utf-8");
System.out.println("--接收到的name--- :" + name);
//返回模拟界面
StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");
//调用monsterService方法
List<Monster> monsters = monsterService.findMonsterByName(name);
content.append("<table border='1px' width='500px' style='border-collapse:collapse'>");
for (Monster monster : monsters) {
content.append("<tr><td>"+ monster.getId()+"</td>" +
"<td>"+monster.getName()+"</td>" +
"<td>"+monster.getAge()+"</td>" +
"<td>"+monster.getSkill()+"</td></tr>");
}
content.append("</table>");
try {
//获取writer返回信息
PrintWriter printWriter = response.getWriter();
printWriter.write(content.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
修改 LzwDispatcherServlet.java
package com.lzw.lzwspringmvc.servlet;
import com.lzw.lzwspringmvc.annotation.Controller;
import com.lzw.lzwspringmvc.annotation.RequestMapping;
import com.lzw.lzwspringmvc.annotation.RequestParam;
import com.lzw.lzwspringmvc.context.LzwWebApplicationContext;
import com.lzw.lzwspringmvc.handler.LzwHandler;
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.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author LiAng
* 1. LzwDispatcherServlet 充当原生DispatcherServlet
* 2. 本质是一个Servlet,继承HttpServlet
* 3. 提示:需要使用到 JavaWeb中的servlet
*/
public class LzwDispatcherServlet extends HttpServlet {
//定义属性 handlerList,保存LzwHandler[url和控制器方法的映射]
private List<LzwHandler> handlerList = new ArrayList<>();
//定义属性 lzwWebApplicationContext,自己的spring容器
LzwWebApplicationContext lzwWebApplicationContext = null;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//获取到web.xml中的 contextConfigLocation
/*
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:lzwspringmvc.xml</param-value>
</init-param>
*/
String configLocation = servletConfig.getInitParameter("contextConfigLocation");
lzwWebApplicationContext = new LzwWebApplicationContext(configLocation);
lzwWebApplicationContext.init();
//调用 initHandlerMapping(),完成url和控制器方法的映射
initHandlerMapping();
//输出 handlerList
System.out.println("handlerList初始化的结果 = " + handlerList);
}
@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("--LzwDispatcherServlet--doPost()--");
//调用方法,完成分发请求
executeDispatch(req, resp);
}
//编写方法,完成url和控制器方法的映射
private void initHandlerMapping(){
if(lzwWebApplicationContext.ioc.isEmpty()){
//判断ioc容器是否为空
return;
}
//遍历ioc容器的bean对象,然后进行url映射处理
for (Map.Entry<String, Object> entry : lzwWebApplicationContext.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
// /springmvc/monster/list
String url = requestMappingAnnotation.value();
//创建LzwHandler对象->就是一个映射关系
LzwHandler lzwHandler = new LzwHandler(url, entry.getValue(), declaredMethod);
//放入到handlerList
handlerList.add(lzwHandler);
}
}
}
}
}
//编写方法,通过request对象,返回HspHandler对象
//如果没有,就返回null
private LzwHandler getLzwHandler(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 (LzwHandler lzwHandler : handlerList) {
if(requestURI.equals(lzwHandler.getUrl())){//说明匹配上了
return lzwHandler;
}
}
return null;
}
//编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
LzwHandler lzwHandler = getLzwHandler(request);
try {
if(null == lzwHandler){//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
}else{//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class<?>[] parameterTypes = lzwHandler.getMethod().getParameterTypes();
//2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时,会使用到
Object[] params = new Object[parameterTypes.length];
//3. 遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for(int i = 0; i < parameterTypes.length; i++){
//取出每一个形参类型
Class<?> parameterType = parameterTypes[i];
//如果这个形参是HttpServletRequest, 将request填充到params
//在原生SpringMVC中,是按照类型来进行匹配
if("HttpServletRequest".equals(parameterType.getSimpleName())){
params[i] = request;
}else if("HttpServletResponse".equals(parameterType.getSimpleName())){
params[i] = response;
}
}
//将http请求参数封装到params数组中。要注意填充实参的时候,顺序问题
//1.(1) 获取http请求的参数集合
//http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
//1.(2) 返回的Map<String,String[]> String:表示http请求的参数名
// String[]:表示http请求的参数值,为什么是数组(有多个hobby)
Map<String, String[]> parameterMap = request.getParameterMap();
//2. 遍历parameterMap 将请求参数,按照顺序填充到实参数组params
for(Map.Entry<String, String[]> entry : parameterMap.entrySet()){
//取出key,这name就是对应请求的参数名
String name = entry.getKey();
//说明:这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
String value = entry.getValue()[0];
//我们得到请求的参数对应目标方法的第几个形参,然后将其填充
//这里专门编写一个方法,得到请求的参数对应的是第几个形参
int indexRequestParameterIndex = getIndexRequestParameterIndex(lzwHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) {//找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]
}
}
/**
* 1. 下面这样写法,其实是针对目标方法是 m(HttpServletRequest request , HttpServletResponse response)
* 2. 这里准备将需要传递给目标方法的 实参=>封装到参数数组=》然后以反射调用的方式传递给目标方法
* 3. public Object invoke(Object obj, Object... args)..
*/
//lzwHandler.getMethod().invoke(lzwHandler.getController(), request, response);
lzwHandler.getMethod().invoke(lzwHandler.getController(), params);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//编写方法,返回请求参数是目标方法的第几个形参
/**
* @param method 目标方法
* @param name 请求的参数名
* @return 是目标方法的第几个形参
*/
public int getIndexRequestParameterIndex(Method method, String name){
//1.得到method的所有形参参数
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
//取出当前的形参参数
Parameter parameter = parameters[i];
//判断parameter是不是有@RequestParam注解
boolean annotationPresent = parameter.isAnnotationPresent(RequestParam.class);
if(annotationPresent){//说明有@RequestParam
//取出当前这个参数的 @RequestParam(value = "xxx")
RequestParam requestParamAnnotation = parameter.getAnnotation(RequestParam.class);
String value = requestParamAnnotation.value();
//这里就是匹配的比较
if (name.equals(value)) {
return i;//找到请求的参数,对应的目标方法的形参的位置
}
}
}
//如果没有匹配成功,就返回-1
return -1;
}
}
完成测试

4.按照形参名匹配请求参数
在方法参数没有指定 @RequestParam,按照默认参数名获取值,进行反射调用
修改 MonsterController.java
//增加方法,通过name返回对应的monster集合
@RequestMapping(value = "/monster/find")
public void findMonsterByName(HttpServletRequest request, HttpServletResponse response, String name){
//设置编码和返回类型
response.setContentType("text/html;charset=utf-8");
System.out.println("--接收到的name--- :" + name);
//返回模拟界面
StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");
//调用monsterService方法
List<Monster> monsters = monsterService.findMonsterByName(name);
content.append("<table border='1px' width='500px' style='border-collapse:collapse'>");
for (Monster monster : monsters) {
content.append("<tr><td>"+ monster.getId()+"</td>" +
"<td>"+monster.getName()+"</td>" +
"<td>"+monster.getAge()+"</td>" +
"<td>"+monster.getSkill()+"</td></tr>");
}
content.append("</table>");
try {
//获取writer返回信息
PrintWriter printWriter = response.getWriter();
printWriter.write(content.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
修改 LzwDispatcherServlet.java
//编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
LzwHandler lzwHandler = getLzwHandler(request);
try {
if(null == lzwHandler){//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
}else{//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class<?>[] parameterTypes = lzwHandler.getMethod().getParameterTypes();
//2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时,会使用到
Object[] params = new Object[parameterTypes.length];
//3. 遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for(int i = 0; i < parameterTypes.length; i++){
//取出每一个形参类型
Class<?> parameterType = parameterTypes[i];
//如果这个形参是HttpServletRequest, 将request填充到params
//在原生SpringMVC中,是按照类型来进行匹配
if("HttpServletRequest".equals(parameterType.getSimpleName())){
params[i] = request;
}else if("HttpServletResponse".equals(parameterType.getSimpleName())){
params[i] = response;
}
}
//将http请求参数封装到params数组中。要注意填充实参的时候,顺序问题
//1.(1) 获取http请求的参数集合
//http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
//1.(2) 返回的Map<String,String[]> String:表示http请求的参数名
// String[]:表示http请求的参数值,为什么是数组(有多个hobby)
Map<String, String[]> parameterMap = request.getParameterMap();
//2. 遍历parameterMap 将请求参数,按照顺序填充到实参数组params
for(Map.Entry<String, String[]> entry : parameterMap.entrySet()){
//取出key,这name就是对应请求的参数名
String name = entry.getKey();
//说明:这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
String value = entry.getValue()[0];
//我们得到请求的参数对应目标方法的第几个形参,然后将其填充
//这里专门编写一个方法,得到请求的参数对应的是第几个形参
int indexRequestParameterIndex = getIndexRequestParameterIndex(lzwHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) {//找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置
//思路
//1. 得到目标方法的所有形参的名称-专门编写方法获取形参名
//2. 对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
getParameterNames(lzwHandler.getMethod());
}
}
/**
* 1. 下面这样写法,其实是针对目标方法是 m(HttpServletRequest request , HttpServletResponse response)
* 2. 这里准备将需要传递给目标方法的 实参=>封装到参数数组=》然后以反射调用的方式传递给目标方法
* 3. public Object invoke(Object obj, Object... args)..
*/
//lzwHandler.getMethod().invoke(lzwHandler.getController(), request, response);
lzwHandler.getMethod().invoke(lzwHandler.getController(), params);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//编写方法, 得到目标方法的所有形参的名称,并放入到集合中返回
/**
* @param method 目标方法
* @return 所有形参的名称, 并放入到集合中返回
*/
public List<String> getParameterNames(Method method){
List<String> parametersList = new ArrayList<>();
//获取到所以的参数名->这里有一个小细节
//在默认情况下 parameter.getName() 得到的名字不是形参真正名字
//而是 [arg0, arg1, arg2...], 这里我们要引入一个插件,使用java8特性,这样才能解决
Parameter[] parameters = method.getParameters();
//遍历parameters 取出名称,放入parametersList
for (Parameter parameter : parameters) {
parametersList.add(parameter.getName());
}
System.out.println("目标方法的形参列表=" + parametersList);
return parametersList;
}
参数名如图:

解决方案
修改 pom.xml
在 <build>
--><pluginManagement>
--><plugins>
里最后面添加
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-complier-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<encoding>utf-8</encoding>
</configuration>
</plugin>
记得刷新一下

点击Maven管理,双击clean,再重启tomcat(最好IDEA也重启)

如果修改 pom.xml 方法不生效
这样设置

记得 Rebuild 一下项目
测试

修改 LzwDispatcherServlet.java
//编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
LzwHandler lzwHandler = getLzwHandler(request);
try {
if(null == lzwHandler){//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
}else{//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class<?>[] parameterTypes = lzwHandler.getMethod().getParameterTypes();
//2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时,会使用到
Object[] params = new Object[parameterTypes.length];
//3. 遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for(int i = 0; i < parameterTypes.length; i++){
//取出每一个形参类型
Class<?> parameterType = parameterTypes[i];
//如果这个形参是HttpServletRequest, 将request填充到params
//在原生SpringMVC中,是按照类型来进行匹配
if("HttpServletRequest".equals(parameterType.getSimpleName())){
params[i] = request;
}else if("HttpServletResponse".equals(parameterType.getSimpleName())){
params[i] = response;
}
}
//将http请求参数封装到params数组中。要注意填充实参的时候,顺序问题
//1.(1) 获取http请求的参数集合
//http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
//1.(2) 返回的Map<String,String[]> String:表示http请求的参数名
// String[]:表示http请求的参数值,为什么是数组(有多个hobby)
Map<String, String[]> parameterMap = request.getParameterMap();
//2. 遍历parameterMap 将请求参数,按照顺序填充到实参数组params
for(Map.Entry<String, String[]> entry : parameterMap.entrySet()){
//取出key,这name就是对应请求的参数名
String name = entry.getKey();
//说明:这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
String value = entry.getValue()[0];
//我们得到请求的参数对应目标方法的第几个形参,然后将其填充
//这里专门编写一个方法,得到请求的参数对应的是第几个形参
int indexRequestParameterIndex = getIndexRequestParameterIndex(lzwHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) {//找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置
//思路
//1. 得到目标方法的所有形参的名称-专门编写方法获取形参名
//2. 对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
List<String> parameterNames = getParameterNames(lzwHandler.getMethod());
for (int i = 0; i < parameterNames.size(); i++) {
//如果请求参数名和目标方法的形参名一样,说明匹配成功
if (name.equals(parameterNames.get(i))) {
params[i] = value;//填充到实参数组
break;
}
}
}
}
/**
* 1. 下面这样写法,其实是针对目标方法是 m(HttpServletRequest request , HttpServletResponse response)
* 2. 这里准备将需要传递给目标方法的 实参=>封装到参数数组=》然后以反射调用的方式传递给目标方法
* 3. public Object invoke(Object obj, Object... args)..
*/
//lzwHandler.getMethod().invoke(lzwHandler.getController(), request, response);
lzwHandler.getMethod().invoke(lzwHandler.getController(), params);
}
} catch (Exception e) {
e.printStackTrace();
}
}
完成测试
阶段7-完成简单视图解析
功能说明
通过方法返回的 String,转发或者重定向到指定页面。
-用户输入白骨精,可以登录成功,否则失败。
-根据登录的结果,可以重定向或者请求转发到 login_ok.jsp / login_error.jsp,并显示妖怪名。

代码实现
修改 MonsterService.java
package com.lzw.service;
import com.lzw.entity.Monster;
import java.util.List;
/**
* @author LiAng
*/
public interface MonsterService {
//增加方法-返回monster列表
public List<Monster> listMonster();
//增加方法,通过传入的name,返回monster列表
public List<Monster> findMonsterByName(String name);
//增加方法,处理登录
public boolean login(String name);
}
修改 MonsterServiceImpl.java
增加方法
@Override
public boolean login(String name) {
//实际上回到DB验证,这里简化
if("白骨精".equals(name)){
return true;
}else {
return false;
}
}
修改 MonsterController.java
增加方法
//处理妖怪登录的方法,返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request, HttpServletResponse response, String mName){
System.out.println("--接收到mName--:" + mName);
//将mName设置到request域
request.setAttribute("mName", mName);
boolean b = monsterService.login(mName);
if(b){//登录成功
//return "forward:/login_ok.jsp";
//测试重定向
//return "redirect:/login_ok.jsp";
//测试默认机制
return "/login_ok.jsp";
}else {//失败
return "forward:/login_error.jsp";
}
}
创建 jsp页面

login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录页面</title>
</head>
<body>
<h1>登录页面</h1>
<form action="/monster/login" method="post">
妖怪名:<input type="text" name="mName"><br/>
<input type="submit" value="登录">
</form>
</body>
</html>
login_ok.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>登录成功</title>
</head>
<body>
<h1>登录成功</h1>
欢迎你:${requestScope.mName}
</body>
</html>
login_error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>登陆失败</title>
</head>
<body>
<h1>登录失败</h1>
sorry,登录失败 ${requestScope.mName}
</body>
</html>
修改 LzwDispatcherServlet.java
//编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
LzwHandler lzwHandler = getLzwHandler(request);
try {
if(null == lzwHandler){//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
}else{//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class<?>[] parameterTypes = lzwHandler.getMethod().getParameterTypes();
//2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时,会使用到
Object[] params = new Object[parameterTypes.length];
//3. 遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for(int i = 0; i < parameterTypes.length; i++){
//取出每一个形参类型
Class<?> parameterType = parameterTypes[i];
//如果这个形参是HttpServletRequest, 将request填充到params
//在原生SpringMVC中,是按照类型来进行匹配
if("HttpServletRequest".equals(parameterType.getSimpleName())){
params[i] = request;
}else if("HttpServletResponse".equals(parameterType.getSimpleName())){
params[i] = response;
}
}
//将http请求参数封装到params数组中。要注意填充实参的时候,顺序问题
//1.(1) 获取http请求的参数集合
//http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
//1.(2) 返回的Map<String,String[]> String:表示http请求的参数名
// String[]:表示http请求的参数值,为什么是数组(有多个hobby)
//处理提交的数据中文乱码
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();
//2. 遍历parameterMap 将请求参数,按照顺序填充到实参数组params
for(Map.Entry<String, String[]> entry : parameterMap.entrySet()){
//取出key,这name就是对应请求的参数名
String name = entry.getKey();
//说明:这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
String value = entry.getValue()[0];
//我们得到请求的参数对应目标方法的第几个形参,然后将其填充
//这里专门编写一个方法,得到请求的参数对应的是第几个形参
int indexRequestParameterIndex = getIndexRequestParameterIndex(lzwHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) {//找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置
//思路
//1. 得到目标方法的所有形参的名称-专门编写方法获取形参名
//2. 对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
List<String> parameterNames = getParameterNames(lzwHandler.getMethod());
for (int i = 0; i < parameterNames.size(); i++) {
//如果请求参数名和目标方法的形参名一样,说明匹配成功
if (name.equals(parameterNames.get(i))) {
params[i] = value;//填充到实参数组
break;
}
}
}
}
/**
* 1. 下面这样写法,其实是针对目标方法是 m(HttpServletRequest request , HttpServletResponse response)
* 2. 这里准备将需要传递给目标方法的 实参=>封装到参数数组=》然后以反射调用的方式传递给目标方法
* 3. public Object invoke(Object obj, Object... args)..
*/
//lzwHandler.getMethod().invoke(lzwHandler.getController(), request, response);
//反射调用目标方法
Object result = lzwHandler.getMethod().invoke(lzwHandler.getController(), params);
//这里就是对返回的结果进行解析=>原生springmvc 可以通过视图解析器来完成
//这里简化,直接解析
if(result instanceof String){
String viewName = (String)result;
if(viewName.contains(":")) {//说明你返回的String 结果forward:/login_ok.jsp 或者 redirect:/xxx/xx/xx.xx
String viewType = viewName.split(":")[0];//forward | redirect
String viewPage = viewName.split(":")[1];//是你要跳转的页面名
if("forward".equals(viewType)){//请求转发
request.getRequestDispatcher(viewPage).forward(request, response);
}else if("redirect".equals(viewType)){//重定向
response.sendRedirect(viewPage);
}
}else{//默认是请求转发
request.getRequestDispatcher(viewName).forward(request, response);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
完成测试
阶段8-完成返回 JSON 格式数据-@ResponseBody
功能说明
自定义@ResponseBody 返回 JSON 格式数据。
代码实现
创建 @ResponseBody.java
package com.lzw.lzwspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author LiAng
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
修改 MonsterController.java
增加方法
//编写方法,返回json格式的数据
//目标方法返回的结果是给springmvc底层通过反射调用后的位置
//我们在springmvc底层反射调用的位置,接收到结果并解析即可
@RequestMapping(value = "/monster/list/json")
@ResponseBody
public List<Monster> listMonsterByJson(HttpServletRequest request, HttpServletResponse response){
List<Monster> monsters = monsterService.listMonster();
return monsters;
}
修改 pom.xml
<!--引入jackson 使用它的工具类可以进行json操作-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
修改 LzwDispatcherServlet.java
//编写方法,完成分发请求任务
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
LzwHandler lzwHandler = getLzwHandler(request);
try {
if(null == lzwHandler){//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
}else{//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class<?>[] parameterTypes = lzwHandler.getMethod().getParameterTypes();
//2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时,会使用到
Object[] params = new Object[parameterTypes.length];
//3. 遍历parameterTypes形参数组,根据形参数组信息,将实参填充到实参数组
for(int i = 0; i < parameterTypes.length; i++){
//取出每一个形参类型
Class<?> parameterType = parameterTypes[i];
//如果这个形参是HttpServletRequest, 将request填充到params
//在原生SpringMVC中,是按照类型来进行匹配
if("HttpServletRequest".equals(parameterType.getSimpleName())){
params[i] = request;
}else if("HttpServletResponse".equals(parameterType.getSimpleName())){
params[i] = response;
}
}
//将http请求参数封装到params数组中。要注意填充实参的时候,顺序问题
//1.(1) 获取http请求的参数集合
//http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
//1.(2) 返回的Map<String,String[]> String:表示http请求的参数名
// String[]:表示http请求的参数值,为什么是数组(有多个hobby)
//处理提交的数据中文乱码
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();
//2. 遍历parameterMap 将请求参数,按照顺序填充到实参数组params
for(Map.Entry<String, String[]> entry : parameterMap.entrySet()){
//取出key,这name就是对应请求的参数名
String name = entry.getKey();
//说明:这里只考虑提交的参数是单值的情况,即不考虑类似checkbox提示的数据
String value = entry.getValue()[0];
//我们得到请求的参数对应目标方法的第几个形参,然后将其填充
//这里专门编写一个方法,得到请求的参数对应的是第几个形参
int indexRequestParameterIndex = getIndexRequestParameterIndex(lzwHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) {//找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置
//思路
//1. 得到目标方法的所有形参的名称-专门编写方法获取形参名
//2. 对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
List<String> parameterNames = getParameterNames(lzwHandler.getMethod());
for (int i = 0; i < parameterNames.size(); i++) {
//如果请求参数名和目标方法的形参名一样,说明匹配成功
if (name.equals(parameterNames.get(i))) {
params[i] = value;//填充到实参数组
break;
}
}
}
}
/**
* 1. 下面这样写法,其实是针对目标方法是 m(HttpServletRequest request , HttpServletResponse response)
* 2. 这里准备将需要传递给目标方法的 实参=>封装到参数数组=》然后以反射调用的方式传递给目标方法
* 3. public Object invoke(Object obj, Object... args)..
*/
//lzwHandler.getMethod().invoke(lzwHandler.getController(), request, response);
//反射调用目标方法
Object result = lzwHandler.getMethod().invoke(lzwHandler.getController(), params);
//这里就是对返回的结果进行解析=>原生springmvc 可以通过视图解析器来完成
//这里简化,直接解析
if(result instanceof String){
String viewName = (String)result;
if(viewName.contains(":")) {//说明你返回的String 结果forward:/login_ok.jsp 或者 redirect:/xxx/xx/xx.xx
String viewType = viewName.split(":")[0];//forward | redirect
String viewPage = viewName.split(":")[1];//是你要跳转的页面名
if("forward".equals(viewType)){//请求转发
request.getRequestDispatcher(viewPage).forward(request, response);
}else if("redirect".equals(viewType)){//重定向
response.sendRedirect(viewPage);
}
}else{//默认是请求转发
request.getRequestDispatcher(viewName).forward(request, response);
}
}
else if(result instanceof ArrayList){//如果是ArrayList
//判断目标方法是否有@ResponseBody
Method method = lzwHandler.getMethod();
if(method.isAnnotationPresent(ResponseBody.class)){
//把result [ArrayList] 转成json格式数据-》返回
//如何将 ArrayList 转成 json
//这里我们需要使用jackson包下的工具类可以轻松的搞定.
ObjectMapper objectMapper = new ObjectMapper();
String resultJson = objectMapper.writeValueAsString(result);
response.setContentType("text/html;charset=utf-8");
//这里简单的处理,就直接返回
PrintWriter writer = response.getWriter();
writer.write(resultJson);
writer.flush();
writer.close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
完成测试
