MVC设计模型
- Model模型 JavaBean
- View视图 JSP
- Controller控制器 Servlet
共同点
- 它们都是表现层框架,都是基于 MVC 模型编写的。
- 它们的底层都离不开原始 ServletAPI。
- 它们处理请求的机制都是一个核心控制器。
区别
- Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
- Spring MVC 是基于方法设计的,而 Struts2 是基于类
- Struts2 每次执行都会创建一个动作类。所以 Spring MVC 会稍微比 Struts2 快些。
- Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 ajax 的请求更方便
- (JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们JavaBean 的属性上面,就可以在需要校验的时候进行校验了。)
- Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些,但执行效率并没有比 JSTL 提升,尤其是 struts2 的表单标签,远没有 html 执行效率高
搭建环境
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<spring.version>5.0.2.RELEASEspring.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>2.1version>
<scope>providedscope>
dependency>
dependencies>
执行流程
入门案例的执行流程
- 当启动Tomcat服务器的时候,因为配置了load-on-startup标签,所以会创建DispatcherServlet对象,
就会加载springmvc.xml配置文件- 开启了注解扫描,那么HelloController对象就会被创建
- 从index.jsp发送请求,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解
找到执行的具体方法- 根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的JSP文件(方法的返回值就是指定目录下的JSP文件名)
- Tomcat服务器渲染页面,做出响应
springmvc.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" 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/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"> <context:component-scan base-package="com.atgw"/> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".jsp"/> bean> <mvc:annotation-driven/> beans>
web.xml
<web-app> <display-name>Archetype Created Web Applicationdisplay-name> <servlet> <servlet-name>dispatcherServletservlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class> <init-param> <param-name>contextConfigLocationparam-name> <param-value>classpath:springmvc.xmlparam-value> init-param> <load-on-startup>1load-on-startup> servlet> <servlet-mapping> <servlet-name>dispatcherServletservlet-name> <url-pattern>/url-pattern> servlet-mapping> web-app>
ControllerHolle
程序//控制器类 @Controller public class HelloController { @RequestMapping(path = "/hello") public String sayHello(){ System.out.println("Hello StringMVC"); return "success"; } }
当RequestMapping
注解在类和类下的方法上都有的时候,在请求的路径上要同时写全路径
//控制器类
@Controller
@RequestMapping(path = "/user")
public class HelloController {
@RequestMapping(path = "/testRequestMapping")
public String testRequestMapping(){
System.out.println("测试RequestMapping");
return "success";
}
}
下面要写全路径(类的加上方法的path)
RequestMapping超链接
作用
value
:用于请求指定的url,它和path属性的作用是一样的
method
:用于指定请求方式(Get,Post等)
@RequestMapping(value = "/testRequestMapping",method = {RequestMethod.POST})
public String testRequestMapping(){
System.out.println("测试RequestMapping");
return "success";
}
params
:用于指定限定请求参数的条件,它支持简单的表达式,要求请求参数的key和value必须和配置的一模一样
@RequestMapping(value = "/testRequestMapping",params = {"username=admin","password"})
public String testRequestMapping(){
System.out.println("测试RequestMapping");
return "success";
}
RequestMapping超链接
发送请求中必须包含指定的请求头
在请求的路径中传入参数,我们可以在控制器类中的方法中写入参数并获取(底层是通过反射实现的)
ParamController
程序
@Controller
@RequestMapping(path = "/user")
public class ParamController {
@RequestMapping(path = "/testParam")
public String testParam(String username,String password){
System.out.println("用户名:"+username);
System.out.println("密码:"+password);
return "success";
}
}
param.jsp
Param请求
User
类
public class User {
private String uname;
private Integer age;
}
Account
类
public class Account implements Serializable {
private String username;
private String password;
private Double money;
//引用类型
private User user;
}
param.jsp
web.xml
文件
<filter>
<filter-name>characterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
filter>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
Account
类
public class Account implements Serializable {
private String username;
private String password;
private Double money;
private List<User> list;
private Map<String,User> map;
}
param.jsp
Account{username='gao123', password='123456', money=999.0, list=[User{uname='熊大', age=9}], map={one=User{uname='熊二', age=8}}}
先创建一个类型转换器(实现Converter
接口)
public class StringToDateConverter implements Converter<String, Date> {
@Override
public Date convert(String s) {
//判断是否为空
if (s == null){
throw new RuntimeException("您还没有传入数据!");
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
//字符串转换成日期
return df.parse(s);
} catch (Exception e) {
throw new RuntimeException("数据类型转换出现错误!");
}
}
}
springmvc.xml
配置自定义类型转换器
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.atgw.utils.StringToDateConverter">bean>
set>
property>
bean>
<mvc:annotation-driven conversion-service="conversionService"/>
//获取servlet原生的API
@RequestMapping(path = "/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response){
System.out.println(request);
HttpSession session = request.getSession();
System.out.println(session);
ServletContext servletContext = session.getServletContext();
System.out.println(servletContext);
System.out.println(response);
return "success";
}
Servlet原生的API
RequestParam
注解@Controller
@RequestMapping("/anno")
public class AnnoController {
@RequestMapping("/testRequestParam")
public String testRequestParam(@RequestParam(name = "name") String username){
System.out.println(username);
return "success";
}
}
<Anno链接
当请求的路径中的参数name
和控制器类的方法获取的参数名不一致时,我们可以添加@RequestParam(name = "name")
注解来配置
RequestBody
注解
用于获取请求体的内容,直接使用得到的是key=value&key=value结构的数据
因为
Get
请求没有请求体,所以Get
请求不适用
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String body){
System.out.println(body);
return "success";
}
结果:
username=gao123&age=12
PathVaribale
注解Restful风格的URL
一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制
- 请求路径一样,可以根据不同的请求方式去执行后台的不同方法
- restful风格的URL优点
- 结构清晰
- 符合标准
- 易于理解
- 扩展方便
【GET】 /users # 查询用户信息列表 【GET】 /users/1001 # 查看某个用户信息 【POST】 /users # 新建用户信息 【PUT】 /users/1001 # 更新用户信息(全部字段) 【PATCH】 /users/1001 # 更新用户信息(部分字段) 【DELETE】 /users/1001 # 删除用户信息
@RequestMapping("testRequestVariable/{sid}")
public String testPathVariable(@PathVariable(name = "sid") String id){
System.out.println(id);
return "success";
}
RequestVariable注解
RequestHeader
注解获取请求头的值
//获取RequestHeader注解中value值对应的请求头
@RequestMapping("testRequestHeader")
public String testRequestHeader(@RequestHeader(value = "accept") String header){
System.out.println(header);
return "success";
}
testRequestHeader注解
结果:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
CookieValue
注解获取Cookie的值
//获取Cookiev值
@RequestMapping("testCookieValue")
public String testCookieValue(@CookieValue(value = "JSESSIONID") String cookie){
System.out.println(cookie);
return "success";
}
testCookieValue注解
结果:
5800F99C212F2A64740BB9B558ED6803
ModelAttribute
注解作用
- 出现在方法上:表示当前方法会在控制器方法执行前线执行
- 出现在参数上:获取指定的数据给参数赋值。
应用场景
- 当提交表单数据不是完整的实体数据时,保证没有提交的字段使用数据库原来的数据。
有返回值
@RequestMapping(value = "/testModelAttribute")
public String testModelAttribute(User user){
System.out.println("testModelAttribute........");
System.out.println(user);
return "success";
}
//该方法会先执行
@ModelAttribute
public User showUser(String uname){
System.out.println("showUser方法执行了");
//通过User查询数据库(模拟)
User user = new User();
user.setUname(uname);//uname是从表单中获取的
user.setAge(20);//如果表单没有此项,则会采用从数据库中获取的
user.setDate(new Date());
return user;
}
无返回值
@RequestMapping(value = "/testModelAttribute")
public String testModelAttribute(@ModelAttribute("123") User user){
System.out.println("testModelAttribute........");
System.out.println(user);
return "success";
}
//该方法会先执行
@ModelAttribute
public void showUser(String uname, Map<String,User> map){
System.out.println("showUser方法执行了");
//通过User查询数据库(模拟)
User user = new User();
user.setUname(uname);//uname是从表单中获取的
user.setAge(20);//如果表单没有此项,则会采用从数据库中获取的
user.setDate(new Date());
map.put("123",user);
}
结果:
showUser方法执行了
testModelAttribute........
User{uname='gao123', age=12, date=Fri Feb 05 19:48:36 CST 2021}
SessionAttributes
注解用于多次执行控制器方法间的参数共享
@Controller
@RequestMapping("/anno")
@SessionAttributes(value = {"msg"})//把msg=美美存入到Session域
public class AnnoController {
@RequestMapping(value = "/testSessionAttributes")
public String testSessionAttributes(Model model){
//底层会存储到request域对象中
model.addAttribute("msg","美美");
return "success";
}
}
SessionAttributes注解
登陆成功页面获取域对象的值
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
${requestScope}
${sessionScope}
Session域中的数据同样也可以在其他的方法中获取
//从Session域获取值
@RequestMapping(value = "/getSessionAttribute")
public String getSessionAttribute(ModelMap modelMap){
System.out.println(modelMap.get("msg"));
return "success";
}
因为返回类型是void,所以就不能再对应指定目录下的响应文件了
采取下面的方法
请求转发(一次请求)
@RequestMapping("/testVoid")
public void testVoid(HttpServletRequest request, HttpServletResponse response)throws Exception{
//编写请求转发的程序
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
return;
}
testVoid方法
重定向(两次请求)
//重定向(WEB-INF里面的内容不能直接请求)
response.sendRedirect(request.getContextPath()+"/index.jsp");
直接响应
@RequestMapping("/testVoid")
public void testVoid(HttpServletRequest request, HttpServletResponse response)throws Exception{
//编写请求转发的程序
//request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
//重定向(WEB-INF里面的内容不能直接请求)
// response.sendRedirect(request.getContextPath()+"/index.jsp");
// return;
//设置中文乱码
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//直接响应
response.getWriter().print("你好");
return;
}
forward
和redirect
进行页面跳转使用上面的方式进行页面跳转时,就不使用视图解析器对象了
//使用关键字forward和redirect来进行页面跳转
@RequestMapping("/testForwardOrRedirect")
public String testForwardOrRedirect(){
//请求转发
// return "forward:/WEB-INF/pages/success.jsp";
//重定向(重定向不能访问WEB-INF里面的内容)
return "redirect:/index.jsp";
}
testForwardOrRedirect
我们在
web.xml
文件中配置了前端控制器,它会对所有的资源进行拦截过滤,导致我们的静态资源(webapp目录下的css,js,images目录)不能正常使用,所以我们在springmvc.xml
文件中对前端控制器进行配置,让其对静态资源不进行拦截
springmvc.xml
文件
<mvc:resources mapping="/js/**/" location="/js/">mvc:resources>
<mvc:resources mapping="/css/**/" location="/css/">mvc:resources>
<mvc:resources mapping="/images/**/" location="/images/">mvc:resources>
下面静态资源可以正常访问
//返回值是ModelAndView类型
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
//创建ModelAndView对象
ModelAndView mv = new ModelAndView();
//模拟从数据库中查询出对象
User user = new User();
user.setUsername("O(∩_∩)O哈哈~");
user.setAge(20);
user.setPassword("234");
//把user对象存储到mv对象中,也会把user对象存入到request对象
mv.addObject("user",user);
//跳转到哪个页面
mv.setViewName("success");
return mv;
}
testModelAndView
上面的操作也会把user对象存入到request对象
success.jsp
页面
${requestScope.user}
${requestScope.user.username}
${requestScope.user.password}
${requestScope.user.age}
response.jsp
//模拟异步请求响应
@RequestMapping("/testAjax")
public void testAjax(@RequestBody String body){//获取请求体
System.out.println("testAjax方法执行了");
System.out.println(body);
}
后台响应数据之后(封装修改)做出回传响应
//模拟异步请求响应
@RequestMapping("/testAjax")
public @ResponseBody User testAjax(@RequestBody User user){//获取请求体
System.out.println("testAjax方法执行了");
//客户端发送ajax请求,传的是json字符串,后端把json字符串封装到user对象中
System.out.println(user);
//作出相应,模拟查询数据库,修改数据之后返回
user.setUsername("山地车");
user.setAge(50);
//返回相应
return user;
}
ajax
做出响应
success:function (data) {
//data服务器响应后端的json数据
alert(data.toString());
alert(data.username);
alert(data.password);
alert(data.age);
}
文件上传坐标
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.1version>
dependency>
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.4version>
dependency>
文件上传
//文件上传
@RequestMapping(value = "/fileupload1")
public String fileupload1(HttpServletRequest request) throws Exception {
System.out.println("文件上传");
//使用fileupload组件完成文件上传
//上传的位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");
System.out.println(path);
//判断该路径是否存在
File file = new File(path);
if (!file.exists()){
//创建该文件夹
file.mkdirs();
}
//解析request对象,获取上传文件项
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
//解析request
List<FileItem> items = upload.parseRequest(request);
//遍历
for (FileItem item : items){
//进行判断,当前item对象是否是文件上传项
if (item.isFormField()){
//说明是普通表单项
}else {
//说明上传文件项
//获取上传文件名
String filename = item.getName();
//把文件名设置成唯一值(使用UUID)
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid + "_" +filename;
//完成文件上传
item.write(new File(path,filename));
//删除临时文件
item.delete();
}
}
return "success";
}
springmvc方式文件上传
//springmvc方式文件上传
@RequestMapping(value = "/fileupload2")
public String fileupload2(HttpServletRequest request, MultipartFile upload) throws Exception {
System.out.println("SpringMVC文件上传");
//使用fileupload组件完成文件上传
//上传的位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");
System.out.println(path);
//判断该路径是否存在
File file = new File(path);
if (!file.exists()){
//创建该文件夹
file.mkdirs();
}
//获取上传文件名
String filename = upload.getOriginalFilename();
//把文件名设置成唯一值(使用UUID)
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid + "_" +filename;
//完成文件上传
upload.transferTo(new File(path,filename));
return "success";
}
坐标
<dependency>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-coreartifactId>
<version>1.18.1version>
dependency>
<dependency>
<groupId>com.sun.jerseygroupId>
<artifactId>jersey-clientartifactId>
<version>1.18.1version>
dependency>
跨服务器方式文件上传
//跨服务器方式文件上传
@RequestMapping(value = "/fileupload3")
public String fileupload3(MultipartFile upload) throws Exception {
System.out.println("跨服务器文件上传");
//定义上传文件的服务器路径
String path = "http://localhost:9090/uploads/";
//获取上传文件名
String filename = upload.getOriginalFilename();
//把文件名设置成唯一值(使用UUID)
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid + "_" +filename;
//创建客户端对象
Client client = Client.create();
//和图片服务器进行连接
WebResource webResource = client.resource(path + filename);
//完成文件上传
webResource.put(upload.getBytes());
return "success";
}
自定义异常类
public class SysException extends Exception {
private String message;
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public SysException(String message) {
this.message = message;
}
}
自定义异常处理器
public class SysExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception ex) {
//获取到异常对象
SysException e = null;
if (ex instanceof SysException){
e = (SysException)ex;
}else {
e = new SysException("系统正在维护...");
}
//创建ModelAndView对象
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg",e.getMessage());//获取错误信息
mv.setViewName("error");
return mv;
}
配置异常处理器
<bean id="sysExceptionResolver" class="com.atgw.exception.SysExceptionResolver">
bean>
@Controller
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping("/testException")
public String testException() throws Exception{
System.out.println("testException执行了");
try {
//模拟异常
int a = 10/0;
}catch (Exception e){
//打印异常信息
e.printStackTrace();
//抛出自定义异常信息
throw new SysException("查询所有用户出现错误了");
}
return "success";
}
}
异常处理
友好提示页面error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
${errorMsg}
SpringMVC拦截器类似于Servlet开发中的过滤器,用于处理器进行预处理和后处理
Controller
进行拦截MyInterceptor1
自定义拦截器
//自定义拦截器
public class MyInterceptor1 implements HandlerInterceptor {
/*
预处理,controller方法执行前
return true 放行,执行下一个拦截器,如果没有,执行controller中的方法
return false 不放行,权限检查
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor1拦截器执行了...前");
//不放行时,请求转发到error.jsp页面
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
return true;
}
/*
controller方法执行后,success.jsp方法执行前
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("controller方法执行后,success.jsp方法执行前");
//如果在这里进行页面跳转的话,比如跳转到error.jsp页面,success.jsp页面会执行,但是不会跳转到success.jsp
}
/*
success.jsp页面之后
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("success.jsp页面之后");
}
}
springmvc.xml
配置拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/user/*"/>
<bean class="com.atgw.interceptor.MyInterceptor1">bean>
mvc:interceptor>
mvc:interceptors>
index.jsp
拦截器
拦截器
UserController
程序
@Controller
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping("/testInterceptor")
public String testInterceptor() {
System.out.println("testInterceptor执行了");
return "success";
}
}