【java高级进阶笔记3】之springMVC
MVC框架设计实现及SpringMVC源码分析
1、SpringMVC应用
1.1 C/S架构和B/S架构
C/S 架构,也就是客户端/服务器
B/S 架构,也就是浏览器服务器
1.2 三层架构
三层架构 | 职责 |
---|---|
表 现 层 | 表现层也就是我们常说的web层。它负责接收客户端请求,向客户端响应结果,通常客户端使用 http协议请求web层web需要接收http请求,完成http响应。MVC是表现层的设计模型,和其他层没有关系 |
业 务 层 | 就是我们常说的service层。它负责业务逻辑处理,和我们开发项目的需求息息相关。web层依赖业务层,但是业务层不依赖web层 |
持 久 层 | 也就是我们是常说的dao层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行持久化的载体 |
1.3 MVC设计模式
MVC分层 | 说明 |
---|---|
Model(模型) | 模型包含业务模型和数据模型,数据模型用户封装数据,业务模型用户处理业务。 |
View(视图) | 通常指的就是我们的jsp或者html。作用一般就是展示数据的。通常视图是依据模型数据创建的。 |
Controller(控制器) | 是应用程序中处理用户交互的部分。作用一般般就是处理程序逻辑的 |
注:MVC提倡每一层只编写自己的东西,不编写任何其他的代码;分层是为了解耦,解耦是为了维护方便和分工协作。
1.4 SpringMVC是什么?
定义:SpringMVC全名叫Spring Web MVC,是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于SpringFrameWork的后续产品
架构图如下:
1.5 SpringMVC的开发流程
1)配置DispatcherServlet前端控制器
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
说明:url-pattern配置有三种方式:
方式一:带后缀,比如*.action,该方式比较精确
方式二:/ 不会拦截.jsp,但是会拦截html等静态资源
方式三:/* 拦截所有,包括.jsp
2)开发处理具体业务逻辑的Handler(@Controller、@RequestMapping)
@RequestMapping("/handle01")
public ModelAndView handle01() {
Date date = new Date();// 服务器当前时间
// 返回服务器时间到前段页面
ModelAndView modelAndView = new ModelAndView();// 封装了数据和页面的信息
// addObject 其实是向请求域中request.setAttribute("date",date);
modelAndView.addObject("date",date);
// 视图信息(封装跳转的页面信息)
modelAndView.setViewName("success");
return modelAndView;
}
3)xml配置文件配置controller扫描,配置springmvc三大件
<!--开启controller扫描-->
<context:component-scan base-package="com.lagou.edu.controller"/>
<!-- 配置springmvc的视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 自动注册最合适的处理器映射器 处理器适配器 -->
<mvc:annotation-driven/>
4)将xml文件路径告诉springmvc(DispatcherServlet)
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
1.6 SpringMVC请求处理流程
如下图所示:
1.7 SpringMVC请求处理步骤
第⼀步:⽤户发送请求⾄前端控制器DispatcherServlet
第⼆步:DispatcherServlet收到请求调⽤HandlerMapping处理器映射器
第三步:处理器映射器根据请求Url找到具体的Handler(后端控制器),⽣成处理器对象及处理器拦截
器(如果 有则⽣成)⼀并返回DispatcherServlet
第四步:DispatcherServlet调⽤HandlerAdapter处理器适配器去调⽤Handler
第五步:处理器适配器执⾏Handler
第六步:Handler执⾏完成给处理器适配器返回ModelAndView
第七步:处理器适配器向前端控制器返回 ModelAndView,ModelAndView 是SpringMVC 框架的⼀个
底层对 象,包括 Model 和 View
第⼋步:前端控制器请求视图解析器去进⾏视图解析,根据逻辑视图名来解析真正的视图。
第九步:视图解析器向前端控制器返回View
第⼗步:前端控制器进⾏视图渲染,就是将模型数据(在 ModelAndView 对象中)填充到 request 域
第⼗⼀步:前端控制器向⽤户响应结果
1.8、Spring MVC 九⼤组件
组件 | 说明 |
---|---|
HandlerMapping(处理器映射器) | HandlerMapping是用来查找Handler的(处理器),在请求到达后,HandlerMapping的作用便是找到请求相应的处理器 |
HandlerAdapter(处理器适配器) | HandlerAdapter的职责要让固定的Servlet处理方法调用Handler来进行处理 |
HandlerExceptionResolver | HandlerExceptionResolver用于处理Handler产生的异常情况。它的作用是根据异常设置ModelAndView,之后交给渲染方法进行渲染,渲染方法会将ModelAndView渲染成页面 |
ViewResolver | ViewResolver即视图解析器,用于将String类型的视图名和Locale解析为View类型的视图,只有一个resolveViewName()方法,Controller层返回的String类型视图名viewName最终会在这里被解析成为View |
RequestToViewNameTranslator | RequestToViewNameTranslator组件的作用是从请求中获取ViewName.因为ViewResolver根据ViewName查找View,但有的Handler处理完成后没有设置View和ViewName,便要通过这个组件从请求中查找ViewName |
ThemeResolver | ThemeResolver组件是?来解析主题的 |
MultipartResolver | MultipartResolver用于上传请求,通过将普通的请求包装成MultipartHttpServletRequest来实现。MultipartHttpServletRequest可以通过getFile()方法直接获得文件 |
LocaleResolver | LocaleResolver用于从请求中解析出Locale,比如中国Locale是zh-CN,用来表示一个区域。这个组件也是i18n的基础 |
FlashMapManager | FlashMap用于重定向时的参数传递,比如在处理用户订单时候,为了避免重复提交,可以处理完post请求之后重定向到一个get请求,这个get请求可以用来显示订单详情之类的信息 |
1.9 数据输出机制之Model、Map、ModelMap
方式一:直接声明形参ModelMap,封装数据
@RequestMapping("/handle02")
public String handle02(ModelMap modelMap) {
Date date = new Date();// 服务器当前时间
modelMap.addAttribute("date",date);
return "success";
}
方式二:直接声明形参Model,封装数据
@RequestMapping("/handle03")
public String handle03(Model model) {
Date date = new Date();// 服务器当前时间
model.addAttribute("date",date);
return "success";
}
方式三:直接声明形参Map集合,封装数据
@RequestMapping("/handle04")
public String handle04(Map<String, Object> map) {
Date date = new Date();// 服务器当前时间
map.put("date", date);
return "success";
}
说明:这三种类型,运行时都是BindingAwareModelMap,BindingAwareModelMap继承了ExtendedModelMap,ExtendedModelMap继承了ModelMap,实现了Model接口
1.10 springMVC请求参数绑定
方式一:使用servlet原生对象
请求url:http://localhost:8080/test/handle02?id=1
@RequestMapping("/handle02")
public ModelAndView handle02(HttpServletRequest request, HttpServletResponse response,HttpSession session) {
String id = request.getParameter("id");
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
方式二:接收简单数据类型参数
请求url:http://localhost:8080/test/handle03?id=1&name=zhangsan
@RequestMapping("/handle03")
public ModelAndView handle03(String name, Integer id) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("id",id);
modelAndView.addObject("name",name);
modelAndView.setViewName("success");
return modelAndView;
}
方式三:接收pojo类型参数
请求url:http://localhost:8080/test/handle04?id=1&name=zhangsan
@RequestMapping("/handle04")
public ModelAndView handle04(User user) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("id",user.getId());
modelAndView.addObject("name",user.getName());
modelAndView.setViewName("success");
return modelAndView;
}
public class User {
private Integer id;
private String name;
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;
}
}
1.11 对 Restful风格请求支持
什么是REST
REST(英文:Representational State Transfer,简称 REST)Restful是一种web软件架构风格,它不是标准也不是协议,它倡导的是一个资源定位及资源操作的风格。互联⽹中的所有东⻄都是资源,既然是资源就会有⼀个唯⼀的uri标识它
案例get
查询,获取资源post
增加,新建资源put
更新delete
删除资源
前端代码
<div>
<h2>SpringMVC对Restful风格url的支持</h2>
<fieldset>
<p>测试用例:SpringMVC对Restful风格url的支持</p>
<!-- get请求 -->
<a href="/demo/handle/15">rest_get测试</a>
<!-- post请求 -->
<form method="post" action="/demo/handle">
<input type="text" name="username"/>
<input type="submit" value="提交rest_post请求"/>
</form>
<!-- put请求(声明的是post请求) -->
<form method="post" action="/demo/handle/15/lisi">
<input type="hidden" name="_method" value="put"/>
<input type="submit" value="提交rest_put请求"/>
</form>
<!-- delete请求(声明的是post请求) -->
<form method="post" action="/demo/handle/15">
<input type="hidden" name="_method" value="delete"/>
<input type="submit" value="提交rest_delete请求"/>
</form>
</fieldset>
</div>
后台控制器代码,通过@PathVariable
获取url中的参数
/*
* get请求 /test/handle/15
*/
@RequestMapping(value = "/handle/{id}",method = {RequestMethod.GET})
public ModelAndView handleGet(@PathVariable("id") Integer id) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
/*
* pots请求 /test/handle
*/
@RequestMapping(value = "/handle",method = {RequestMethod.POST})
public ModelAndView handlePost(String username) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
/*
* put请求 /test/handle/15/lisi
*/
@RequestMapping(value = "/handle/{id}/{name}",method = {RequestMethod.PUT})
public ModelAndView handlePut(@PathVariable("id") Integer id,@PathVariable("name") String username) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
/*
* delete请求 /test/handle/15
*/
@RequestMapping(value = "/handle/{id}",method = {RequestMethod.DELETE})
public ModelAndView handleDelete(@PathVariable("id") Integer id) {
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
web.xml中配置请求方式过滤器(将特定的post请求转换为put和delete请求)
<!--配置springmvc请求方式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求方式进行转换-->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
1.12 SpringMVC使用Json
Json交互
1)前端到后台:前端ajax发送json格式字符串,后台直接接收为pojo参数,使用注解@RequstBody
2)后台到前端:后台直接返回pojo对象,前端直接接收为json对象或者字符串,使用注解@ResponseBody
SpringMVC使用Json交互案例
1)引入jar包
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
2)前端html代码
<div>
<h2>Ajax json交互</h2>
<fieldset>
<input type="button" id="ajaxBtn" value="ajax提交"/>
</fieldset>
</div>
3)前端js代码
<head>
<title>SpringMVC 测试页</title>
<script type="text/javascript" src="/js/jquery.min.js"></script>
<script>
$(function () {
$("#ajaxBtn").bind("click",function () {
// 发送ajax请求
$.ajax({
url: '/demo/handle07',
type: 'POST',
data: '{"id":"1","name":"李四"}',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
success: function (data) {
alert(data.name);
}
})
})
})
</script>
</head>
4)后台代码
@RequestMapping("/handle07")
// 添加@ResponseBody之后,不再走视图解析器那个流程,而是等同于response直接输出数据
public @ResponseBody User handle07(@RequestBody User user) {
// 业务逻辑处理,修改name为张三丰
user.setName("张三丰");
return user;
}
2、SpringMVC高级技术
2.1 Servlet、过滤器、监听器、拦截器对比
分类 | 说明 |
---|---|
Servlet | 处理Request请求和Response响应 |
过滤器(Filter) | 对Request请求起到过滤的作用,作用在Servlet之前,如果配置为/*可以对所有的资源访问(servlet、js/css静态资源等)进行过滤处理 |
监听器(Listener) | 实现了javax.servlet.ServletContextListener接口的服务器端组件,它随Web应用的启动而启动,只初始化一次,然后会一直监视,随Web应用的停止而销毁 |
拦截器(Interceptor) | 是SpringMVC、Struts等表现层框架自己的,不会拦截jsp/html/css/image的访问等,只会拦截访问的控制器方法(Handler) |
注:
serlvet、filter、listener是配置在web.xml中的
interceptor是配置在表现层框架自己的配置文件中的(如:springmvc.xml)
关系图如下:
2.1 单个拦截器的执行流程
代码
后台代码,定义一个类实现HandlerInterceptor
接口
/**
* 自定义拦截器
*/
public class MyInteceptror01 implements HandlerInterceptor {
/**
* 会在handle方法业务逻辑执行之前执行
* 一般处理权限校验逻辑
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInteceptror01 preHandle==========>");
return true;
}
/**
* 会在handle方法业务逻辑执行之后,未跳转页面之前执行
* @param request
* @param response
* @param handler
* @param modelAndView 封装了视图和数据
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInteceptror01 postHandle==========>");
}
/**
* 会在跳转页面完毕之后执行
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInteceptror01 afterCompletion==========>");
}
}
springmvc中配置拦截器
<mvc:interceptors>
<!--<bean class="com.lagou.edu.controller.inteceptror.MyInteceptror01"></bean>-->
<mvc:interceptor>
<!-- 配置拦截件的url,**代表当前目录下所有的url -->
<mvc:mapping path="/**"/>
<!-- 拦截排除的url -->
<!--<mvc:exclude-mapping path="/demo/**"/>-->
<bean class="com.lagou.edu.controller.inteceptror.MyInteceptror01" />
</mvc:interceptor>
</mvc:interceptors>
执行流程
1)程序先执行preHandle()
方法,如果该方法的返回值为true,则程序会继续向下执行处理器中的方法,否则将不再向下执行。
2)在业务处理器(即控制器Controller类)处理完请求后,会执行postHandle()
方法,然后会通过DispatcherServlet
向客户端返回响应。
3)在DispatcherServlet
处理完请求后,才会执行afterCompletion()
方法。
流程图
2.2 多个拦截器的执行流程
执行流程
1)preHandle()
方法会按照配置文件中拦截器的配置顺序执行
2)postHandle()
方法和afterCompletion()
方法则会按照配置顺序的反序执行
流程图
2.3 处理multipart形式的数据(文件上传)
代码
pom.xml中引入jiar包
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
前端代码
<div>
<h2>multipart 文件上传</h2>
<fieldset>
<form method="post" enctype="multipart/form-data" action="/demo/upload">
<input type="file" name="uploadFile"/>
<input type="submit" value="上传"/>
</form>
</fieldset>
</div>
springmvc.xml中增加解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--设置上传文件大小上限,单位是字节,-1代表没有限制也是默认的-->
<property name="maxUploadSize" value="5000000"/>
</bean>
后台代码
/**
* 文件上传
* @return
*/
@RequestMapping(value = "/upload")
public ModelAndView upload(MultipartFile uploadFile,HttpSession session) throws IOException {
// 重命名,原名123.jpg ,获取后缀
String originalFilename = uploadFile.getOriginalFilename();// 原始名称
// 扩展名 jpg
String ext = originalFilename.substring(originalFilename.lastIndexOf(".") + 1, originalFilename.length());
String newName = UUID.randomUUID().toString() + "." + ext;
// 存储,要存储到指定的文件夹,/uploads/yyyy-MM-dd,考虑文件过多的情况按照日期,生成一个子文件夹
String realPath = session.getServletContext().getRealPath("/uploads");
String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
File folder = new File(realPath + "/" + datePath);
if(!folder.exists()) {
folder.mkdirs();
}
// 存储文件到目录
uploadFile.transferTo(new File(folder,newName));
// TODO 业务逻辑代码
Date date = new Date();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("date",date);
modelAndView.setViewName("success");
return modelAndView;
}
3、自定义简易版springmvc框架
3.1 思路
1、配置web.xml,加载自定义的servlet
类(LagouDispatcherServlet
)
2、LagouDispatcherServlet
初始化方法init
1)加载配置文件 springmvc.properties
2)扫描相关的类,扫描注解
3)初始化bean
对象(实现ioc容器,基于注解)
4)实现依赖注入
5)构造一个HandlerMapping
处理器映射器,将配置好的url
和Method
建立映射关系
3、LagouDispatcherServlet
请求调用方法doGet
/doPost
1)根据url
请求去map(handlerMapping
)中找到对应的handel
2)然后利用java
反射机制调用(invoke)controller
中的url
对应的方法,传递对应的参数,并得到返回结果
3.1 代码
代码整体结构
核心代码
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>mvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mvc Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--编译插件定义编译细节-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
<!--告诉编译器,编译的时候记录下形参的真实名称-->
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<!-- tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>lagouDispatcherServlet</servlet-name>
<servlet-class>com.lagou.edu.mvcaframework.servlet.LagouDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:springmvc.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>lagouDispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
LagouDispatcherServlet.java
package com.lagou.edu.mvcaframework.servlet;
import com.lagou.edu.mvcaframework.annotations.LagouAutowired;
import com.lagou.edu.mvcaframework.annotations.LagouController;
import com.lagou.edu.mvcaframework.annotations.LagouRequestMapping;
import com.lagou.edu.mvcaframework.annotations.LagouService;
import com.lagou.edu.projo.Handler;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LagouDispatcherServlet extends HttpServlet {
private Properties properties = new Properties();
private List<String> beanNames = new ArrayList<String>(); // beans权限的类名的集合
// ioc容器
private Map<String, Object> ioc = new HashMap<String, Object>();
private List<Handler> handleMapping = new ArrayList<Handler>();
@Override
public void init(ServletConfig config) throws ServletException {
String contextConfigLocation =
config.getInitParameter("contextConfigLocation").replace("classpath*:", "");
// 1 加载配置文件 springmvc.properties
doLoadConfig(contextConfigLocation);
// 2 扫描相关的类,扫描注解
doScan(properties.getProperty("scanPackage"));
// 3 初始化bean对象(实现ioc容器,基于注解)
doInstance();
// 4 实现依赖注入
doAutoWired();
// 5 构造一个HandlerMapping处理器映射器,将配置好的url和Method建立映射关系
initHandlerMapping();
System.out.println("lagout mvc 初始化完成......");
}
/** 构造一个HandlerMapping处理器映射器,将配置好的url和Method建立映射关系 */
private void initHandlerMapping() {
Set<Map.Entry<String, Object>> entries = ioc.entrySet();
if (entries.isEmpty()) {
return;
}
// 循环遍历
for (Map.Entry<String, Object> entry : entries) {
Class<?> aClass = entry.getValue().getClass();
if (!aClass.isAnnotationPresent(LagouController.class)
|| !aClass.isAnnotationPresent(LagouRequestMapping.class)) {
// 没有@LagouController注解和@LagouRequestMapping的类跳过,
continue;
}
LagouRequestMapping requestMappingAnnotation =
entry.getValue().getClass().getAnnotation(LagouRequestMapping.class);
if (requestMappingAnnotation == null) {
// 没有@LagouRequestMapping注解,跳过
continue;
}
String bathUrl = requestMappingAnnotation.value();
Method[] methods = aClass.getDeclaredMethods();
if (methods == null || methods.length == 0) {
continue;
}
// 循环遍历方法
for (Method method : methods) {
// 获取方法上的@LagouRequestMapping注解
LagouRequestMapping methodAnnotation = method.getAnnotation(LagouRequestMapping.class);
if (methodAnnotation == null) {
continue;
}
String url = methodAnnotation.value();
if (StringUtils.isBlank(url)) {
continue;
}
String realUrl = bathUrl + url;
// 构建Handlerd对象
Handler handler = new Handler(entry.getValue(), method, Pattern.compile(realUrl));
// 解析参数列表
Parameter[] parameters = method.getParameters();
Map<String, Integer> paramIndexMapping = new HashMap<String, Integer>();
for (int i = 0; i < parameters.length; i++) {
// 判断参数是否为HttpServletRequet,如果有
String name = parameters[i].getType().getSimpleName();
if ("HttpServletRequest".equals(name)) {
paramIndexMapping.put("HttpServletRequest", 0);
} else if ("HttpServletResponse".equals(name)) {
paramIndexMapping.put("HttpServletResponse", 1);
} else {
paramIndexMapping.put(parameters[i].getName(), i);
}
handler.setParamIndexMapping(paramIndexMapping);
}
handleMapping.add(handler);
}
}
}
/** 实现依赖注入 */
private void doAutoWired() {
if (ioc.isEmpty()) return;
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
String key = entry.getKey(); // 获取键(beanId)
Object value = entry.getValue(); // 获取值(对象)
// 获取类中的所有属性的值
Field[] fields = value.getClass().getDeclaredFields();
// 判断是为空,如果为空不处理
if (fields != null && fields.length > 0) {
for (Field declaredField : fields) {
// 判断属性上是否有@LagouService注解
if (declaredField.isAnnotationPresent(LagouAutowired.class)) {
LagouAutowired autowiredAnnotation = declaredField.getAnnotation(LagouAutowired.class);
String beanName = "";
// 判断value是否有值
if (StringUtils.isNotBlank(autowiredAnnotation.value())) {
// value有值
beanName = autowiredAnnotation.value();
} else {
// value没有值,取类名首字母小写
beanName = fristLower(declaredField.getType().getSimpleName());
}
Object bean = ioc.get(beanName);
declaredField.setAccessible(true);
// 通过反射,进行属性注入
try {
declaredField.set(value, bean);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
/** 初始化bean对象(实现ioc容器,基于注解) */
private void doInstance() {
// 循环beanName集合
for (String beanName : beanNames) {
// 通过反射获取类
try {
Class<?> aClass = Class.forName(beanName);
// 判断类上是否有@LagouController、@LagouService注解
if (aClass.isAnnotationPresent(LagouController.class)) {
// controller直接放到map中
ioc.put(fristLower(aClass.getSimpleName()), aClass.newInstance());
} else if (aClass.isAnnotationPresent(LagouService.class)) {
// server要处理一下,判断是否有value属性
// 获取类名首字母小写,并且获取接口的首字母小写
LagouService annotation = aClass.getAnnotation(LagouService.class);
String annotationValue = annotation.value();
if (StringUtils.isNotBlank(annotationValue)) {
// 如果有直接用value,如果没有
ioc.put(annotationValue, aClass.newInstance());
} else {
ioc.put(fristLower(aClass.getSimpleName()), aClass.newInstance());
// 获取类实现的接口
Class<?>[] interfaces = aClass.getInterfaces();
if (interfaces != null && interfaces.length > 0) {
for (Class<?> anInterface : interfaces) {
ioc.put(fristLower(anInterface.getSimpleName()), aClass.newInstance());
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 类名首字符转小写
*
* @param beanName
* @return
*/
private String fristLower(String beanName) {
String result =
beanName.substring(0, 1).toLowerCase() + beanName.substring(1, beanName.length());
return result;
}
/**
* 扫描相关的类,扫描注解
*
* @param scanPackage
*/
private void doScan(String scanPackage) {
// 找到对应的C盘路径 com.lagou.edu.demo
String path =
Thread.currentThread().getContextClassLoader().getResource("").getPath()
+ scanPackage.replaceAll("\\.", "\\/");
File file = new File(path);
// 获取当前路径下的所有文件
File[] files = file.listFiles();
for (File tempFile : files) {
// 判断当前记录是否为文件夹
if (tempFile.isDirectory()) {
// 是文件夹,迭代
doScan(scanPackage + "." + tempFile.getName());
} else if (tempFile.getName().endsWith(".class")) {
String className = scanPackage + "." + tempFile.getName().replaceAll(".class", "");
beanNames.add(className);
}
}
}
/**
* 加载springmvc.properties配置文件
*
* @param contextConfigLocation
*/
private void doLoadConfig(String contextConfigLocation) {
InputStream resourceAsStream =
this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
}
@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 {
Handler handler = getHandler(req);
if (handler == null) {
resp.getWriter().write("404 not found");
return;
}
Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
// 根据上述数组长度创建一个新的数组(参数数组,是要传入反射调用的)
Object[] paraValues = new Object[parameterTypes.length];
Map<String, String[]> parameterMap = req.getParameterMap();
// 遍历request中所有参数 (填充除了request,response之外的参数)
for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
// name=1&name=2 name [1,2]
String value = StringUtils.join(param.getValue(), ","); // 如同 1,2
// 如果参数和方法中的参数匹配上了,填充数据
if (!handler.getParamIndexMapping().containsKey(param.getKey())) {
continue;
}
// 方法形参确实有该参数,找到它的索引位置,对应的把参数值放入paraValues
Integer index = handler.getParamIndexMapping().get(param.getKey()); // name在第 2 个位置
paraValues[index] = value; // 把前台传递过来的参数值填充到对应的位置去
}
int requestIndex =
handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); // 0
paraValues[requestIndex] = req;
int responseIndex =
handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName()); // 1
paraValues[responseIndex] = resp;
try {
handler.getMethod().invoke(handler.getController(), paraValues);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
// 获取handler
private Handler getHandler(HttpServletRequest req) {
// 从请求中获取url
String requestURI = req.getRequestURI();
if (handleMapping.isEmpty()) {
return null;
}
for (Handler handler : handleMapping) {
if (!handler.getPattern().matcher(requestURI).matches()) {
continue;
}
return handler;
}
return null;
}
}
4、SpringMVC源码剖析
4.1、前端控制器DispatcherServlet继承结构
4.1、初始化过程
- tomcat启动: 加载应用的web.xml
- 初始化DispatcherServlet
由tomcat调用DispatcherServlet的init方法,只会调用一次
-
调用HttpServletBean的init方法
-
调用HttpServletBean的initServletBean方法(核心方法)
-
调用HttpServletBean的initWebApplicationContext方法(初始化ApplicationContext)
-
调用HttpServletBean的createWebApplicationContext方法
-
调用HttpServletBean的createWebApplicationContext方法
-
调用AbstractApplicationContext的refresh方法(IOC容器启动的核心方法)
-
中间成省略,最后会调用FrameworkServlet的onRefresh方法,完成九大组件的初始化
4.1、初始化过程
-
分析入口:FrameworkServlet中的service方法
-
调用FrameworkServlet中的doGet方法
-
调用FrameworkServlet中的processRequest方法
-
调用DispatcherServlet中的doService方法
-
调用DispatcherServlet中的doDispatch方法(核心方法)
5、SSM整合
5.1、整合策略
SSM = Spring
+ SpringMVC
+ Mybatis
= (Spring
+ Mybatis
)+ SpringMVC
先整合 Spring
+ Mybatis
然后再整合 SpringMVC
5.2、Mybatis整合Spring
整合目标
- 数据库连接池以及事务管理都交给Spring容器来完成
- SqlSessionFactory对象应该放到Spring容器中作为单例对象管理
- Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象
整合目标
- Junit测试jar(4.12版本)
- Mybatis的jar(3.4.5)
- Spring相关jar(
spring-context
、spring-test
、spring-jdbc
、spring-tx
、spring-aop
、aspectjweaver
) - Mybatis/Spring整合包jar(
mybatis-spring-xx.jar
) - Mysql数据库驱动jar
- Druid数据库连接池的jar
5.3、整合SpringMVC
整合思路
- 在Mybatis整合Spring的基础上开发一个SpringMVC的入门案例
5.3、整合后代码
代码整体结构
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lagou.edu</groupId>
<artifactId>ssm</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>ssm Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<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>
<dependencies>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!--spring相关-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<!--mybatis与spring的整合包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
<!--数据库驱动jar-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!--SpringMVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<!--jsp-api&servlet-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--页面使用jstl表达式-->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<!--json数据交互所需jar,start-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
<!--json数据交互所需jar,end-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
web.xml文件
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext*.xml</param-value>
</context-param>
<!--spring框架启动-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--springmvc启动-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://ip:3306/test_yuyz
jdbc.username=
jdbc.password=
springmvc.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--扫描controller-->
<context:component-scan base-package="com.lagou.edu.controller"/>
<!--配置springmvc注解驱动,自动注册合适的组件handlerMapping和handlerAdapter-->
<mvc:annotation-driven/>
</beans>
applicationContext-dao.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--数据库连接池以及事务管理都交给Spring容器来完成-->
<!--引⼊外部资源⽂件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--第三⽅jar中的bean定义在xml中-->
<bean id="dataSource"
class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--包扫描-->
<context:component-scan base-package="com.lagou.edu.mapper"/>
<!--SqlSessionFactory对象应该放到Spring容器中作为单例对象管理-->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<!--别名映射扫描-->
<property name="typeAliasesPackage" value="com.lagou.edu.pojo"/>
<!--数据源dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对象-->
<!--扫描mapper接⼝,⽣成代理对象,⽣成的代理对象会存储在ioc容器中-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--mapper接⼝包路径配置-->
<property name="basePackage" value="com.lagou.edu.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>
applicationContext-service.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--包扫描-->
<context:component-scan base-package="com.lagou.edu.service"/>
<!--事务管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务管理注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
AccountMapper.xml文件
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lagou.edu.mapper.AccountMapper">
<select id="queryAccountList" resultType="com.lagou.edu.pojo.Account">
select * from account
</select>
</mapper>
AccountController.java文件
package com.lagou.edu.controller;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controller
@RequestMapping("/account")
public class AccountController {
@Autowired
private AccountService accountService;
@RequestMapping("/queryAll")
@ResponseBody
public List<Account> queryAll() throws Exception {
return accountService.queryAccountList();
}
}
AccountService.java文件
package com.lagou.edu.service;
import com.lagou.edu.pojo.Account;
import java.util.List;
public interface AccountService {
/**
* 查询所有账户信息
* @return
* @throws Exception
*/
public List<Account> queryAccountList() throws Exception;
}
AccountServiceImpl.java文件
package com.lagou.edu.service.impl;
import com.lagou.edu.mapper.AccountMapper;
import com.lagou.edu.pojo.Account;
import com.lagou.edu.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public List<Account> queryAccountList() throws Exception {
return accountMapper.queryAccountList();
}
}
AccountMapper.java文件
package com.lagou.edu.mapper;
import com.lagou.edu.pojo.Account;
import java.util.List;
public interface AccountMapper {
/**
* 查询所有账户信息
* @return
* @throws Exception
*/
public List<Account> queryAccountList() throws Exception;
}
Account.java文件
package com.lagou.edu.pojo;
public class Account {
private String cardNo;
private String name;
private Integer money;
public String getCardNo() {
return cardNo;
}
public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"cardNo='" + cardNo + '\'' +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
数据库建表语句
CREATE TABLE `account` (
`cardNo` varchar(50) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`money` int(255) DEFAULT NULL,
PRIMARY KEY (`cardNo`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
本文地址:https://blog.csdn.net/yuyangzhi10/article/details/109371707