1. 使用SpringMVC的原因
通过之前的学习,已经可以使用 Mybatis将数据库替换;使用 Spring将各层之间进行解耦。目前请求的入口还是 Controller层中的 Servlet,tomcat服务器在接收到请求后,会根据请求地址自动调用对应的 servlet中的 service方法,但此流程存在以下问题:
- 每个功能都要声明对应的 Servlet,很麻烦
- 在 Servlet中获取请求数据比较麻烦
- 响应时,其实只想声明对应的响应数据
解决:
- 只声明一个 servlet,作为项目请求的公共入口,参考之前的文章,合并Servlet
- 将同类型的逻辑方法统一放到对应的逻辑类中,以免结构不清晰
- 在该servlet中存在逻辑处理,可根据请求地址动态调用相关逻辑类的逻辑方法
- 如何在 servlet中获取逻辑类对象 — 使用 Spring容器的子容器,在子容器中存储所有的 Controller实例化对象(在 init方法中实现一次性获取)
- 如何根据请求动态调用对象的逻辑方法 — 使用反射 + 注解
2. 原理图
MyServlet中的逻辑,框架已经实现了
本质
:将 Servlet进行了封装,提供一个公共的 Servlet,可以根据请求,动态的调用对应的逻辑方法完成处理。
基本流程:tomcat服务器启动;根据web.xml中的配置,创建对应的Servlet类;然后init()方法开始初始化,加载配置文件;根据配置文件中的内容,扫描注解,形成beans
3. SpringMVC的基本实现
-
配置 SpringMVC的依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>6.1.8</version> </dependency>
-
在 web.xml中配置 Servlet
- Servlet的访问路径
- SpringMVC容器对象的配置文件路径
<!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> <!-- 配置 SpringMVC提供的 Servlet的访问路径 --> <servlet> <servlet-name>mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置 SpringMVC容器对象的配置文件的加载路径 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!--服务器启动即完成DispatcherServlet的初始化创建--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc</servlet-name> <url-pattern>/</url-pattern> <!-- 拦截除JSP之外的所有请求 --> </servlet-mapping> </web-app>
-
在resources下创建 SpringMVC配置文件 springmvc.xml
- 配置注解扫描路径
- 配置 SpringMVC的驱动解析器
<?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:mvc="http://www.springframework.org/schema/mvc" 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 "> <!-- 配置注解扫描路径 --> <context:component-scan base-package="com.lwclick" /> <!-- 配置注解驱动解析器 --> <mvc:annotation-driven/> </beans>
-
创建对应的类,并在类中声明单元方法进行逻辑处理
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * @Author: LW * @Date: 2024 * @Description: */ @Controller public class SpringMvcFirstController { // 声明单元方法:给 DispatcherServlet使用,必须按照 SpringMVC的方式来声明逻辑方法 @RequestMapping("demo") public String demo() { System.out.println("我是控制器的第一个单元方法"); return "a"; } }
-
浏览器中,输入地址 localhost:8080/demo,即可请求到对应的单元方法(此处的 demo为
@RequestMapping
注解中的值)
4. 单元方法获取请求数据
通过上面 SpringMVC的基本使用流程,发现 DispatcherServlet可根据请求,动态的调用对应的单元方法进行处理;
那么在单元方法中,如何获取请求数据呢?
解决:tomcat接收请求,然后封装到 request对象内,将其作为实参传递给 DispatcherServlet的 service方法,而 service方法会根据请求动态的调用单元方法,因此只需在 service方法中将 request作为实参传递给单元方法即可(单元方法必须声明对应的形参接收数据
)
实现方法:
-
紧耦方式
-
DispatcherServlet的 service方法,直接将此次请求的 request对象传递给单元方法
然后在单元方法上声明形参 HttpServletRequest来接收
-
-
解耦方式
-
DispatcherServlet的 service方法,根据需求从 request对象中取出数据,然后将数据传递给单元方法
然后在单元方法上声明对应的形参进行接收
-
4.1 紧耦方式-request对象
@Controller
public class SpringMvcFirstController {
/**
* 紧耦合方式获取请求数据
* 使用:
* 在单元方法上声明形参,类型为 HttpServletRequest
*
* 注意:
* 单元方法都是由 DispatcherServlet根据请求调用的,因此是由它来传入实参
*/
@RequestMapping("reqData")
public String getDataByRequest(HttpServletRequest request) {
// 获取数据
String username = request.getParameter("username");
// 处理逻辑
System.out.println(username);
// 响应
return "abc";
}
}
4.2 解耦方式-形参名即请求键名
在单元方法上,声明除了 request(HttpServletRequest类型)之外的形参时,DispatcherServlet会通过形参名作为请求数据的键名来获取数据(相当于 request.getParameter(“形参名”) 由 DispatcherServlet帮我们做了)
-
若形参名和请求数据的键名不一致,则形参接到的数据为 null
-
如果形参的数据类型为基本类型,则会出现数据类型转换异常(比如将 null转换为 int)
因此建议将形参的数据类型都声明为包装类
-
-
若请求数据的数据类型和形参的数据类型不匹配,会报400异常
当需要获取同键不同值的请求数据时(比如爱好-多选框),也可以在后台声明对应的形参名(如 String[] hobbies)
@Controller
public class SpringMvcFirstController {
/**
* 解耦方式获取请求数据——形参名即请求键名
* 使用:
* 声明除 request(HttpServletRequest类型)之外的形参,
* DispatcherServlet会通过形参名作为键名获取数据
* @return
*/
@RequestMapping("reqDataByArgName")
public String reqDataByArgName(String username) {
// 处理逻辑
System.out.println(username);
// 响应
return "abc";
}
}
4.3 解耦方式-@RequestParam
当形参名与请求数据的键名无法做到一致,又想获取数据时,可以使用 @RequestParam
注解来实现
@RequestParam的属性:
- value:获取请求数据的键名(只有 value时,可以省略不写)
- required:表示请求中是否必须携带当前键名
- defaultValue:默认值,不可以和 required一起使用
@Controller
public class SpringMvcFirstController {
/**
* 解耦方式获取请求数据——使用 @RequestParam
*
* @return
*/
@RequestMapping("reqDataByAnno")
public String reqDataByAnno(@RequestParam("uname") String username) {
// 处理逻辑
System.out.println(username);
// 响应
return "abc";
}
}
4.4 解耦方式-实体类
当请求数据过多时,可以将请求封装到实体类对象中进行使用,保证数据的完整性
- 实体类的属性名和请求数据的键名需要一致
- 属性的数据类型使用包装类,避免出现类型转换异常
- 必须提供对应属性名的 get/set方法
@Controller
public class SpringMvcFirstController {
/**
* 解耦方式获取请求数据——使用实体类对象
*
* @return
*/
@RequestMapping("reqDataByObject")
public String reqDataByObject(User user) {
// 处理逻辑
System.out.println(user);
// 响应
return "abc";
}
}
4.5 紧耦和解耦方式混用
形参处可以使用 request,包装类类型的形参名,实体类等
@Controller
public class SpringMvcFirstController {
/**
* 解耦方式获取请求数据——使用实体类对象
*
* @return
*/
@RequestMapping("reqDataMixture")
public String reqDataMixture(User user, @RequestParam("uname") String name,
Integer age, HttpServletRequest request) {
// 处理逻辑
System.out.println(user);
// 响应
return "abc";
}
}
5. restful格式请求
5.1 问题
目前浏览器发起请求给服务器时,不管用 GET还是 POST方式,请求数据的格式都是键值对
GET请求:localhost:8080/project/getUser?uname=zhangsan&age=18
POST请求:localhost:8080/project/addUser,请求体数据: uname=zhangsan age=18
这就要求后台获取请求数据的代码,必须按照指定的键名来获取 ----> 耦合性过高
5.2 实现
直接将数据本身作为请求地址的一部分发送给后台,后台使用 restful格式接收数据
restful格式:localhost:8080/project/getUser/zhangsan/18
,可以是 GET,也可以是 POST
5.3 后台获取数据-{}占位符、@PathVariable
从服务器的角度看,服务器接收请求后,会根据请求地址调用对应的 Servlet来处理请求
因此针对restful格式的请求:localhost:8080/project/getUser/zhangsan/18,服务器在接收到该请求后,调用DispatcherServlet(后面简称为DS),DS底层再根据请求的 URL地址信息,调用对应的单元方法。
前面内容,在 web.xml中配置的拦截路径为 ‘/’,因此DS认为请求地址中项目名后的信息为要调用的单元方法的名称(即@RequestMapping注解中的值),所以DS会调用值为 "getUser/zhangsan/18"的方法; 若要获取 lisi的信息,则地址变为了 getUser/lisi/20
可以看到restful格式,获取不同的数据,地址就不同,不可能在后台声明如此多的单元方法,因此需要声明一个公共的单元方法 ----> SpringMVC对 restful格式的支持
后台在 @RequestMapping中使用 {}占位符进行模糊匹配,使用
@PathVariable获取请求数据`
@Controller
public class SpringMvcFirstController {
/**
* 处理 restful请求格式
* SpringMVC的单元方法支持模糊匹配的声明,使用 {} 占位符
*
*
* @RequestMapping("getUser/{param1}/{param2}")
* 代表以 getUser开头,后面可携带2个参数
* 如: getUser/zhangsan/18 getUser/lisi/20
*
*
* 形参列表中,使用 @PathVariable 接收数据
* 如果占位符中的名字和形参名一致,可以不写
* 如果不一致,格式为:@PathVariable("占位符名")
* @return
*/
@RequestMapping("getUser/{param1}/{obj2}")
public String getUser(@PathVariable String param1, // 占位符中的名称和形参名一样
@PathVariable("obj2") String param2, // 占位符中的名称和形参名不一样
String fav) { // 不加 @PathVariable注解,依旧以fav作为请求键名的方式获取数据
// 处理逻辑
System.out.println(param1);
System.out.println(param2);
// 响应
return "abc";
}
}
后台处理,就是从请求地址中,截取一部分数据来使用
restful 只是一种请求数据携带格式,并且可以和正常键值对数据的方式混用
6. 编码过滤器配置
前后台数据交互中,存在中文,浏览器的默认编码为 ISO-8859-1,但是 tomcat服务器默认的编码为 UTF-8,彼此之间对中文的编码形式不一致,就会导致乱码的产生。
解决:使用 SpringMVC的编码过滤器配置 ---- 在 web.xml中进行配置即可
<web-app>
<!-- 配置编码过滤器 ====== 要放在最上面!!! ====== -->
<filter>
<filter-name>charsetFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 设置编码格式 -->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<!-- 设置编码格式的生效范围, true代表对"请求"和"响应"都有效 -->
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>charsetFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 其他配置:servlet、 servletMapping -->
</web-app>
7. 静态资源放行配置
前面内容,在 web.xml中配置的拦截范围是 ‘/’,表示拦截除 JSP请求之外的所有请求,那么JS、CSS、图片等静态资源也会被拦截 ---- 需要配置静态资源的放行
在 springmvc.xml中进行配置
<!-- 配置静态资源放行
mapping 代表的是请求的地址格式,即项目地址后直接带 js, localhost:8080/js/ajax.js
location 代表要映射到的目录位置,/js/ 即项目根目录下的 js文件夹
-->
<mvc:resources mapping="/js/**" location="/js/" />
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources mapping="/img/**" location="/img/" />
8. SpringMVC的响应
在单元方法中,接收数据,完成逻辑处理后,如何进行响应呢?
**解决:**使用了 SpringMVC之后,servlet只有一个 DispatcherServlet了;单元方法是由 DS调用的,那么只需要在单元方法执行完毕后,通过 return的内容,告诉 DS如何响应就可以了。
原始方式:使用紧耦方式,即原生的 response进行响应(了解即可:response方式,不经过 servlet,直接响应,所以单元方法的返回值为 void)
8.1 forward转发
通过单元方法的返回值,告诉 DispatcherServlet请求转发指定的资源:JSP资源、其他单元方法等(直接 return “字符串”,就是 forward方式)
转发的方式,request是同一个
@Controller
public class MyController {
/**
* 转发 JSP资源
*
* @return jsp资源的绝对路径
*/
@RequestMapping("forwardJsp")
public String forwardJsp(String uname, Integer age) {
System.out.println("使用 forward关键字请求转发 JSP资源!");
// jsp页面的路径, /index.jsp即项目根目录下的(从 webapp下开始找)
return "/index.jsp";
}
/**
* 转发其他单元方法
*
* @return 其他单元方法的请求路径
*/
@RequestMapping("forwardMethod")
public String forwardMethod(String uname, Integer age) {
System.out.println("使用 forward关键字请求其他单元方法!");
// 其他单元方法的请求地址
return "/forwardJsp";
}
}
8.2 redirect重定向
redirect是两次请求,因此 request不是同一个,使用格式: return "redirect:/xxxx"
如果想要传递数据,可以使用 HttpSession session,将数据放到 session中
@Controller
public class MyController {
/**
* 重定向到 JSP资源
*
* @return jsp资源的绝对路径
*/
@RequestMapping("redirectJsp")
public String redirectJsp(String uname, Integer age) {
System.out.println("使用 redirect关键字重定向到 JSP资源!");
// jsp页面的路径, /index.jsp即项目根目录下的(从 webapp下开始找)
return "redirect:/index.jsp";
}
/**
* 重定向到其他单元方法
*
* @return 其他单元方法的请求路径
*/
@RequestMapping("redirectMethod")
public String redirectMethod(String uname, Integer age) {
System.out.println("使用 redirect关键字重定向到其他单元方法!");
// 其他单元方法的请求地址 ====== 其他单元方法收到的 request不是从浏览器直接过来的 =====
return "redirect:/forwardJsp";
}
}
9. SSM框架搭建
-
创建 maven项目,并补齐目录结构
-
在 pom.xml中配置相关依赖(注意版本的对照关系)
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lwclick</groupId> <artifactId>SSM</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>SSM Maven Webapp</name> <!-- 配置版本号 --> <properties> <spring-version>6.1.6</spring-version> <commons-logging-version>1.3.1</commons-logging-version> <mybatis-spring-version>3.0.3</mybatis-spring-version> <!-- 需要注意JDK版本 --> <mybatis-version>3.5.16</mybatis-version> <slf4j-log4j12-version>2.0.12</slf4j-log4j12-version> <log4j-version>2.23.1</log4j-version> <mysql-version>8.0.33</mysql-version> </properties> <dependencies> <!-- 配置 SpringMVC的依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-version}</version> </dependency> <!-- 配置 Spring的依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>${commons-logging-version}</version> </dependency> <!-- 配置 Spring整合 Mybatis的依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${mybatis-spring-version}</version> </dependency> <!-- 配置 Mybatis的依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis-version}</version> </dependency> <!-- 配置日志依赖 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j-log4j12-version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>${log4j-version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${log4j-version}</version> </dependency> <!-- 配置 JDBC依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-version}</version> </dependency> <!-- 配置 web相关依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/ssm</path> </configuration> </plugin> </plugins> </build> </project>
-
在 "java"目录下,新建controller、mapper、pojo、service等目录
-
在 "resources"目录下,新建 applicationcontext.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" default-autowire="byName"> <!--属性文件扫描--> <context:property-placeholder location="classpath:db.properties" /> <!--配置注解扫描, service层 --> <context:component-scan base-package="com.lwclick.service.impl" /> <!--配置数据源bean--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${mysql.driver}" /> <property name="url" value="${mysql.url}" /> <property name="username" value="${mysql.username}" /> <property name="password" value="${mysql.password}" /> </bean> <!--配置工厂bean--> <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean" /> <!--配置Mapper扫描bean,mapper层--> <bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="factory" /> <property name="basePackage" value="com.lwclick.mapper" /> </bean> <!--配置事务管理bean--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" /> <!--配置事务管理方法--> <tx:advice id="advice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="sel*" read-only="true"/> <tx:method name="ins*"/> <tx:method name="upd*"/> <tx:method name="del*"/> </tx:attributes> </tx:advice> <!--配置事务管理切面--> <aop:config> <aop:pointcut id="mp" expression="execution(* com.lwclick.service.impl.*.*(..))"/> <aop:advisor advice-ref="advice" pointcut-ref="mp" /> </aop:config> </beans>
-
在 "resources"目录下,新建 db.properties
mysql.driver=com.mysql.cj.jdbc.Driver mysql.url=jdbc:mysql://localhost:3306/ssm mysql.username=root mysql.password=root
-
在 "resources"目录下,新建 log4j.properties
log4j.rootCategory=info log4j.logger.com.lwclick.mapper=debug, CONSOLE,LOGFILE log4j.logger.com.lwclick.advice=debug, CONSOLE,LOGFILE log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=- %c-%d-%m%n log4j.appender.LOGFILE=org.apache.log4j.FileAppender log4j.appender.LOGFILE.File=D:/ssm.log log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.LOGFILE.layout.ConversionPattern=- %c-%d-%m%n
-
在 "resources"目录下,新建 springmvc.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:mvc="http://www.springframework.org/schema/mvc" 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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--配置注解扫描, controller层--> <context:component-scan base-package="com.lwclick.controller" /> <!--配置注解解析器--> <mvc:annotation-driven /> <!--配置静态资源放行--> <mvc:resources mapping="/js/**" location="/js/" /> <mvc:resources mapping="/css/**" location="/css/" /> <mvc:resources mapping="/images/**" location="/images/" /> </beans>
-
配置 webapp目录下 WEB-INF目录下的 web.xml文件
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <!--配置Spring容器的配置文件路径--> <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> <servlet-name>mvc</servlet-name> <!-- 'org.springframework.web.filter.CharacterEncodingFilter' is not assignable to 'javax.servlet.Filter,javax.servlet.Filter' 可以回退spring及springmvc的版本 --> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <!--配置SpringMVC容器的配置文件路径--> <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>mvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--配置编码过滤器--> <filter> <filter-name>code</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>code</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
-
在 "controller"包下新建控制器类,书写逻辑
通过依赖注入的方式,在controller层声明 service层对象,在service层声明 mapper层对象
(前提是在 applicationcontext.xml文件内配置了service和 mapper层的注解扫描,从前台能访问的前提是在 springmvc.xml中配置了 controller层的注解扫描)
需要注意的是,如果声明了事务,在 controller层,声明 service层的对象,用的是
private UserService userService
,即接口,而不是接口的实现类 xxxImpl
10. 使用作用域对象完成数据的流转
10.1 作用域对象
- PageContext对象
- 作用域:当前 jsp页面内有效
- request对象
- 作用域:一次请求内
- 解决了一次请求内的资源的数据共享问题
- session对象
- 作用域:一次会话内有效
- 浏览器不关闭,且后台的 session不失效,则在同一个用户的任意请求中都可以获取到同一个 session对象
- application (ServletContext)对象
- 作用域:整个项目内有效
- 一个项目只有一个,在服务器启动的时候就完成初始化创建,解决了不同用户的数据共享问题
10.2 使用 request对象
/**
* 使用 request对象,作为请求转发的数据流转的载体
* 需要在单元方法上,声明类型为 HttpServletRequest的 request对象
*/
@RequestMapping("testRequest")
public String testReq(String uname, HttpServletRequest request) {
System.out.println("使用 request对象作为请求转发数据流转的载体:" + uname);
// 在 req.jsp中,可以获取键值为 str的数据
request.setAttribute("str", "我是request对象");
return "/req.jsp";
}
10.3 使用 session对象
使用 session对象作为同一个用户,不同请求的数据流转的载体(用在重定向中,可以避免无法传递数据)
/**
* 使用 session对象,作为同一个用户,不同请求的数据流转的载体
* 在单元方法上,声明类型为 HttpSession的 session对象
*/
@RequestMapping("testSession")
public String testSession(String uname, HttpSession session) {
System.out.println("使用session作为数据流转的载体:" + uname);
// 通过某一请求,重定向到 session.jsp中后,在 session.jsp中仍然可以获取键值为 str的数据
session.setAttribute("str", "我是session对象");
return "redirect:/session.jsp";
}
10.4 使用 application对象
使用 application对象,作为项目公共数据的载体,可以让不同的用户共享数据(application对象的获取,只能在单元方法中通过 request获取,无法使用形参的方式)
/**
* 使用 application对象,作为项目公共数据的载体,可以让不同的用户共享数据
* 在单元方法中,使用类型为 HttpServletRequest的 request对象获取 servletContext
*/
@RequestMapping("testApplication")
public String testApplication(String uname, HttpServletRequest request) {
System.out.println("使用session作为数据流转的载体:" + uname);
ServletContext servletContext = request.getServletContext();
servletContext.setAttribute("str", "我是 ServletContext对象");
return "redirect:/session.jsp";
}
10.5 使用 SpringMVC的 Model对象
Model对象也可作为数据流转的载体,是 SpringMVC提供的方法
使用:在单元方法上,声明 Model类型的形参即可(是由DispatcherServlet创建,并作为实参传递给单元方法)
-
请求转发方式:
- model对象中存储的数据,相当于存储到了 request对象中
/** * 使用 Model对象作为数据流转的载体 --- 请求转发方式 * 在单元方法上声明 Model类型的形参 */ @RequestMapping("testModelForward") public String testModelForward(String uname, Model model) { System.out.println("使用 Model对象作为数据流转的载体:" + uname); model.addAttribute("str", "我是 Model对象作为请求转发数据流转的载体!"); return "/model.jsp"; }
-
重定向方式:
- 会将第一次请求中的 model数据,作为第二次请求的请求数据,第一次请求的 model对象销毁(只能携带基本类型的数据)
/** * 使用 Model对象作为数据流转的载体 --- 重定向方式 */ @RequestMapping("testModelRedirect") public String testModelRedirect(String uname, Model model) { System.out.println("使用 model作为数据流转的载体:" + uname); model.addAttribute("str", "我是 Model对象作为 重定向 数据流转的载体!"); // ==== 重定向到 model.jsp后,会携带参数 str,值为 '我是 Model对象作为 重定向 数据流转的载体!' === return "redirect:/model.jsp"; }
11. 对 Ajax请求的处理
目前 DispatcherServlet的底层会将单元方法的返回值按照请求转发或者重定向来处理,但是 ajax的请求在被处理完后,需要直接响应,因此需要告诉 DS直接进行响应 — @ResponseBody
-
配置 jackson依赖
SpringMVC的底层会自动调用该依赖中的资源,将响应数据转换为 json格式
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.17.1</version> </dependency>
-
在单元方法上增加 @ResponseBody注解,将响应数据直接 return即可(返回类型为要 return的数据类型)
/** * 处理 ajax请求 */ @RequestMapping("testAjax") @ResponseBody public User testAjax(String uname, Integer age) { System.out.println("单元方法,处理ajax请求:" + uname); return new User(uname, age); }
-
在 ajax回调函数中,直接使用返回的 json对象即可,无需再用 eval函数将响应数据转换为 json对象
12. 自定义视图解析器
12.1 默认视图解析器-ModelAndView
DispatcherServlet 底层是如何通过单元方法 return值中的关键字来区分是请求转发还是重定向呢? — 视图解析器
SpringMVC官方封装好了 请求转发(InternalResourceView)、重定向(RedirectView)、请求转发和重定向(ModelAndView) 这几个视图解析器,DispatcherServlet目前默认用的就是 ModelAndView
12.2 自定义视图解析器
可以让开发者根据需求配置一些内容,同时还具备 ModelAndView的逻辑 ---- InternalResourceViewResolver
它在接收到单元方法的返回值后,会给返回值增加一个前缀和后缀常量,然后返回拼接的内容
-
在 springmvc.xml中进行配置
<!-- 配置视图解析器 --> <bean id="resourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/packageA/page" /> <!-- 前缀内容 --> <property name="suffix" value=".jsp" /> <!-- 后缀内容 --> </bean>
配置好后,如果单元方法的返回值中,不带任何关键词(forward: 、redirect:)就会触发自定义的视图解析器,否则还是使用 ModelAndView来完成资源解析
而且自定义视图解析器只做资源的请求转发
12.3 使用场景
目前项目相关的页面资源及静态资源都直接声明在了 web目录下,并且在 springmvc.xml中配置了静态资源放行(<mvc:resources mapping="/js/**" location="/js/" />
)因此只要知道某个资源的URL地址,任何人都可以访问到,非常不安全
解决:WEB-INF文件夹,浏览器无法直接访问它下面的资源,必须通过内部请求转发才能访问;
@RequestMapping("getJsp")
public String getJsp() {
// 逻辑代码
return "/WEB-INF/jsp/index.jsp";
}
代码优化:
-
为避免资源过多,每次都要在单元方法中写前缀及后缀内容,结合自定义视图解析器 InternalResourceViewResolver进行优化
-
在 springmvc.xml中进行配置
<!-- 配置视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-IFN/jsp/" /> <!-- 前缀内容 --> <property name="suffix" value=".jsp" /> <!-- 后缀内容 --> </bean>
-
-
为避免资源过多,需要声明很多的单元方法,使用 restful声明公共的单元方法,转发 WEB-INF下的资源
-
声明单元方法
/** * 使用自定义视图解析器 + restful */ @RequestMapping("{uri}") public String getJsp(@PathVariable String uri) { System.out.println("使用自定义视图解析器,保证资源的安全"); System.out.println("使用restful,声明公共单元方法,完成 WEB-INF下的资源的转发"); return uri; // 直接写资源的名称即可,对应的为 /WEB-INF/jsp/xxx.jsp }
-
因此可以将 JS、CSS、image等资源也都放到 /WEB-INF目录下,同时需要修改 springmvc.xml文件中的资源放行配置
<!--配置静态资源放行-->
<mvc:resources mapping="/js/**" location="/WEB-INF/js/" /> <!-- 增加 /WEB-INF目录 -->
<mvc:resources mapping="/css/**" location="/WEB-INF/css/" />
<mvc:resources mapping="/images/**" location="/WEB-INF/images/" />
13. 上传功能
需求:将用户本地的资源,上传到服务器
前台 ajax代码示例:
// 获取请求数据(要上传的文件)
var file = $("#photo")[0].files[0]; // 存储了上传的文件信息的js对象 <input type="file" id="photo">
// 创建 FormData对象存储要上传的资源 ajax
var formData = new FormData();
formData.append("photo", file); // 后台获取数据的键名
// 发起ajax请求,上传请求数据
$.ajax({
url: "regUpload", // 处理上传请求的单元方法路径,后台 servlet地址
type: "post", // 请求方式,设置为post
processData: false, // 设置false
contentType: false, // 设置false,将请求数据类型设置为 multipart/form-data
data: formData, // 请求数据,用户要上传的文件的二进制数据
success: function (data) { // 上传成功后的回调函数,响应数据为json对象
if(data.state) {
alert("上传成功")
} else {
alert("上传失败")
}
}
})
在 springmvc.xml中配置上传解析器
<!-- 配置上传解析bean 注意!! id 必须为multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>
<!-- 注意:
从 Spring3.1开始,Spring提供了两个 MultipartResolver的实现用于处理 multiparty请求
CommonsMultipartResolver: 使用commons fileupload,需要引入对应 pom依赖
StandardServletMultipartResolver: 基于 Servlet3.0 (即Tomcat 7.0.x之后的版本)
-->
在 web.xml中配置上传解析器的初始化配置
<servlet>
<servlet-name>mvc</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>
<!-- 配置上传解析器的初始化配置 -->
<multipart-config>
<!-- 临时文件的目录 -->
<location>d:/</location>
<!-- 上传单个文件的最大限制,单位 字节 -->
<max-file-size>2097152</max-file-size>
<!-- 上传整个请求的最大限制,单位 字节-->
<max-request-size>4194304</max-request-size>
</multipart-config>
</servlet>
在 controller中书写单元方法进行处理
@Controller
public class MyController {
/**
* 在单元方法上直接声明形参接收数据(通过 photo键名获取数据:前台设置的名字)
* @param photo
*/
@RequestMapping("regUpload")
public void regUpload(MultipartFile photo) {
System.out.println(photo.getSize());
System.out.println("收到文件");
// 其他逻辑处理
}
}
14. 拦截器
在 DispatcherServlet之后,单元方法之前使用拦截器预处理请求
-
创建Java类,实现 HandlerInterceptor接口
public class MyInter implements HandlerInterceptor { /** * 拦截请求, 在单元方法之前执行 * * @param request 当次的请求对象 * @param response 当次的响应对象 * @param handler 实参实际是 HandlerMethod的对象, 由 DispatcherServlet传过来的 * HandlerMethod的对象中,存储了当次请求的单元方法的方法对象 * ========= 因此,可以通过反射调用请求的单元方法 ========= * @return true表示放行, false表示拦截 * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HandlerMethod hm = (HandlerMethod) handler; Method method = hm.getMethod(); return HandlerInterceptor.super.preHandle(request, response, handler); } /** * 在单元方法执行完,准备跳转资源之前执行 * * @param request 当次的请求对象 * @param response 当次的响应对象 * @param handler 实参实际是 HandlerMethod的对象, 由 DispatcherServlet传过来的 * @param modelAndView 当次请求的单元方法的返回值的对象(单元方法执行后的视图解析器对象) * ---- 可以对跳转的资源进行重置 或者对 model对象中流转的数据进行修改 * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // modelAndView.setViewName("重置的资源的地址"); modelAndView.getModel().put("键名", "要修改的值"); HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } /** * 处理在当次请求的任意位置出现的异常 * * @param request 当次的请求对象 * @param response 当次的响应对象 * @param handler 实参实际是 HandlerMethod的对象, 由 DispatcherServlet传过来的 * @param ex 处理过程中的异常信息 * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 打印异常堆栈信息 System.out.println(ex.getMessage()); HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
-
在 springmvc.xml中配置拦截器 bean
<!-- 配置拦截器的bean --> <mvc:interceptors> <!-- ======全局拦截器====== 直接配置bean --> <bean id="myInterAll" class="com.lwclick.interceptor.MyInter" /> <!-- ======局部拦截器====== mvc:interceptor 标签下--> <!-- 配置对应的实现bean,以及相应的拦截范围 --> <mvc:interceptor> <!-- 拦截范围:拦截单元方法的路径 --> <mvc:mapping path="/demo/"/> <!-- 可以配置多个标签,表示拦截多个路径 --> <bean id="myInter" class="com.lwclick.interceptor.MyInter" /> </mvc:interceptor> </mvc:interceptors>
如果存在多个拦截器,按照在 springmvc.xml中的位置进行触发: preHandle(配置靠上) --> preHandle(配置靠下) --> 单元方法 --> postHandle(配置靠下) --> postHandle(配置靠上) --> afterCompletion(配置靠下) --> afterCompletion(配置靠上)
15. SpringMVC运行原理
核心组件:
- DispatchServlet:Servlet分发器,整个SPringMVC框架入口
- HandlerMapping:寻找 URL所请求的 HandlerMethod,找 @RequestMapping()
- 实现类 DefaultAnnotationHandlerMapping实际起作用
- HandlerAdapter:实际调用HandlerMethod的组件
- 实现类 AnnotationMethodHandlerAdapter实际起作用
- ViewResovler:视图解析器,解析 HandlerMethod返回值,把逻辑视图转换为需要调用的物理视图
- 自定义时:InternalResourceViewResolver
当在 springmvc.xml中配置了 <mvc:annotation-driven/>
时,实际创建了以上 4个组件的 bean