自己实现 SpringMVC 底层机制 核心分发 控制器+ Controller 和 Service 注入容器 + 对象自动装配 + 控制器 方法获取参数 + 视图解析 + 返回 JSON 格式数
搭建 SpringMVC 底层机制开发环境
1、创建 Maven 项目 wyx-springmvc [提示: 我们在讲解 手动实现tomcat 时,我们已经使用过,不知道可以看我的博客
2、对 wyx-springmvc 进行配置: 修改 D:\idea_java_projects\wyx-springmvc\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、对 wyx-springmvc 进行配置: 创建 main 和 test 相关的源码目录和资源目录和测试目录 (这是 Maven 工程的开发规范,前面讲过..), 如图
提示:
引入需要的基本的 jar 包, 修改 pom.xml
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--引入原生servlet依赖 的jar-->
<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,防止版本冲突
4. 后面在讲解maven时,还有详细讲解
-->
<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>
实现任务阶段 1- 开发 WyxDispatcherServlet
说明:
编写 WyxDispatcherServlet 充当原生的 DispatcherServlet(即核心控制器)
分析示意图
创建WyxDispatcherServlet.java类
public class WyxDispatcherServlet 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);
}
}
创建wyxspringmvc.xml
充当 原生的 applicationContext-mvc.xml 文件 (就是 spring 的容器配置文件 比如指定要扫描 哪些包下的类) 先创建给空的文件
修改web.xml, 完成 WyxDispatcherServlet 的
<!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>
<!--配置WyxDispatcherServlet, 作为我们自己的前端控制器-->
<servlet>
<servlet-name>WyxDispatcherServlet</servlet-name>
<servlet-class>com.wyxdu.wyxspringmvc.servlet.WyxDispatcherServlet</servlet-class>
<!--给WyxDispatcherServlet配置参数,指定要操作的spring容器配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:wyxspringmvc.xml</param-value>
</init-param>
<!--WyxDispatcherServlet在tomcat启动时,就自动加载-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>WyxDispatcherServlet</servlet-name>
<!--因为WyxDispatcherServlet作为前端控制器,所以需要拦截所有请求,url-pattern配置 /-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置 Tomcat, 完成测试
配置 Tomcat 具体步骤我们在讲解 JavaWeb 时 已经操作过 不知道请看 链接
修改WyxDispatcherServlet.java
public class WyxDispatcherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
System.out.println("WyxDispatcherServlet doGet()被调用");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
System.out.println("WyxDispatcherServlet doPost()被调用");
}
}
启动 Tomcat完成测试
实现任务阶段 2- 完成客户端/浏览器可以请求控制层
创建 自己的 Controller 和自定义注解
● 分析示意图
创建MonsterController.java
public class MonsterController {
public void listMonsters(HttpServletRequest request, HttpServletResponse
response) {
response.setContentType("text/html;charset=utf-8");
try {
PrintWriter printWriter = response.getWriter();
printWriter.write("<h1>妖怪列表</h1>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
创建自定义注解Controller
annotation\Controller.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
创建自定义注解RequestMapping
annotation\RequestMapping
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
配置 wyxspringmvc.xml
在该文件指定,我们的 springmvc 要扫描的包
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<component-scan base-package="com.wyxedu.controller"></component-scan>
</beans>
编写 XMLParser 工具类,可以解析 wyxspringmvc.xml
完成功能说明: -编写 XMLParser 工具类, 可以解析 wyxringmvc.xml, 得到要扫描的包-如图
创建XMLPaser.java类
package com.wyxdu.wyxspringmvc.xml;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
/**
* XMLParser 用于解析spring配置文件
*/
public class XMLParser {
public static String getBasePackage(String xmlFile) {
//这个解析的过程,是前面讲过的
SAXReader saxReader = new SAXReader();
//通过得到类的加载路径-》获取到spring配置文件.[对应的资源流]
InputStream inputStream =
XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);
try {
//得到xmlFile文档
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
Element componentScanElement =
rootElement.element("component-scan");
Attribute attribute = componentScanElement.attribute("base-package");
String basePackage = attribute.getText();
return basePackage;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
创建WyxSpringMvcTest
public class HspSpringMvcTest {
@Test
public void readXML() {
String basePackage = XMLPaser.getbasePackage("wyxspringmvc.xml");
System.out.println("basePackage= " + basePackage);
}
}
开发 WyxWebApplicationContext,充当 Spring 容器-得到扫描类的全路径列
完成的功能说明
- 把指定的目录包括子目录下的 java 类的全路径扫描到集合中,
比如 ArrayList -如图[对 java 基础知识的使用
扫描后的
classFullPathList=[com.wyxdu.controller.MonsterController, com.wyxedu.service.impl.MonsterServiceImpl, com.wyxedu.service.MonsterService]
创建WyxWebApplicationContext.java
public class WyxWebApplicationContext {
private ArrayList<String> classFullPathList = new ArrayList
public void init() {
//这里是写的固定的spring容器配置文件.?=>做活
String basePackage = XMLParser.getBasePackage("wyxspringmvc.xml");
//这时basePackage => com.wyxdu.controller,com.wyxdu.service
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);//扫描
}
}
System.out.println("扫描后的= classFullPathList=" + classFullPathList);
}
public void scanPackage(String pack) {
//得到包所在的工作路径[绝对路径]
//下面这句话的含义是 通过类的加载器,得到你指定的包对应的 工作路径[绝对路径]
//比如 "com.hspedu.controller" => url 是 D:\hspedu_springmvc\hsp-springmvc\target\hsp-springmvc\WEB-INF\classes\com\hspedu\controller
//细节说明: 1. 不要直接使用Junit测试, 否则 url null
// 2. 启动tomcat来吃测试
URL url =
this.getClass().getClassLoader()
.getResource("/" + pack.replaceAll("\\.", "/"));
System.out.println("urlss=" + url);
//根据得到的路径, 对其进行扫描,把类的全路径,保存到classFullPathList
String path = url.getFile();
System.out.println("path= " + path);
//在io中,把目录,视为一个文件
File dir = new File(path);
//遍历dir[文件/子目录]
for (File f : dir.listFiles()) {
if (f.isDirectory()) {//如果是一个目录,需要递归扫描
scanPackage(pack + "." + f.getName());
} else {
//说明:这时,你扫描到的文件,可能是.class, 也可能是其它文件
//就算是.class, 也存在是不是需要注入到容器
//目前先把文件的全路径都保存到集合,后面在注入对象到容器时,再处理
String classFullPath =
pack + "." + f.getName().replaceAll(".class", "");
classFullPathList.add(classFullPath);
}
}
}
}
修改WyxDispatcherServlet.java类
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//创建自己的spring容器
wyxWebApplicationContext =
new WyxWebApplicationContext(configLocation);
wyxWebApplicationContext.init();
}
启动 Tomcat 完成测试,
看看扫描是否成功. 需要使用 Tomcat 启动方式完成测试,直接用 Junit 测试 URL 是null
15:03:30.297 信 息 [RMI TCP Connection(3)-127.0.0.1]
org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet
contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were
scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can
improve startup time and JSP compilation time.
扫描后
classFullPathList=[com.hspedu.controller.MonsterController, com.wyxdu.service.impl.MonsterServiceImpl, com.wyxdu.service.MonsterService]
完善 WyxWebApplicationContext,充当 Spring 容器-实例化对象到容器中
public void init() {
String basePackage = XMLParser.getBasePackage("wyxspringmvc.xml");
//这时basePackage => com.wyxdu.controller,com.wyxdu.service
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);//扫描
}
}
System.out.println("扫描后的= classFullPathList=" + classFullPathList);
//将扫描到的类, 反射到ico容器
executeInstance();
System.out.println("扫描后的 ioc容器= " + ioc);
}
public void executeInstance() {
//判断是否扫描到类
if (classFullPathList.size() == 0) {//说明没有扫描到类
return;
}
try {
//遍历classFullPathList,进行反射
for (String classFullPath : classFullPathList) {
Class<?> clazz = Class.forName(classFullPath);
//说明当前这个类有@Controller
if (clazz.isAnnotationPresent(Controller.class)) {
//得到类名首字母小写
String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() +
clazz.getSimpleName().substring(1);
ioc.put(beanName, clazz.newInstance());
} //如果有其它的注解,可以扩展 , 来处理@Service
else if()
}
} catch (Exception e) {
e.printStackTrace();
}
}
完成测试
(提示: 启动 tomcat 来加载 WyxDispatcherServlet 的方式测试
扫描后的 ioc容器= {goodsController=com.wyxdu.controller.xx.GoodsController@29e91c96,
完成请求 URL 和控制器方法的映射关系
示意图分析
将配置的@RequestMapping 的 url 和 对应的 控制器-方法 映射关系保存到集合中
-如图
handlerList初始化的结果= [WyxHandler{url='/order/list', controller=com.wyxdu.controller.OrderController@79b8c11d, method=public void com.wyxdu.controller.OrderController.listOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
创建WyxHandler 类
public class WyxHandler {
private String url;
private Object controller;
private Method method;
public WyxHandler(String url, Object controller, Method method) {
this.url = url;
this.controller = controller;
this.method = method;
}
public WyxHandler() {
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
return "WyxHandler{" +
"url='" + url + '\'' +
", controller=" + controller +
", method=" + method +
'}';
}
}
修改WyxDispatcherServlet.java类
public class WyxDispatcherServlet extends HttpServlet {
//定义属性 handlerList , 保存HspHandler[url和控制器方法的映射]
private List<WyxHandler> handlerList =
new ArrayList<>();
//定义属性 wyxWebApplicationContext,自己的spring容器
WyxWebApplicationContext wyxWebApplicationContext = null;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//创建自己的spring容器
wyxWebApplicationContext =
new WyxWebApplicationContext();
wyxWebApplicationContext.init();
//调用 initHandlerMapping , 完成url和控制器方法的映射
initHandlerMapping();
//输出handlerList
System.out.println("handlerList初始化的结果= " + handlerList);
}
private void initHandlerMapping() {
if(wyxWebApplicationContext.ioc.isEmpty()) {
throw new RuntimeException("spring ioc 容器为空");
}
for(Map.Entry<String,Object> entry:
wyxWebApplicationContext.ioc.entrySet()) {
//先取出注入的Object的clazz对象
Class<?> clazz = entry.getValue().getClass();
//如果注入的Bean是Controller
if(clazz.isAnnotationPresent(Controller.class)) {
//取出它的所有方法
Method[] declaredMethods = clazz.getDeclaredMethods();
//遍历方法
for (Method declaredMethod : declaredMethods) {
//判断该方法是否有@RequestMapping
if(declaredMethod.isAnnotationPresent(RequestMapping.class)) {
//取出@RequestMapping值->就是映射路径
RequestMapping requestMappingAnnotation =
declaredMethod.getAnnotation(RequestMapping.class);
//这里小伙伴可以把工程路径+url
//getServletContext().getContextPath()
// /springmvc/monster/list
String url = requestMappingAnnotation.value();
handlerList.add(new
WyxHandler(url,entry.getValue(),declaredMethod));
}
}
}
}
}
完成测试
(启动 Tomcat , 加载 HspDispatcherServlet 方式), 测试结果前面已经展示了
完成 WyxDispatcherServlet 分发请求到对应控制器方法
示意图
当用户发出请求,根据用户请求 url 找到对应的控制器-方法, 并反射调用
- 如果用户请求的路径不存在,返回 404
修改WyxDispatcherServlet.java类
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//System.out.println("--WyxDispatcherServlet--doPost---");
//调用方法,完成分发请求
executeDispatch(req, resp);
}
private void executeDispatch(HttpServletRequest req,
HttpServletResponse response) {
WyxHandler wyxHandler = getWyxHandler(req);
try {
if (null == wyxHandler) {//没有匹配的 Handler
response.getWriter().print("<h1>404 NOT FOUND</h1>");
} else {//有匹配的 Handler, 就调用
wyxHandler.getMethod().invoke(wyxHandler.getController(), req,
response);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private WyxHandler getWyxHandler(HttpServletRequest request) {
//1.先获取的用户请求的uri 比如http://localhost:8080/springmvc/monster/list
// uri = /springmvc/monster/list
//2. 这里要注意得到uri 和 保存url 是有一个工程路径的问题
// 两个方案解决 =>第一个方案: 简单 tomcat 直接配置 application context =>/
// 第二个方案 保存 hsphandler对象 url 拼接 getServletContext().getContextPath()
String requestURI = request.getRequestURI();
//遍历handlerList
for (WyxHandler wyxHandler : handlerList) {
if (requestURI.equals(wyxHandler.getUrl())) {//说明匹配成功
return wyxHandler;
}
}
return null;
}
private void initHandlerMapping() {
if(wyxWebApplicationContext.ioc.isEmpty()) {
throw new RuntimeException("spring ioc 容器为空");
}
for(Map.Entry<String,Object> entry:
wyxWebApplicationContext.ioc.entrySet()) {
//先取出注入的Object的clazz对象
Class<?> clazz = entry.getValue().getClass();
//如果注入的Bean是Controller
if(clazz.isAnnotationPresent(Controller.class)) {
//取出它的所有方法
Method[] declaredMethods = clazz.getDeclaredMethods();
//遍历方法
for (Method declaredMethod : declaredMethods) {
//判断该方法是否有@RequestMapping
if(declaredMethod.isAnnotationPresent(RequestMapping.class)) {
//取出@RequestMapping值->就是映射路径
RequestMapping requestMappingAnnotation =
declaredMethod.getAnnotation(RequestMapping.class);
//这里小伙伴可以把工程路径+url
//getServletContext().getContextPath()
// /springmvc/monster/list
String url = requestMappingAnnotation.value();
handlerList.add(new
WyxHandler(url,entry.getValue(),declaredMethod));
}
}
}
}
}
完成测试(启动 Tomcat)
增加方法和控制器再看看, 创建OrderController.java
@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动态获取 wyxspringmvc.xml
说明:
前面我们加载 hspspringmvc.xml 是硬编码, 现在做活, 从 web.xml 动态获取
分图析示意
-WyxDispatcherServlet 在创建并初始化 WyxWebApplicationContext,动态的从 web.xml 中获 取到配置文件
修改 WyxWebApplicationContext
public class WyxWebApplicationContext {
//定义属性classFullPathList, 保存扫描包/子包的类的全路径
private List<String> classFullPathList =
new ArrayList<>();
//定义属性ioc, 存放反射生成的Bean对象 /Controller/Service
public ConcurrentHashMap<String, Object> ioc =
new ConcurrentHashMap<>();
//无参构造器
public WyxWebApplicationContext() {
}
private String configLocation;//属性,表示spring容器配置文件
public WyxWebApplicationContext(String configLocation) {
this.configLocation = configLocation;
}
public void init() {
String basePackage =
XMLParser.getBasePackage(configLocation.split(":")[1]);
//这时basePackage => com.wyxdu.controller,com.wyxdu.service
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);//扫描
}
}
System.out.println("扫描后的= classFullPathList=" + classFullPathList);
//将扫描到的类, 反射到ico容器
executeInstance();
System.out.println("扫描后的 ioc容器= " + ioc);
}
修改WyxDispatcherServlet.java类
public void init(ServletConfig servletConfig) throws ServletException {
//获取到web.xml中的 contextConfigLocation
/*
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:wyxspringmvc.xml</param-value>
</init-param>
*/
String configLocation =
servletConfig.getInitParameter("contextConfigLocation");
//创建自己的spring容器
wyxWebApplicationContext =
new WyxWebApplicationContext(configLocation);
wyxWebApplicationContext.init();
//调用 initHandlerMapping , 完成url和控制器方法的映射
initHandlerMapping();
//输出handlerList
System.out.println("handlerList初始化的结果= " + handlerList);
}
完成测试
(启动 tomcat 方式, 修改后,redeploye 即可)
实现任务阶段 4- 完成自定义@Service 注解功能
功能说明: 如果给某个类加上@Service, 则可以将其注入到我们的 Spring 容器
分析示意图
给 Service 类标注@Service, 可以将对象注入到 Spring 容器中
- 并可以通过接口名支持多级, 类名来获取到 Service Bean
结果为
monsterService=com.wyxdu.service.impl.MonsterServiceImpl@6cc86233, monsterServiceImpl=com.wyxdu.service.impl.MonsterServiceImpl@6cc86233,
创建Monster类
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注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
创建Service接口
public interface MonsterService{
//增加方法-返回monster列表
public List<Monster> listMonster();
}
创建MonsterServiceImpl实现类
@Service
public class MonsterServiceImpl implements MonsterService {
@Override
public List<Monster> listMonsters() {
ArrayList<Monster> monsters = new ArrayList<>();
monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 500));
monsters.add(new Monster(200, "蜘蛛精", "吐口水", 200));
return monsters;
}
}
修改wyxspringmvc.xml
<?xml version="1.0" encoding="utf-8" ?>
<beans>
<!--指定要扫描的基本包以及子包的java类-->
<component-scan base-package="com.wyxdu.controller,com.wyxdu.service"></component-scan>
</beans>
修改WyxWebApplicationContext
public void executeInstance() {
//判断是否扫描到类
if (classFullPathList.size() == 0) {//说明没有扫描到类
return;
}
try {
//遍历classFullPathList,进行反射
for (String classFullPath : classFullPathList) {
Class<?> clazz = Class.forName(classFullPath);
//说明当前这个类有@Controller
if (clazz.isAnnotationPresent(Controller.class)) {
//得到类名首字母小写
String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() +
clazz.getSimpleName().substring(1);
ioc.put(beanName, clazz.newInstance());
} //如果有其它的注解,可以扩展 , 来处理@Service
else if (clazz.isAnnotationPresent(Service.class)) {//如果类有@Serivce注解
//先获取到Service的value值=> 就是注入时的beanName
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);
}
//3. 使用类名的首字母小写来注入bean
// 通过 clazz 来即可.
String beanName2 = clazz.getSimpleName().substring(0, 1).toLowerCase() +
clazz.getSimpleName().substring(1);
ioc.put(beanName2, instance);
} else {//如果有指定名称,就使用该名称注入即可
ioc.put(beanName, clazz.newInstance());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
完成测试
(启动 Tomcat, 自动加载 HspDispatcherServlet, 完成 IOC 容器的注入) 前面有结果
实现任务阶段 5- 完成 Spring 容器对象的自动装配 -@Autowried
说明: 完成 Spring 容器中对象的注入/自动装配
分析示意图
- 浏览器输入 http://localhost:8080/monster/list, 返回列表信息
● 代码实现, 说明,整个实现思路,就是参考 SpringMVC 规范
创建自定义注解AutoWired
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoWired {
String value() default "";
}
修改MonsterController
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.getSkill() + "</td><td>"
+ monster.getAge() + "</td></tr>");
}
content.append("</table>");
//获取writer返回信息
try {
PrintWriter printWriter = response.getWriter();
printWriter.write(content.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
修改WyxWebApplicationContext
public class WyxWebApplicationContext {
//定义属性classFullPathList, 保存扫描包/子包的类的全路径
private List<String> classFullPathList =
new ArrayList<>();
//定义属性ioc, 存放反射生成的Bean对象 /Controller/Service
public ConcurrentHashMap<String, Object> ioc =
new ConcurrentHashMap<>();
//无参构造器
public WyxWebApplicationContext() {
}
private String configLocation;//属性,表示spring容器配置文件
public WyxWebApplicationContext(String configLocation) {
this.configLocation = configLocation;
}
//编写方法,完成自己的spring容器的初始化
public void init() {
//这里是写的固定的spring容器配置文件.?=>做活
//String basePackage = XMLParser.getBasePackage("wyxspringmvc.xml");
String basePackage =
XMLParser.getBasePackage(configLocation.split(":")[1]);
//这时basePackage => com.wyxedu.controller,com.wyxdu.service
String[] basePackages = basePackage.split(",");
//遍历basePackages, 进行扫描
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack);//扫描
}
}
System.out.println("扫描后的= classFullPathList=" + classFullPathList);
//将扫描到的类, 反射到ico容器
executeInstance();
System.out.println("扫描后的 ioc容器= " + ioc);
//完成注入的bean对象,的属性的装配
executeAutoWired();
System.out.println("装配后 ioc容器= " + ioc);
}
}
executeAutoWired();方法调用
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();
}
}
}
}
}
启动 Tomcat, 完成测试
浏览器输入 http://localhost:8080/monster/list
实现任务阶段 6- 完成控制器方法获取参数-@RequestParam
功能说明:
自定义@RequestParam 和 方法参数名获取参数
完成任务说明
- 后端 Handler 的目标方法
@RequestMapping(value = "/monster/find")
public void findMonstersByName(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "name") String name) {
//代码....
}
完成: 将方法的 HttpServletRequest 和 HttpServletResponse 参数封装到参数数组,进行反射调用
● 代码实现, 说明,整个实现思路,就是参考 SpringMVC 规范
修改WyxDispatcherServlet.java类
private void executeDispatch(HttpServletRequest request,
HttpServletResponse response) {
WyxHandler wyxHandler = getWyxHandler(request);
try {
if (null == wyxHandler) {//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
} else {//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class<?>[] parameterTypes =
wyxHandler.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;
}
}
//wyxHandler.getMethod()
// .invoke(wyxHandler.getController(),request,response);
//反射调用目标方法
Object result = wyxHandler.getMethod()
.invoke(wyxHandler.getController(), params);
}
完成测试
(启动 tomcat), 浏览器输入 http://localhost:8080/monster/list , 仍然可以看 到正确的返回
完成: 在方法参数 指定 @RequestParam 的参数封装到参数数组,进行反射调用
- 测试页面
- 后端 Handler 的目标方法
@RequestMapping(value = "/monster/find")
public void findMonstersByName(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "name") String name) {
//代码.... }
创建自定义注解RequestParam
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "";
}
修改WyxDispatcherServlet.java接口类
public interface MonsterService {
public List<Monster> listMonsters();
public List<Monster> findMonstersByName(String name);
}
修改MonsterSer viceImpl.java实现类
@Service
public class MonsterServiceImpl implements MonsterService {
@Override
public List<Monster> listMonster() {
//这里就模拟数据->DB
List<Monster> monsters =
new ArrayList<>();
monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));
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, "牛魔王", "芭蕉扇", 400));
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
@Controller
public class MonsterController {
//@AutoWired表示要完成属性的装配.
@AutoWired
private MonsterService monsterService;
@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='400px' style='border-collapse:collapse'>");
for (Monster monster : monsters) {
content.append("<tr><td>" + monster.getId()
+ "</td><td>" + monster.getName() + "</td><td>"
+ monster.getSkill() + "</td><td>"
+ monster.getAge() + "</td></tr>");
}
content.append("</table>");
//获取writer返回信息
try {
PrintWriter printWriter = response.getWriter();
printWriter.write(content.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
修改WyxDispatcherServlet.java类
private void executeDispatch(HttpServletRequest request,
HttpServletResponse response) {
WyxHandler wyxHandler = getWyxHandler(request);
try {
if (null == wyxHandler) {//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
} else {//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class<?>[] parameterTypes =
wyxHandler.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. 获取http请求的参数集合
//解读
//http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
//2. 返回的Map<String,String[]> String:表示http请求的参数名
// String[]:表示http请求的参数值,为什么是数组
//
//处理提交的数据中文乱码
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(wyxHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) {//找到对应的位置
params[indexRequestParameterIndex] = value;
} else {
//一会写
}
/**
* 解读
* 1. 下面这样写法,其实是针对目标方法是 m(HttpServletRequest request , HttpServletResponse response)
* 2. 这里准备将需要传递给目标方法的 实参=>封装到参数数组=》然后以反射调用的方式传递给目标方法
* 3. public Object invoke(Object obj, Object... args)..
*/
//wyxHandler.getMethod()
// .invoke(wyxHandler.getController(),request,response);
//反射调用目标方法
wyxHandler.getMethod()
.invoke(wyxHandler.getController(), params);
} catch (Exception e) {
e.printStackTrace();
}
getIndexRequestParameterIndex方法
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;
}
完成测试 (Redeploy Tomcat 即 可 )
浏览器输入http://localhost:8080/monster/find?name=牛魔王
完成: 在方法参数 没有指定 @RequestParam ,按照默认参数名获取值, 进行反射调用
修改MonsterController
@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='400px' style='border-collapse:collapse'>");
for (Monster monster : monsters) {
content.append("<tr><td>" + monster.getId()
+ "</td><td>" + monster.getName() + "</td><td>"
+ monster.getSkill() + "</td><td>"
+ monster.getAge() + "</td></tr>");
}
content.append("</table>");
//获取writer返回信息
try {
PrintWriter printWriter = response.getWriter();
printWriter.write(content.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
修改WyxDispatcherServlet.java类
private void executeDispatch(HttpServletRequest request,
HttpServletResponse response) {
WyxHandler wyxHandler = getWyxHandler(request);
try {
if (null == wyxHandler) {//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
} else {//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class<?>[] parameterTypes =
wyxHandler.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. 获取http请求的参数集合
//解读
//http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
//2. 返回的Map<String,String[]> String:表示http请求的参数名
// String[]:表示http请求的参数值,为什么是数组
//
//处理提交的数据中文乱码
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(wyxHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) {//找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]
//思路
//1. 得到目标方法的所有形参的名称-专门编写方法获取形参名
//2. 对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
List<String> parameterNames =
getParameterNames(wyxHandler.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)..
*/
//wyxHandler.getMethod()
// .invoke(wyxHandler.getController(),request,response);
//反射调用目标方法
Object result = wyxHandler.getMethod()
.invoke(wyxHandler.getController(), params);
} catch (Exception e) {
e.printStackTrace();
}
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;
}
得 到 控 制 器 方 法 的 参 数 名 ,
比 如 public void findMonstersByName(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "name") String name) request, response, name
注意:
1. 在默认情况下,返回的并不是 request, response ,name 而是 arg0, arg1,arg2
2. 需要使用到 jdk8 的新特性,并需要在 pom.xml 配置 maven 编译插件(可以百度搜索到),才能得到 request, response, name
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-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 ,完成测试
后台:
如果请求参数和方法参数不一致:
实现任务阶段 7- 完成简单视图解析
功能说明:
通过方法返回的 String, 转发或者重定向到指定页面
完成任务说明
- 用户输入白骨精,可以登录成功, 否则失败
- 根据登录的结果, 可以重定向或者请求转发到 login_ok.jsp / login_error.jsp, 并显示妖怪名
思路分析示意图
修改MonsterService接口
public interface MonsterService{
//增加方法-返回monster列表
public List<Monster> listMonster();
//增加方法,通过传入的name,返回monster列表
public List<Monster> findMonsterByName(String name);
//增加方法,处理登录
public boolean login(String name);
}
修改MonsterServiceImpl实现类新增方法
@Override
public boolean login(String name) {
//实际上会到DB验证->这里模拟
if ("白骨精".equals(name)) {
return true;
} else {
return false;
}
}
修改MonsterController 增加方法
//处理妖怪登录的方法,返回要请求转发/重定向的字符串
@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";
//测试默认的方式-forward
return "/login_ok.jsp";
} else {//登录失败
return "forward:/login_error.jsp";
}
}
创建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>
修改WyxDispatcherServlet.java类
private void executeDispatch(HttpServletRequest request,
HttpServletResponse response) {
WyxHandler wyxHandler = getWyxHandler(request);
try {
if (null == wyxHandler) {//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
} else {//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class<?>[] parameterTypes =
wyxHandler.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. 获取http请求的参数集合
//解读
//http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
//2. 返回的Map<String,String[]> String:表示http请求的参数名
// String[]:表示http请求的参数值,为什么是数组
//
//处理提交的数据中文乱码
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(wyxHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) {//找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]
//思路
//1. 得到目标方法的所有形参的名称-专门编写方法获取形参名
//2. 对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
List<String> parameterNames =
getParameterNames(wyxHandler.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)..
*/
//wyxHandler.getMethod()
// .invoke(wyxHandler.getController(),request,response);
//反射调用目标方法
Object result = wyxHandler.getMethod()
.invoke(wyxHandler.getController(), params);
//这里就是对返回的结果进行解析=>原生springmvc 可以通过视图解析器来完成
//这里直接解析,只要把视图解析的核心机制讲清楚就OK
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];//是你要跳转的页面名
//判断是forward 还是 redirect
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();
}
}
完成测试
浏览器输入 测试http://localhost:8080/monster/login?mName=xxx
实现任务阶段 8- 完成返回 JSON 格式数据-@ResponseBody
功能说明:
通自定义@ResponseBody 返回 JSON 格式
分析示意图
● 代码实现
说明,整个实现思路,就是参考 SpringMVC
创建自定义注解ResponseBody
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
修改MonsterController.java类 增加方法
编写方法,返回json格式的数据
1. 目标方法返回的结果是给springmvc底层通过反射调用的位置
2. 我们在springmvc底层反射调用的位置,接收到结果并解析即可
3. 方法上标注了 @ResponseBody 表示希望以json格式返回给客户端/浏览器
4. 目标方法的实参,在springmvc底层通过封装好的参数数组,传入..
@RequestMapping("/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>
修改WyxDispatcherServlet.java类
private void executeDispatch(HttpServletRequest request,
HttpServletResponse response) {
WyxHandler wyxHandler = getWyxHandler(request);
try {
if (null == wyxHandler) {//说明用户请求的路径/资源不存在
response.getWriter().print("<h1>404 NOT FOUND</h1>");
} else {//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class<?>[] parameterTypes =
wyxHandler.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;
}
}
//处理提交的数据中文乱码
request.setCharacterEncoding("utf-8");
//将http请求参数封装到params数组中, 提示,要注意填充实参的时候,顺序问题
//1. 获取http请求的参数集合
//解读
//http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒
//2. 返回的Map<String,String[]> String:表示http请求的参数名
// String[]:表示http请求的参数值,为什么是数组
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(wyxHandler.getMethod(), name);
if (indexRequestParameterIndex != -1) {//找到对应的位置
params[indexRequestParameterIndex] = value;
} else {//说明并没有找到@RequestParam注解对应的参数,就会使用默认的机制进行配置[待..]
//思路
//1. 得到目标方法的所有形参的名称-专门编写方法获取形参名
//2. 对得到目标方法的所有形参名进行遍历,如果匹配就把当前请求的参数值,填充到params
List<String> parameterNames =
getParameterNames(wyxHandler.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)..
*/
//wyxHandler.getMethod()
// .invoke(wyxHandler.getController(),request,response);
//反射调用目标方法
Object result = wyxHandler.getMethod()
.invoke(wyxHandler.getController(), params);
//这里就是对返回的结果进行解析=>原生springmvc 可以通过视图解析器来完成
//这里直接解析,只要把视图解析的核心机制清楚就OK
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];//是你要跳转的页面名
//判断是forward 还是 redirect
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 = wyxHandler.getMethod();
if(method.isAnnotationPresent(ResponseBody.class)) {
//把result [ArrayList] 转成json格式数据-》返回
//这里我们需要使用到java中如何将 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();
}
}