手动实现SpringMVC底层机制
- 准备工作
-
- 搭建SpringMVC底层机制开发环境
- 实现任务阶段一
-
- 开发ZzwDispatcherServlet
-
- 说明: 编写ZzwDispatcherServlet充当原生的DispatcherServlet(即核心控制器)
- 分析+代码实现
- 配置Tomcat, 完成测试
- 实现任务阶段二
-
- 完成客户端/浏览器可以请求控制层
-
- 1.创建自己的Controller和自定义注解
- 2.配置zzwspringmvc.xml
- 3.编写XMLParser工具类, 可以解析zzwspringmvc.xml
- 4.开发 ZzwWebApplicationContext, 充当Spring容器-得到扫描类的全路径列表.
- 5.完善ZzwWebApplicationContext, 充当Spring容器-实例化对象到容器中
- 6.完成请求URL和控制器方法的映射关系
- 7.完成ZzwDispatcherServlet 分发请求到对应控制器方法
- 实现任务阶段三
-
- 从web.xml动态获取zzwspringmvc.xml
- 实现任务阶段四
-
- 完成自定义@Service注解功能
- 实现任务阶段五
-
- 完成Spring容器对象的自动装配-@Autowired
- 实现任务阶段六
-
- 完成控制器方法获取参数-@RequestParam
-
- 1.将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到数组, 进行反射调用
- 2.在方法形参处, 指定 @RequestParam, 将对应的实参封装到参数数组, 进行反射调用
- 3.在方法形参 没有指定 @RequestParam, 按照默认参数名获取值, 进行反射调用
- 实现任务阶段七
-
- 完成简单视图解析
- 实现任务阶段八
-
- 完成返回JSON格式数据-@ResponseBody
-
- 分析+代码实现
- 完成测试
- 总结
准备工作
搭建SpringMVC底层机制开发环境
1.创建maven-web项目
出现了点小插曲. 项目建成后, 没有src目录, 且右下角报错
Cannot find JRE '1.7
做如下修改
改成1.8
缺少的文件夹需自己手动创建
pom.xml配置
dependencies>
dependency>
groupId>junitgroupId>
artifactId>junitartifactId>
version>4.11version>
scope>testscope>
dependency>
dependency>
groupId>javax.servletgroupId>
artifactId>javax.servlet-apiartifactId>
version>3.1.0version>
scope>providedscope>
dependency>
dependency>
groupId>dom4jgroupId>
artifactId>dom4jartifactId>
version>1.6.1version>
dependency>
dependency>
groupId>org.apache.commonsgroupId>
artifactId>commons-lang3artifactId>
version>3.5version>
dependency>
dependencies>
实现任务阶段一
开发ZzwDispatcherServlet
说明: 编写ZzwDispatcherServlet充当原生的DispatcherServlet(即核心控制器)
分析+代码实现
1.com.zzw.zzwspringmvc.servlet
包下新建ZzwDispatcherServlet.java
/**
* 解读
* 1.ZzwDispatcherServlet 充当原生的DispatcherServlet
* 2.本质是一个Servlet, 继承HttpServlet
*/
public class ZzwDispatcherServlet 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);
}
}
2.src/main/resources
(类路径)下新建 zzwspringmvc.xml
, spring的容器配置文件
对应的类路径
3.webapp/WEB-INF
配置web.xml
load-on-startup讲解
servlet>
servlet-name>ZzwDispatcherServletservlet-name>
servlet-class>com.zzw.zzwspringmvc.servlet.ZzwDispatcherServletservlet-class>
init-param>
param-name>contextConfigLocationparam-name>
param-value>classpath:zzwspringmvc.xmlparam-value>
init-param>
load-on-startup>1load-on-startup>
servlet>
servlet-mapping>
servlet-name>ZzwDispatcherServletservlet-name>
url-pattern>/url-pattern>
servlet-mapping>
配置Tomcat, 完成测试
1.配置tomcat
2.更改ZzwDispatcherServlet
代码, 启动Tomcat
, 开始测试
public class ZzwDispatcherServlet extends HttpServlet {
@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("ZzwDispatcherServlet doPost()...");
}
}
随便请求一个网址
实现任务阶段二
完成客户端/浏览器可以请求控制层
1.创建自己的Controller和自定义注解
1.创建自己的Controller
和自定义注解
1.1在com.zzw.controller
下新建MonsterController
public class MonsterController {
//编写方法, 可以列出怪物列表
//springmvc 是支持原生的servlet api, 为了看到底层机制
//这里我们涉及两个参数
public void listMonster(HttpServletRequest request, HttpServletResponse response) {
//设置返回编码和返回类型
response.setContentType("text/html;charset=utf-8");
//获取writer返回信息
PrintWriter writer = null;
try {
writer = response.getWriter();
} catch (IOException e) {
throw new RuntimeException(e);
}
writer.print("妖怪名信息: 孙悟空--猪八戒--沙僧
");
writer.flush();
writer.close();
}
}
1.2在com.zzw.zzwspringmvc.annotation
下新建注解类Controller
RetentionPolicy.RUNTIME: 编译器把注解记录在class文件中, 当运行Java程序时, JVM 会保留注解. 程序可以通过反射获取该注解
/**
* @author 赵志伟
* @version 1.0
* 该注解用于标识一个控制器组件
* 这里涉及到注解知识, 在java基础
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
1.3在该包下新建注解类RequestMapping
/**
* @author 赵志伟
* @version 1.0
* RequestMapping 注解用于指定控制器-方法的映射路径
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";;
}
1.4在MonsterController
中添加注解
@Controller
public class MonsterController {
//编写方法, 可以列出怪物列表
//springmvc 是支持原生的servlet api, 为了看到底层机制
//这里我们涉及两个参数
@RequestMapping(value = "/monster/list")
public void listMonster(HttpServletRequest request, HttpServletResponse response) {
//设置返回编码和返回类型
response.setContentType("text/html;charset=utf-8");
//获取writer返回信息
try {
response.getWriter().write("妖怪名信息: 孙悟空--猪八戒--沙僧
");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2.配置zzwspringmvc.xml
2.配置zzwspringmvc.xml
后面别忘了测试Service
beans>
component-scan base-package="com.zzw.controller"/>
beans>
3.编写XMLParser工具类, 可以解析zzwspringmvc.xml
3.在com.zzw.zzwspringmvc.xml
编写XMLParser
工具类, 可以解析zzwspringmvc.xml
, 得到要扫描的包
Dom4j解析配置文件代码实现
/**
* @author 赵志伟
* @version 1.0
* XMLParser 用于解析spring配置文件
*/
@SuppressWarnings({"all"})
public class XMLParser {
public static String getBasePackage(String xmlFile) {
//1.得到解析器
SAXReader reader = new SAXReader();
//2.得到类的加载路径 => 获取到spring配置文件[对应的资源流]
InputStream inputStream =
XMLParser.class.getClassLoader().getResourceAsStream(xmlFile);
try {
//3.得到xml文件的文档
Document document = reader.read(inputStream);
//4.获取rootElement
Element rootElement = document.getRootElement();
//5.获取component-scan节点
Element componentScanElement =
(Element) rootElement.elements("component-scan").get(0);
//6.获取component-scan节点的base-package属性值
String basePackage = componentScanElement.attributeValue("base-package");
//7.返回
return basePackage;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
3.1.在com.zzw.test
新建ZzwSpringMVCTest.java
测试类
XMLParser类
在很多包下都有, 别选错
public class ZzwSpringMVCTest {
@Test
public void readXML() {
String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
System.out.println(basePackage);
}
}
4.开发 ZzwWebApplicationContext, 充当Spring容器-得到扫描类的全路径列表.
4.开发ZzwWebApplicationContext.java
, 充当Spring
容器. 得到扫描类的全路径列表.
把指定的目录包括子目录下的java
类的全路径扫描到集合中, 比如 ArrayList
[java基础]
在com.zzw.zzwspringmvc.context
下新建ZzwWebApplicationContext.java
/**
* @author 赵志伟
* @version 1.0
* ZzwWebApplicationContext 表示我们自己的spring容器
*/
@SuppressWarnings({"all"})
public class ZzwWebApplicationContext {
//定义属性classFullPathList, 保存扫描包/子包的类的全路径
private ListString> classFullPathList =
new ArrayListString>();
//编写方法, 完成自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
scanPackage(basePackage);
}
/**
* 创建方法, 完成对包的扫描->涉及 io/容器/字符串处理
* @param pack 表示要扫描的包, 比如 com.zzw.controller
*/
public void scanPackage(String pack) {
//得到包所在的工作路径[对应的绝对路径]
ClassLoader classLoader = this.getClass().getClassLoader();
//下面这句话的含义是服务器托管网 通过类的加载器, 得到指定的包对应的工作路径的绝对路径
//比如 com.zzw.controller => resource = file:/D:/idea_project/zzw_springmvczzw-springmvc/target/classes/com/zzw/controller
URL resource = classLoader.getResource(pack.replace(".", "/"));
//细节说明:
// 1.不要直接使用Junit测试, 否则 url返回null
// 2.启动Tomcat测试, 才能得到这个类路径
System.out.println("resource=" + resource);
}
}
4.1前端控制器ZzwDispatcherServlet
增加init
方法
/**
* 解读
* 1.ZzwDispatcherServlet 充当原生的DispatcherServlet
* 2.本质是一个Servlet, 继承HttpServlet
*/
public class ZzwDispatcherServlet extends HttpServlet {
@Override
public void init() throws ServletException {
ZzwWebApplicationContext zzwWebApplicationContext =
new ZzwWebApplicationContext();
zzwWebApplicationContext.init();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ZzwDispatcherServlet doGet()...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ZzwDispatcherServlet doPost()...");
}
}
启动Tomcat, 访问http://localhost:8080/zzw_springmvc2/monster/list, 进行测试
4.2开发自己的spring容器
/**
* @author 赵志伟
* @version 1.0
* ZzwWebApplicationContext 表示我们自己的spring容器
*/
@SuppressWarnings({"all"})
public class ZzwWebApplicationContext {
//定义属性classFullPathList, 保存扫描包/子包的类的全路径
private ListString> classFullPathList =
new ArrayListString>();
//编写方法, 完成自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
scanPackage(basePackage);
//classFullPathList=[com.zzw.controller.MonsterController,
// com.zzw.controller.xx.GoodsController,
// com.zzw.controller.xx.OrderController]
System.out.println("classFullPathList=" + classFullPathList);
}
/**
* 创建方法, 完成对包的扫描->涉及 io/容器/字符串处理
*
* @param pack 表示要扫描的包, 比如 com.zzw.controller
*/
public void scanPackage(String pack) {
//得到包所在的工作路径[对应的绝对路径]
ClassLoader classLoader = this.getClass().getClassLoader();
//下面这句话的含义是 通过类的加载器, 得到指定的包对应的工作路径的绝对路径
//比如 com.zzw.controller => resource = file:/D:/idea_project/zzw_springmvc/zzw-springmvc/target/zzw-springmvc/WEB-INF/classes/com/zzw/controller/
URL resource = classLoader.getResource(pack.replace(".", "/"));
//细节说明:
// 1.不要直接使用Junit测试, 否则 url返回null
// 2.启动Tomcat测试, 才能得到这个类路径
System.out.println("resource=" + resource);
//根据得到的路径, 对其进行扫描, 把类的全路径保存到classFullPathList
// path = D:/idea_project/zzw_springmvc/zzw-springmvc/target/zzw-springmvc/WEB-INF/classes/com/zzw/controller/
String path = resource.getFile();
File dir = new File(path);//在io中, 目录也是文件
//遍历dir[文件/子目录]
for (File f : dir.listFiles()) {
if (f.isDirectory()) {//如果是一个目录, 需要递归扫描
scanPackage(pack + "." + f.getName());//f.getName() 子包的名称
} else {
//说明: 这时, 你扫描到的文件, 可能是.class文件, 也可以是其它文件
// 就算是.class文件, 也存在是不是需要注入到容器中的问题
// 目前先把所有.class文件的全路径都保存到集合中, 后面在注入对象到容器时, 再处理
// 这里只考虑 .class文件
String classFullPath = pack + "." + f.getName().replaceAll(".class", "");
classFullPathList.add(classFullPath);
}
}
}
}
4.3在Controller
包下随机建立子包
以及Controller
, 重启Tomcat
, 测试
5.完善ZzwWebApplicationContext, 充当Spring容器-实例化对象到容器中
5.完善ZzwWebApplicationContext
, 充当Spring容器, 实例化对象到容器中
将扫描到的类, 在满足条件的情况下(即有相应的注解@Controller @Service...
)
增加ioc
容器
增加executeInstance
方法
/**
* @author 赵志伟
* @version 1.0
* ZzwWebApplicationContext 表示我们自己的spring容器
*/
@SuppressWarnings({"all"})
public class ZzwWebApplicationContext {
//定义属性classFullPathList, 保存扫描包/子包的类的全路径
private ListString> classFullPathList =
new ArrayListString>();
//定义属性ioc, 存放反射生成的bean对象 Controller/Service
public ConcurrentHashMapString, Object> ioc =
new ConcurrentHashMapString, Object>();
//编写方法, 完成自己的spring容器的初始化
public void init() {
String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
scanPackage(basePackage);
//classFullPathList=[com.zzw.controller.MonsterController,
// com.zzw.controller.xx.GoodsController,
// com.zzw.controller.xx.OrderController]
System.out.println("classFullPathList=" + classFullPathList);
//将扫描到的类, 反射到ioc容器
executeInstance();
System.out.println("扫描后的 ioc容器 " + ioc);
}
/**
* 创建方法, 完成对包的扫描->涉及 io/容器/字符串处理
*
* @param pack 表示要扫描的包, 比如 com.zzw.controller
*/
public void scanPackage(String pack) {
//得到包所在的工作路径[对应的绝对路径]
ClassLoader classLoader = this.getClass().getClassLoader();
//下面这句话的含义是 通过类的加载器, 得到指定的包对应的工作路径的绝对路径
//比如 com.zzw.controller => resource = file:/D:/idea_project/zzw_springmvc/zzw-springmvc/target/zzw-springmvc/WEB-INF/classes/com/zzw/controller/
URL resource = classLoader.getResource(pack.replace(".", "/"));
//细节说明:
// 1.不要直接使用Junit测试, 否则 url返回null
// 2.启动Tomcat测试, 才能得到这个类路径
System.out.println("resource=" + resource);
//根据得到的路径, 对其进行扫描, 把类的全路径保存到classFullPathList
// path = D:/idea_project/zzw_springmvc/zzw-springmvc/target/zzw-springmvc/WEB-INF/classes/com/zzw/controller/
String path = resource.getFile();
File dir = new File(path);//在io中, 目录也是文件
//遍历dir[文件/子目录]
for (File f : dir.listFiles()) {
if (f.isDirectory()) {//如果是一个目录, 需要递归扫描
scanPackage(pack + "." + f.getName());//f.getName() 子包的名称
} else {
//说明: 这时, 你扫描到的文件, 可能是.class文件, 也可以是其它文件
// 就算是.class文件, 也存在是不是需要注入到容器中的问题
// 目前先把所有.class文件的全路径都保存到集合中, 后面在注入对象到容器时, 再处理
// 这里只考虑 .class文件
String classFullPath = pack + "." + f.getName().replaceAll(".class", "");
classFullPathList.add(classFullPath);
}
}
}
//编写方法, 将扫描到的类, 在满足条件的情况下, 反射到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)) {
//beanName 假设是默认的, 即类名首字母小写
String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1);
ioc.put(beanName, clazz.newInstance());
}//如果有其它注解, 可以拓展!!
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
我这里输出的时候乱码, 我的解决方案是. 全改成UTF-8
6.完成请求URL和控制器方法的映射关系
6.完成请求URL和控制器方法的映射关系
将配置的@RequestMapping
的url
和 对应的 控制器-方法 映射关系保存到集合中
6.1在com.zzw.zzwspringmvc.handler
下新建ZzwHandler
/**
* @author 赵志伟
* @version 1.0
* ZzwHandler 对象记录请求的url 和 控制器方法映射关系
*/
@SuppressWarnings({"all"})
public class ZzwHandler {
private String url;
private Object controller;
private Method method;
public ZzwHandler(String url, Object controller, Method method) {
this.url = url;
this.controller = controller;
this.method = method;
}
//getter, setter, toString方法
}
6.2修改ZzwDispatcherServlet
将init
方法内声明的zzwWebApplicationContext
属性提到外面, 扩大它的作用域
定义属性 handlerList
, 保存ZzwHandler
[url和控制器方法的映射关系]
编写方法[initHandlerMapping], 完成url 和 控制器方法的映射 (initHandlerMapping也可以写在HandlerMapping类中, 逻辑是一样的
判断方法上有没有某种注解
- 示意图分析
/**
* 解读
* 1.ZzwDispatcherServlet 充当原生的DispatcherServlet
* 2.本质是一个Servlet, 继承HttpServlet
* 3.提示: 这里我们需要使用到 java web 讲解的Servlet
*/
public class ZzwDispatcherServlet extends HttpServlet {
//定义属性 handlerList, 保存ZzwHandler[url和控制器方法的映射关系]
private ListZzwHandler> handlerList
= new ArrayListZzwHandler>();
//定义属性 zzwWebApplicationContext, 自己的spring容器
ZzwWebApplicationContext zzwWebApplicationContext = null;
@Override
public void init() throws ServletException {
zzwWebApplicationContext = new ZzwWebApplicationContext();
zzwWebApplicationContext.init();
//调用 initHandlerMapping, 完成url和控制器方法的映射
initHandlerMapping();
System.out.println("handlerList初始化的结果=" + handlerList);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ZzwDispatcherServlet doGet()...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ZzwDispatcherServlet doPost()...");
}
//编写方法, 完成url 和 控制器方法的映射
private void initHandlerMapping() {
if (zzwWebApplicationContext.ioc.isEmpty()) {
//判断当前的ioc容器是否为空
return;
}
//遍历ioc容器的bean对象, 然后进行url映射处理
//java基础 map遍历
for (Map.EntryString, Object> entry : zzwWebApplicationContext.ioc.entrySet()) {
//先取出实例的clazz对象[要获取类的内部信息, 类的实例对象不好用, 要用类的Class对象 反射知识]
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.getDeclaredAnnotation(RequestMapping.class);
String url = requestMappingAnnotation.value();
//创建ZzwHandler对象->就是一个映射关系
//我自己的理解:
//一个zzwHanler对象存放一个Controller对象, 以及这个Controller对象的一个方法, 以及这个方法的注解值url
ZzwHandler zzwHandler =
new ZzwHandler(url, entry.getValue(), declaredMethod);
//放入到 handlerList
handlerList.add(zzwHandler);
}
}
}
}
}
}
7.完成ZzwDispatcherServlet 分发请求到对应控制器方法
功能说明: 完成ZzwDispatcherServlet 分发请求到对应控制器方法
-
示意图[分析说明]
-
当用户发出请求, 根据用户请求url 找到对应的 控制器-方法, 并反射调用
-
如果用户请求的路径不存在, 返回404
ZzwDispatcherServlet
添加getZzwHandler()
方法和executeDispatcher()
方法, 在doPost
中调用 executeDispatcher()
方法
request.getRequestURI();
在听课时, 这句代码不太理解
/**
* 解读
* 1.ZzwDispatcherServlet 充当原生的DispatcherServlet
* 2.本质是一个Servlet, 继承HttpServlet
* 3.提示: 这里我们需要使用到 java web 讲解的Servlet
*/
public class ZzwDispatcherServlet extends HttpServlet {
//定义属性 handlerList, 保存ZzwHandler[url和控制器方法的映射关系]
private ListZzwHandler> handlerList
服务器托管网 = new ArrayListZzwHandler>();
//定义属性 zzwWebApplicationContext, 自己的spring容器
ZzwWebApplicationContext zzwWebApplicationContext = null;
@Override
public void init() throws ServletException {
//创建自己的spring容器
zzwWebApplicationContext = new ZzwWebApplicationContext();
zzwWebApplicationContext.init();
//调用 initHandlerMapping, 完成url和控制器方法的映射
initHandlerMapping();
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("ZzwDispatcherServlet doPost()...");
//调用方法, 完成请求转发
executeDispatcher(req, resp);
}
//编写方法, 完成url 和 控制器方法的映射
private void initHandlerMapping() {
if (zzwWebApplicationContext.ioc.isEmpty()) {
//判断当前的ioc容器是否为空
return;
}
//遍历ioc容器的bean对象, 然后进行url映射处理
//java基础 map遍历
for (Map.EntryString, Object> entry : zzwWebApplicationContext.ioc.entrySet()) {
//先取出实例的clazz对象[要获取类的内部信息, 类的实例对象不好用, 要用类的Class对象 反射知识]
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.getDeclaredAnnotation(RequestMapping.class);
//这里小伙伴可以工程路径+url
// getServletContext().getContextPath() + requestMappingAnnotation.value();
String url = requestMappingAnnotation.value();
//创建ZzwHandler对象->就是一个映射关系
//我自己的理解:
//一个zzwHanler对象存放一个Controller对象, 以及这个Controller对象的一个方法, 以及这个方法的注解值url
ZzwHandler zzwHandler =
new ZzwHandler(url, entry.getValue(), declaredMethod);
//放入到 handlerList
handlerList.add(zzwHandler);
}
}
}
}
}
//编写方法, 通过request对象, 返回ZzwHandler对象
//如果没有, 就返回null
private ZzwHandler getZzwHandler(HttpServletRequest request) {
//1.先获取到用户请求的url 比如http://localhost:8080/zzw_springmvc/monster/list
// uri = /zzw_springmvc/monster/list
//2.这里要注意得到的uri 和 保存的url 有一个工程路径的问题
//两个方案解决 =>第一个方案: 简单 tomcat 直接配置 application context => /
// 第二个方案: 保存 zzwHandler对象 url, 拼接 this.getServletContext().getContextPath()
// 一个重点: 要想在其他地方调用getServletContext()或者getServletConfig(), 需要在init()方法里调用super.init(servletConfig);
String requestURI = request.getRequestURI();
//遍历 handlerList
for (ZzwHandler zzwHandler : handlerList) {
if (requestURI.equals(zzwHandler.getUrl())) {//说明匹配成功
return zzwHandler;
}
}
return null;
}
//编写方法, 完成分发请求任务
private void executeDispatcher(HttpServletRequest request,
HttpServletResponse response) {
try {
ZzwHandler zzwHandler = getZzwHandler(request);
if (zzwHandler == null) {//说明用户请求的路径/资源不存在
response.getWriter().print("404 NOT FOUND!
");
} else {//匹配成功, 反射调用控制器的方法
zzwHandler.getMethod().
invoke(zzwHandler.getController(), request, response);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
OrderController
增加两个方法listOrder(), addOrder()
@Controller
public class OrderController {
@RequestMapping(value = "/order/list")
public void listOrder(HttpServletRequest request, HttpServletResponse response) {
//设置返回编码和返回类型
response.setContentType("text/html;charset=utf8");
//获取writer返回信息
try {
response.getWriter().write("订单列表信息
");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@RequestMapping(value = "/order/add")
public void addOrder(HttpServletRequest request, HttpServletResponse response) {
//设置返回编码和返回类型
response.setContentType("text/html;charset=utf8");
//获取writer返回信息
PrintWriter writer = null;
try {
writer = response.getWriter();
} catch (IOException e) {
throw new RuntimeException(e);
}
writer.print("添加订单...
");
writer.flush();
writer.close();
}
}
GoodsController
增加一个方法listGoods()
@Controller
public class GoodsController {
@RequestMapping(value = "/goods/list")
public void listGoods(HttpServletRequest request, HttpServletResponse response) {
//设置返回编码和返回类型
response.setContentType("text/html;charset=utf8");
//获取writer返回信息
try {
response.getWriter().write("添加订单...
");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
测试
实现任务阶段三
从web.xml动态获取zzwspringmvc.xml
说明: 前面我们加载zzwspringmvc.xml是硬编码, 现在做活. 从web.xml动态获取
示意图[分析说明]
1.ZzwDispatcherServlet
在创建并初始化ZzwWebApplicationContext
时, 动态地从web.xml
中获取到spring
配置文件.
servletConfig使用
/**
* 解读
* 1.ZzwDispatcherServlet 充当原生的DispatcherServlet
* 2.本质是一个Servlet, 继承HttpServlet
* 3.提示: 这里我们需要使用到 java web 讲解的Servlet
*/
public class ZzwDispatcherServlet extends HttpServlet {
//定义属性 handlerList, 保存ZzwHandler[url和控制器方法的映射关系]
private ListZzwHandler> handlerList
= new ArrayListZzwHandler>();
//定义属性 zzwWebApplicationContext, 自己的spring容器
ZzwWebApplicationContext zzwWebApplicationContext = null;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//获取到web.xml中的
/*
contextConfigLocation
classpath:zzwspringmvc.xml
*/
String configLocation = servletConfig.getInitParameter("contextConfigLocation");
//创建自己的spring容器
zzwWebApplicationContext = new ZzwWebApplicationContext(configLocation);
zzwWebApplicationContext.init();
//调用 initHandlerMapping, 完成url和控制器方法的映射
initHandlerMapping();
System.out.println("handlerList初始化的结果=" + handlerList);
}
.......
}
2.ZzwWebApplicationContext.java
中添加一个属性configLocation, 和一个无参构造器, 一个有参构造器, 并修改init()方法
/**
* @author 赵志伟
* @version 1.0
* ZzwWebApplicationContext 表示我们自己的spring容器
*/
public class ZzwWebApplicationContext {
//定义属性classFullPath, 保存扫描包/子包的类的全路径
private ListString> classFullPathList
= new ArrayListString>();
//定义属性ioc, 存放反射生成的bean对象 有Controller/Service注解
public ConcurrentHashMapString, Object> ioc
= new ConcurrentHashMapString, Object>();
//添加一个无参构造器
public ZzwWebApplicationContext() {
}
//创建一个属性, 表示spring容器配置文件
private String configLocation;
//构建一个有参构造器
public ZzwWebApplicationContext(String configLocation) {
this.configLocation = configLocation;
}
//编写方法, 完成自己的spring容器的初始化
public void init() {
//这里我们写的是固定的spring容器配置文件
//String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
String basePackage = XMLParser.getBasePackage(configLocation.split(":")[1]);
scanPackage(basePackage);
System.out.println("basePackage=" + basePackage);
System.out.println("classFullPathList=" + classFullPathList);
//将扫描到的类, 反射到ioc容器
executeInstance();
/*
扫描后的 ioc容器={goodsController=com.zzw.controller.xx.GoodsController@3d4ca903,
orderController=com.zzw.controller.xx.OrderController@76ce738c,
monsterController=com.zzw.controller.MonsterController@bceda48}
*/
System.out.println("扫描后的 ioc容器=" + ioc);
}
........
}
测试…
实现任务阶段四
完成自定义@Service注解功能
说明: 如果给某个类加上@Service
, 则可以将其注入到我们的Spring容器
示意图[分析说明]
补充: DAO和DB由MyBatis接管, 和SpringMVC关系并不大. 所以我们暂时不考虑DAO和DB.
1.在com.zzw.entity
包下新建Monster
public class Monster {
private Integer id;
private String name;
private String skill;
private Integer age;
//全参构造器, getter, setter, toString方法
}
2.在com.zzw.zzwspringmvc.annotation
下新建@Service
. 这个注解是springmvc框架要支持的东西, 所以要在zzwspringmvc包下
/**
* @author 赵志伟
* @version 1.0
* Service 注解, 用于标识一个Service对象, 并注入到spring容器
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
3.在com.zzw.service
下新建MonsterService接口
.
public interface MonsterService {
//增加方法-返回monster列表
public ListMonster> listMonster();
}
3.1在com.zzw.service.impl
新建MonsterServiceImpl实现类
. 并标注@Service
, 可以将对象注入到Spring
容器
/**
* @author 赵志伟
* @version 1.0
* MonsterServiceImpl 作为一个Service注入到spring容器
*/
@SuppressWarnings({"all"})
public class MonsterServiceImpl implements MonsterService {
//这里我们模拟数据->DB
public ListMonster> listMonster() {
ListMonster> monsters = new ArrayListMonster>();
monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));
monsters.add(new Monster(200, "汤姆猫", "抓老鼠", 200));
return monsters;
}
}
3.2完善zzwspringmvc.xml
, 加上com.zzw.service
beans>
component-scan base-package="com.zzw.controller, com.zzw.service"/>
beans>
3.3更改ZzwWebApplicationContext.java
的init()
//编写方法, 完成自己的spring容器的初始化
public void init() {
//这里我们写的是固定的spring容器配置文件 => 做活
//String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
String basePackage =
XMLParser.getBasePackage(configLocation.split(":")[1]);
//这时basePackages => com.zzw.controller, com.zzw.service
String[] basePackages = basePackage.split(",");
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack.trim());
}
}
........
}
4.ZzwWebApplicationContext
的executeInstance
增加一个else if分支. 并可以通过接口支持多级-类名来获取到Service Bean
//编写方法, 将扫描到的类, 在满足条件的情况下, 反射到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)) {
//beanName 假设是默认的, 即类名首字母小写
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值 => 就是注入时的beanName
Service serviceAnnotation = clazz.getDeclaredAnnotation(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) {
throw new RuntimeException(e);
}
}
5.测试-重启tomcat
扫描后的 ioc容器={goodsController=com.zzw.controller.xx.GoodsController@5fb9a20e, monsterService=com.zzw.service.impl.MonsterServiceImpl@3b03f989, monsterServiceImpl=com.zzw.service.impl.MonsterServiceImpl@3b03f989, orderController=com.zzw.controller.xx.OrderController@2f51e8b1, monsterController=com.zzw.controller.MonsterController@7a223f3b}
handlerList初始化的结果=
[ZzwHandler{url=‘/goods/list’, controller=com.zzw.controller.xx.GoodsController@79b1752f, method=public void com.zzw.controller.xx.GoodsController.listGoods(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
ZzwHandler{url=‘/order/add’, controller=com.zzw.controller.xx.OrderController@1b82cb63, method=public void com.zzw.controller.xx.OrderController.addOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
ZzwHandler{url=‘/order/list’, controller=com.zzw.controller.xx.OrderController@1b82cb63, method=public void com.zzw.controller.xx.OrderController.listOrder(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)},
ZzwHandler{url=‘/monster/list’, controller=com.zzw.controller.MonsterController@32128628, method=public void com.zzw.controller.MonsterController.listMonster(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)}]
实现任务阶段五
完成Spring容器对象的自动装配-@Autowired
说明: 完成Spring容器中对象的注入/自动装配
示意图[分析说明]
分析:
加入@Autowired
注解, 进行对象属性的装配. -如图
测试:
浏览器输入 http://localhost:8080/monster/list
, 返回列表信息
代码实现:
1.在com.zzw.zzwspringmvc.annotation
下新建@Autowired
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String value() default "";
}
1.1MonsterController
添加属性monsterService
, 标注@Autowired
@Controller
public class MonsterController {
//@Autowired表示要完成属性的装配.
@Autowired
private MonsterService monsterService;
.....
}
2.ZzwWebApplicationContext.java
增加方法executeAutowired()
//编写方法, 完成属性的自动装配
public void executeAutowired() {
//判断ioc有没有要装配的对象
if (ioc.isEmpty()) {
return;//你也可以抛出异常, throw new RuntimeException("ioc 容器没有bean对象")
}
//遍历ioc容器中的所有注入的bean对象, 然后获取到bean的所有字段/属性, 判断是否需要装配
/**
* entry => -> String 就是你注入对象时的名称, Object就是bean对象
*/
for (Map.EntryString, 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.getDeclaredAnnotation(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 (ioc.get(beanName) == null) {//说明你指定的名字对应的bean不在ioc容器
throw new RuntimeException("ioc容器中, 不存在你要装配的bean");
}
//防止属性是private, 我们需要暴力破解
declaredField.setAccessible(true);
try {
declaredField.set(bean, ioc.get(beanName));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
3.ZzwWebApplicationContext.java
的init()
方法的最后添加三行代码
//编写方法, 完成自己的spring容器的初始化
public void init() {
//这里我们写的是固定的spring容器配置文件 => 做活
//String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
String basePackage =
XMLParser.getBasePackage(configLocation.split(":")[1]);
//这时basePackages => com.zzw.controller, com.zzw.service
String[] basePackages = basePackage.split(",");
if (basePackages.length > 0) {
for (String pack : basePackages) {
scanPackage(pack.trim());
}
}
System.out.println("basePackage=" + basePackage);
System.out.println("classFullPathList=" + classFullPathList);
//将扫描到的类, 反射到ioc容器
executeInstance();
/*
扫描后的 ioc容器={goodsController=com.zzw.controller.xx.GoodsController@3d4ca903,
orderController=com.zzw.controller.xx.OrderController@76ce738c,
monsterController=com.zzw.controller.MonsterController@bceda48}
*/
System.out.println("扫描后的 ioc容器=" + ioc);
//完成注入的bean对象,的属性的装配
executeAutowired();
System.out.println("装配后 ioc容器=" + ioc);
3.打断点, debug, 重启tomcat
4.修改MonsterController
的listMonster
方法
@RequestMapping(value = "/monster/list")
public void listMonster(HttpServletRequest request, HttpServletResponse response) {
//设置返回编码和返回类型
response.setContentType("text/html;charset=utf-8");
StringBuilder content = new StringBuilder("妖怪列表信息
");//单线程使用StringBuilder
content.append("");
//调用monsterService
ListMonster> monsters = monsterService.listMonster();
for (Monster monster : monsters) {
content.append("
" + monster.getId() + "
" + monster.getName() +
"
" + monster.getAge() + "
" + monster.getSkill() + "
");
}
content.append("
");
//获取writer返回信息
try {
response.getWriter().write(content.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
5.重启tomcat
, 浏览器输入 http://localhost:8080/monster/list
实现任务阶段六
完成控制器方法获取参数-@RequestParam
功能说明: 自定义@RequestParam 和 方法参数名获取参数
完成: 将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到数组, 进行反射调用
完成: 在方法参数 指定 @RequestParam 的参数封装到参数数组, 进行反射调用
完成: 在方法参数 没有指定 @RequestParam, 按照默认参数名获取值, 进行反射调用
示意图[分析说明]
1.将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到数组, 进行反射调用
修改ZzwDispatcherServlet
的executeDispatcher()
方法
//编写方法, 完成分发请求任务
private void executeDispatcher(HttpServletRequest request,
HttpServletResponse response) {
try {
ZzwHandler zzwHandler = getZzwHandler(request);
if (zzwHandler == null) {//说明用户请求的路径/资源不存在
response.getWriter().print("404 NOT FOUND!
");
} else {//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse 封装到参数数组
//1. 得到目标方法的所有形参参数信息[返回对应的数组]
Class?>[] parameterTypes =
zzwHandler.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)
* zzwHandler.getMethod()
* .invoke(zzwHandler.getController(), request, response)
* 2.这里我们准备将需要传递给目标方法的 实参=> 封装到参数数组=> 然后以反射调用的方式传递给目标方法
* public Object invoke(Object var1, Object... var2)...
*/
zzwHandler.getMethod().
invoke(zzwHandler.getController(), params);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
2.在方法形参处, 指定 @RequestParam, 将对应的实参封装到参数数组, 进行反射调用
1.MonsterService
新添方法findMonsterByName
public interface MonsterService {
//增加方法-返回monster列表
public ListMonster> listMonster();
//增加方法, 通过传入的name, 返回对应的monster列表
public ListMonster> findMonsterByName(String name);
}
MonsterServiceImpl
将其实现
@Service
public class MonsterServiceImpl implements MonsterService {
public ListMonster> findMonsterByName(String name) {
//这里我们模拟数据->DB
ListMonster> monsters = new ArrayListMonster>();
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集合
ListMonster> findMonsters = new ArrayListMonster>();
//遍历monsters集合, 返回满足条件的对象
for (Monster monster : monsters) {
if (monster.getName().contains(name)) {
findMonsters.add(monster);
}
}
return findMonsters;
}
}
2.com.zzw.zzwspringmvc.annotation
下新建@RequestParam
/**
* @author 赵志伟
* @version 1.0
* RequestParam 注解 标注在目标方法的参数上, 表示对应http请求的参数
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)//runtime表示在反射时可以拿到这个注解
@Documented
public @interface RequestParam {
String value() default "";
}
3.ZzwDispatcherServlet
增添代码
1)method.getParameters():
得到所有的形参
2)method.getParameterTypes():
得到形参的类型
//编写方法, 完成分发请求任务
private void executeDispatcher(HttpServletRequest request,
HttpServletResponse response) {
try {
ZzwHandler zzwHandler = getZzwHandler(request);
if (zzwHandler == null) {//说明用户请求的路径/资源不存在
response.getWriter().print("404 NOT FOUND!
");
} else {//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse 封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class?>[] parameterTypes =
zzwHandler.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请求的参数集合
//老韩解读
//2.返回的Map String: 表示http请求的参数名
// String[]: 表示http请求的参数值, 想一下为什么是数组?
//http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒&hobby=吃肉(防止有类似checkbox)
MapString, String[]> parameterMap =
request.getParameterMap();
//2.遍历parameterMap, 将请求参数, 按照顺序填充到实参数组
for (Map.EntryString, String[]> entry : parameterMap.entrySet()) {
//取出key. 这个name就是对应请求的参数名
String name = entry.getKey();
//说明: 只考虑提交的数据是单值的情况, 即不考虑类似checkbox提交的数据
// 老师这里做了简化, 如果考虑多值情况, 也不难
String value = entry.getValue()[0];
//我们得到请求的参数对应我们目标方法的第几个形参, 然后将其填充
//这里专门编写一个方法, 得到请求参数对应的是第几个形参
//1. API 2.java内力真正增加 3.老韩忠告..
int indexRequestParameterIndx =
getIndexRequestParameterIndex(zzwHandler.getMethod(), name);
if (indexRequestParameterIndx != -1) {//找到对应的位置
params[indexRequestParameterIndx] = value;
} else {//说明并没有找到@RequestParam注解对应的参数, 就会使用默认的机制进行匹配[待...]
//一会再写
}
}
/**
* 1.下面这样写, 其实是针对目标方法 m(HttpServletRequest request, HttpServletResponse response)
* zzwHandler.getMethod()
* .invoke(zzwHandler.getController(), request, response)
* 2.这里我们准备将需要传递给目标方法的 实参=> 封装到参数数组=> 然后以反射调用的方式传递给目标方法
* public Object invoke(Object var1, Object... var2)...
*/
zzwHandler.getMethod().
invoke(zzwHandler.getController(), params);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//编写方法, 返回请求参数是目标方法的第几个形参
/**
* @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.MonsterController
增加如下方法
//增加方法, 通过name返回对应的monster对象
@RequestMapping(value = "/monster/find")
public void findMonsterByName(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "name") String name) {
//设置返回编码和返回类型
response.setContentType("text/html;charset=utf8");
System.out.println("---接收到的name---" + name);
StringBuilder content = new StringBuilder("妖怪列表信息
");//单线程使用StringBuilder
content.append("");
//调用monsterService
ListMonster> monsters = monsterService.findMonsterByName(name);
for (Monster monster : monsters) {
content.append("
" + monster.getId() + "
" + monster.getName() + "
"
+ monster.getAge() + "
" + monster.getSkill() + "
");
}
content.append("
");
//获取writer返回信息
try {
PrintWriter writer = response.getWriter();
writer.print(content.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
5.测试
3.在方法形参 没有指定 @RequestParam, 按照默认参数名获取值, 进行反射调用
1.去掉MonsterController
的findMonsterByName()
方法的name
字段的@RequestParam注解
//增加方法, 通过name返回对应的monster对象
@RequestMapping(value = "/monster/find")
public void findMonsterByName(HttpServletRequest request,
HttpServletResponse response,
String name) {
.....
}
2.ZzwDispatcherServlet
的executeDispatcher()
的else分支
内增加如下代码, 并增加方法getParameterNames
//编写方法, 完成分发请求任务
private void executeDispatcher(HttpServletRequest request,
HttpServletResponse response) {
try {
ZzwHandler zzwHandler = getZzwHandler(request);
if (zzwHandler == null) {//说明用户请求的路径/资源不存在
response.getWriter().print("404 NOT FOUND!
");
} else {//匹配成功, 反射调用控制器的方法
//目标将: HttpServletRequest 和 HttpServletResponse 封装到参数数组
//1. 得到目标方法的所有形参参数信息[对应的数组]
Class?>[] parameterTypes =
zzwHandler.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=喝酒&hobby=吃肉(防止有类似checkbox)
//2.返回的Map String: 表示http请求的参数名
// String[]: 表示http请求的参数值, 为什么是数组
//
MapString, String[]> parameterMap = request.getParameterMap();
//2.遍历parameterMap, 将请求参数, 按照顺序填充到实参数组
for (Map.EntryString, String[]> entry : parameterMap.entrySet()) {
//取出key. 这个name就是对应请求的参数名
String name = entry.getKey();
//说明: 只考虑提交的数据是单值的情况, 即不考虑类似checkbox提交的数据
// 老师这里做了简化, 如果考虑多值情况, 也不难
String value = entry.getValue()[0];
//我们得到请求的参数对应我们目标方法的第几个形参, 然后将其填充
//这里专门编写一个方法, 得到请求参数对应的是第几个形参
//1. API 2.java内力真正增加 3.老韩忠告..
int indexRequestParameterIndx =
getIndexRequestParameterIndex(zzwHandler.getMethod(), name);
if (indexRequestParameterIndx != -1) {//找到对应的位置
params[indexRequestParameterIndx] = value;
} else {//说明并没有找到@RequestParam注解对应的参数, 就会使用默认的机制进行配置[待...]
//思路
//1.得到目标方法的所有形参的名称, 而不是形参类型的名称-专门编写一个方法获取形参名
//2.对得到的目标方法的所有形参名进行遍历, 如果匹配就把当前请求的参数值, 填充到params
ListString> parameterNames = getParameterNames(zzwHandler.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)
* zzwHandler.getMethod()
* .invoke(zzwHandler.getController(), request, response)
* 2.这里我们准备将需要传递给目标方法的 实参=> 封装到参数数组=> 然后以反射调用的方式传递给目标方法
* public Object invoke(Object var1, Object... var2)...
*/
zzwHandler.getMethod().
invoke(zzwHandler.getController(), params);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//编写方法, 得到目标方法的所有形参的名称, 并放入到集合中返回
/**
* @param method 目标方法
* @return 所有形参的名称, 并放入到集合中返回
*/
public ListString> getParameterNames(Method method) {
ListString> parametersList = new ArrayListString>();
//获取到所有的参数名称, 而不是参数类型的名称
//这里有一个小细节-->在默认情况下 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;
}
3.parameter.getName()
得到的名字不是形参真正的名字, 而是 [arg0, arg1, arg2…]. 我们需要引入一个插件.
Parameter[] parameters = method.getParameters();
parameters=[javax.servlet.http.HttpServletRequest arg0,
javax.servlet.http.HttpServletResponse arg1,
java.lang.String arg2]
parameter.getName() ⇒ [arg0, arg1, arg2]
在pom.xml
的build节点
内插入以下代码, 点击右上角刷新
build>
finalName>zzw-springmvc2(你的文件名)finalName>
plugins>
plugin>
groupId>org.apache.maven.pluginsgroupId>
artifactId>maven-compiler-pluginartifactId>
version>3.7.0version>
configuration>
source>1.8source>
target>1.8target>
compilerArgs>
arg>-parametersarg>
compilerArgs>
encoding>utf-8encoding>
configuration>
plugin>
plugins>
build>
3.1点击maven
管理, Lifecycle
目录下, clean
项目, 重启(不是重新部署)tomcat
.
5.测试
实现任务阶段七
完成简单视图解析
功能说明: 通过方法返回的String, 转发
或者重定向
到指定页面
●完成任务说明
-用户输入白骨精, 可以登陆成功, 否则失败
-根据登陆的结果, 可以重定向或者请求转发到 login_ok.jsp / login_error.jsp
, 并显示妖怪名
–思路分析示意图
1.在webapp
目录下新建
1)login.jsp
html>
head>
title>登陆页面title>
head>
body>
h1>登陆页面h1>
form action="?" method="post">
妖怪名: input type="text" name="mName"/>br/>
input type="submit" value="登录">
form>
body>
html>
2)login_ok.jsp
html>
head>
title>登录成功title>
head>
body>
h1>登陆成功h1>
欢迎你: ${?}
body>
html>
3)login_error.jsp
html>
head>
title>登录失败title>
head>
body>
h1>登陆失败h1>
sorry, 登陆失败 ${?}
body>
html>
2.MonsterService
增加一个方法login
public interface MonsterService {
....
//增加方法, 处理登录
public boolean login(String name);
}
MonsterServiceImpl
实现它
@Service
public class MonsterServiceImpl implements MonsterService {
....
@Override
public boolean login(String name) {
//实际上会到DB验证->这里我们模拟一下
if ("白骨精".equals(name)) {
return true;
} else {
return false;
}
}
}
3.MonsterController
添加一个方法login
//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
System.out.println("---接收到mName---" + mName);
boolean b = monsterService.login(mName);
if (b) {//登陆成功
return "forward:/login_ok.jsp";
} else {//登陆失败
return "forward:/login_error.jsp";
}
}
接着 login.jsp
填充action="/monster/login"
h1>登陆页面h1>
http://localhost:8080/monster/login--%>
form action="/monster/login" method="post">
妖怪名: input type="text" name="mName"/>br/>
input type="submit" value="登录">
form>
4.测试
如果输入中文, 发现提交的数据有中文乱码问题
解决方案
我们在底层解决乱码问题.
在ZzwDispatcherServlet前端控制器
的完成分发请求任务
的executeDispatcher()方法内
, 添加如下代码 request.setCharacterEncoding("utf-8");
即可
//2.返回的Map String: 表示http请求的参数名
// String[]: 表示http请求的参数值, 为什么是数组
//处理提交的数据中文乱码问题
request.setCharacterEncoding("utf-8");
MapString, String[]> parameterMap = request.getParameterMap();
5.在ZzwDispatcherServlet
的executeDispatcher
方法, 添加如下代码
//上面代码省略...
/**
* 1.下面这样写, 其实是针对目标方法 m(HttpServletRequest request, HttpServletResponse response)
* zzwHandler.getMethod()
* .invoke(zzwHandler.getController(), request, response)
* 2.这里我们准备将需要传递给目标方法的 实参=> 封装到参数数组=> 然后以反射调用的方式传递给目标方法
* public Object invoke(Object var1, Object... var2)...
*/
//反射调用目标方法
Object result = zzwHandler.getMethod()
.invoke(zzwHandler.getController(), params);
//这里就是对返回的结果进行解析=>原生springmvc 可以通过视图解析器来完成
//这里老师让我们直接解析, 只要把视图解析器的核心机制表达清楚就OK
//instanceof 判断 运行类型
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)) {//说明你希望重定向
//如果是redirect, 那么这里需要拼接Application Context. 只不过这个项目的Application Context 正好是 /
response.sendRedirect(viewPage);
}
} else {//默认是请求转发
request.getRequestDispatcher(viewName)
.forward(request, response);
}
}//这里还可以扩展
5.1测试
将信息保存到request域, 在页面显示.
6.MonsterController
修改login
方法, 将mName
保存到request域
//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
System.out.println("---接收到mName---" + mName);
//将nName设置到request域
request.setAttribute("mName", mName);
boolean b = monsterService.login(mName);
if (b) {//登陆成功
return "forward:/login_ok.jsp";
} else {//登陆失败
return "forward:/login_error.jsp";
}
}
6.1修改login_ok.jsp
. 设置isELIgnored="false"
, 否则el表达式不会生效, 默认是true
html>
head>
title>登录成功title>
head>
body>
h1>登陆成功h1>
欢迎你: ${requestScope.mName}
body>
html>
6.2修改login_error.jsp
.设置isELIgnored="false"
, 否则el表达式不会生效 默认是true
html>
head>
title>登录失败title>
head>
body>
h1>登陆失败h1>
sorry, 登陆失败: ${requestScope.mName}
body>
html>
7.演示redirect.
MonsterController
修改login
方法
//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
System.out.println("---接收到mName---" + mName);
//将nName设置到request域
request.setAttribute("mName", mName);
boolean b = monsterService.login(mName);
if (b) {//登陆成功
//return "forward:/login_ok.jsp";
//测试从定向
return "redirect:/login_ok.jsp";
} else {//登陆失败
return "forward:/login_error.jsp";
}
}
7.1测试
7.1演示默认方式(forward)
MonsterController
修改login
方法
//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
System.out.println("---接收到mName---" + mName);
//将nName设置到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";
}
}
测试
7.2 将登录页面提交的参数名改成monsterName
, 还会不会登陆成功?
h1>登陆页面h1>
http://localhost:8080/monster/login--%>
form action="/monster/login" method="post">
妖怪名: input type="text" name="monsterName"/>br/>
input type="submit" value="登录">
form>
测试
7.3 解决方案: 在MonsterController
的login
方法, 形参mName
加上@RequestParam(value = "monsterName"
//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "monsterName") String mName) {
System.out.println("---接收到mName---" + mName);
//将nName设置到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";
}
}
7.4测试
实现任务阶段八
完成返回JSON格式数据-@ResponseBody
功能说明: 通过自定义@ResponseBody
, 返回JSON格式数据
●完成任务说明
-在实际开发中, 比如前后端分离的项目, 通常是直接返回json
数据给客户端/浏览器
-客户端/浏览器接收到数据后, 自己决定如何处理和显示
-测试页面 浏览器输入: http://localhost:8080/monster/list/json
分析+代码实现
1.在com.zzw.zzwspringmvc.annotation
下新建@ResponseBody
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
String value() default "";
}
2.MonsterController
添加方法listMonsterByJson
@Controller
public class MonsterController {
//@Autowired表示要完成属性的装配.
@Autowired
private MonsterService monsterService;
/**
* 编写方法, 返回json格式的数据
* 1.老师梳理
* 2.目标方法返回的结果是给springmvc底层反射调用的位置使用
* 3.我们在springmvc底层反射调用的位置, 接收到结果并解析即可
* 4.方法上标注了@ResponseBody 表示希望以json格式返回给客户端/浏览器
* 5.目标方法的实参, 在springmvc底层通过封装好的参数数组, 传入...
* @param request
* @param respons
* @return
*/
@RequestMapping("/monster/list/json")
@ResponseBody
public ListMonster> listMonsterByJson(HttpServletRequest request,
HttpServletResponse respons) {
ListMonster> monsters = monsterService.listMonster();
return monsters;
}
}
3.pom.xml
引入jackson
, 刷新
.
这里我们不使用gson
!--引入jackson, 使用它的工具类可以进行json操作-->
dependency>
groupId>com.fasterxml.jackson.core/groupId>
artifactId>jackson-databind/artifactId>
version>2.12.4/version>
/dependency>
3.1com.zzw
包下新建一个测试类ZzwTest
public class ZzwTest {
public static void main(String[] args) {
ListMonster> monsters = new ArrayListMonster>();
monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));
monsters.add(new Monster(200, "汤姆猫", "抓老鼠", 200));
//把monsters 转成json
ObjectMapper objectMapper = new ObjectMapper();
try {
String monsterJson = objectMapper.writeValueAsString(monsters);
System.out.println("monsterJson=" + monsterJson);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
4.ZzwDispatcherServlet
的executeDispatcher
, 反射调用目标方法的位置扩展代码
if (result instanceof String) {
String viewName = (String) result;
......
}//这里还可以扩展
else if (result instanceof ArrayList) {//如果是ArrayList
//判断目标方法是否有@ResponseBody注解
Method method = zzwHandler.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();
}
}
完成测试
1.浏览器测试
2.也可以在线转成规整的json
格式
JSON工具在线解析
3.也可以使用Postman
进行测试
总结
还在补充中…
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net