springmvc是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合。springmvc是一个基于mvc的web框架。
发起请求到前端控制器(DispatcherServlet)
前端控制器请求HandlerMapping查找 Handler(可以根据xml配置、注解进行查找)
处理器映射器HandlerMapping向前端控制器返回Handler
前端控制器调用处理器适配器去执行Handler
处理器适配器去执行Handler
Handler执行完成给适配器返回ModelAndView
处理器适配器向前端控制器返回ModelAndView(ModelAndView是springmvc框架的一个底层对象,包括 Model和view)
前端控制器请求视图解析器去进行视图解析,根据逻辑视图名解析成真正的视图(jsp)
视图解析器向前端控制器返回View
前端控制器进行视图渲染,视图渲染将模型数据(在ModelAndView对象中)填充到request域
前端控制器向用户响应结果
前端控制器DispatcherServlet(不需要程序员开发)
作用:接收请求,响应结果,相当于转发器,中央处理器。DispatcherServlet减少了其它组件之间的耦合度。
处理器映射器HandlerMapping(不需要程序员开发)
作用:根据请求的url查找Handler
处理器适配器HandlerAdapter
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler
处理器Handler(需要程序员开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler
视图解析器View resolver(不需要程序员开发)
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
视图View(需要程序员开发jsp)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)
<servlet>
<servlet-name>springMvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springMvc.xmlparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>springMvcservlet-name>
<url-pattern>*.actionurl-pattern>
servlet-mapping>
注意:
在配置的时候需要配置init-param,这里是为了初始化Spring容器
关于url-pattern(Servlet的拦截方式)
拦截固定后缀的url,比如设置为 .do、.action, 例如:/user/add.action
此方法最简单,不会导致静态资源(jpg,js,css)被拦截。
拦截所有,设置为/,例如:/user/add /user/add.action
此方法可以实现REST风格的url,很多互联网类型的应用使用这种方式。但是此方法会导致静态文件(jpg,js,css)被拦截后不能正常显示。需要特殊处理。
拦截所有,设置为/*,此设置方法错误,因为请求到Action,当action转到jsp时再次被拦截,提示不能根据jsp路径mapping成功。
在Spring的配置文件中进行配置
<bean id="adapter" class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
- 处理器适配器可以处理所有实现了Controller接口的类
- SimpleControllerHandlerAdapter:即简单控制器处理适配器,所有实现了org.springframework.web.servlet.mvc.Controller 接口的Bean作为SpringMVC的后端控制器。
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/*
必须实现Controller接口
*/
public class ItemList01 implements Controller {
@Override
public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
List<Items> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Items items = new Items();
items.setPrice((i+1)*120.0f);
items.setCreatetime(new Date());
items.setDetail("这是第"+(i+1)+"个作品");
items.setId(i);
items.setName("电脑"+i);
list.add(items);
}
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("itemsList",list);
modelAndView.setViewName("/WEB-INF/jsp/items/itemsList.jsp");
return modelAndView;
}
}
在Spring的配置文件中配置
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
在Spring的配置文件中配置
<bean name="/queryList/itemsList.action" class="zzuli.zw.learnSpringMVC.controller.ItemList01"/>
name值将来作为访问路径url
在Spring配置文件中配置
<bean id="resolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>
在非注解情况下,我们可以使用两种方式的处理器映射器:
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
这种方式的处理器映射器会根据配置的Handler的name来查找Handler
此时的Handler配置方式
<bean name="/queryList/itemsList.action" class="zzuli.zw.learnSpringMVC.controller.ItemList01"/>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/queryList/itemsList02.action">list01prop>
<prop key="/query/itemsList.action">list01prop>
props>
property>
bean>
其中key为url路径,值为配置的Handler的id
此时的Handler的配置方式
<bean id="list01" name="/queryList/itemsList.action" class="zzuli.zw.learnSpringMVC.controller.ItemList01"/>
注意:
在非注解情况下我们也可以使用两种适配器,他们有不同的使用场景
<bean id="adapter" class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
使用这种适配器,处理器必须实现Controller接口才行
此时的处理器:
public class ItemList01 implements Controller {
@Override
public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse){
List list = new ArrayList<>();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("itemsList",list);
modelAndView.setViewName("/WEB-INF/jsp/items/itemsList.jsp");
return modelAndView;
}
}
可以看到,里面只有一个方法,而且最终返回的是ModelAndView对象
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"/>
使用这种适配器,处理器必须实现HttpRequestHandler接口
此时的处理器:
public class ItemList02 implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
}
}
可以看到,里面同样只有一个方法,但是返回值为void,这就意味着我们可以使用response来返回json串
两种方式的比较:
注解中的处理器映射器和处理器适配器需要配对使用
一般使用的是以下两个:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
使用注解配置有两种方式:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
<mvc:annotation-driven/>
实际开发过程中推荐使用第二种方式进行配置
@Controller("itemList")
public class ItemList03 {
@RequestMapping("/itemsList/queryItems")
public ModelAndView queryItems(){
List list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Items items = new Items();
items.setPrice((i+1)*120.0f);
items.setCreatetime(new Date());
items.setDetail("这是第"+(i+1)+"个作品");
items.setId(i);
items.setName("电脑"+i);
list.add(items);
}
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("itemsList",list);
modelAndView.setViewName("/WEB-INF/jsp/items/itemsList.jsp");
return modelAndView;
}
}
其中我们可以使用@RequestMapping注解来替代原来非注解配置url的方式。
<bean id="resolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
bean>
为视图解析器配置两个参数即可
配置之前
modelAndView.setViewName("/WEB-INF/jsp/items/itemsList.jsp");
配置之后
modelAndView.setViewName("items/itemsList");
通过@RequestMapping注解可以定义不同的处理器映射规则。
在class上添加@RequestMapping(url)指定通用请求前缀, 限制此类下的所有方法请求url必须以请求前缀开头,通过此方法对url进行分类管理。
如下:
@RequestMapping放在类名上边,设置请求前缀
@Controller
@RequestMapping("/item")
方法名上边设置请求映射url:
@RequestMapping放在方法名上边,如下:
@RequestMapping("/queryItem ")
@RequestMapping(method = RequestMethod.GET)
//如果通过Post访问则报错:HTTP Status 405 - Request method 'POST' not supported
@RequestMapping(method = RequestMethod.POST)
@RequestMapping(method={RequestMethod.GET,RequestMethod.POST})
controller方法中定义ModelAndView对象并返回,对象中可添加model数据、指定view。
public ModelAndView selectByName(){
ItemsQueryAo query = new ItemsQueryAo();
Items items = new Items();
items.setName("电脑");
query.setItems(items);
List list = itemsService.getByName(query);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("itemsList",list);
modelAndView.setViewName("items/itemsList");
return modelAndView;
}
该方式默认使用请求转发
public String queryAll(Model model){
List list = itemsService.getByName(null);
model.addAttribute("itemsList",list);
return "items/itemsList";
}
controller方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
使用该凡是返回的字符串默认使用请求转发的方式解析,同时可以使用加前缀的方式来实现转发和重定向
//重定向
return "redirect:selectAll.action";
//请求转发
return "forward:selectAll.action";
在controller方法形参上可以定义request和response,使用request或response指定响应结果:
public void query(HttpServletRequest request, HttpResponse response){
}
处理器适配器在执行Handler之前需要把http请求的key/value数据绑定到Handler方法形参数上。
注解适配器对RequestMapping标记的方法进行适配,对方法中的形参会进行参数绑定,早期springmvc采用PropertyEditor(属性编辑器)进行参数绑定,将request请求的参数绑定到方法形参上,3.X之后springmvc就开始使用Converter进行参数绑定。
使用@RequestParam常用于处理简单类型的绑定。
value:参数名字,即入参的请求参数名字,如value=“item_id”表示请求的参数区中的名字为item_id的参数的值将传入
required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报;
TTP Status 400 - Required Integer parameter ‘XXXX’ is not present
defaultValue:默认值,表示如果请求中没有同名参数时的默认值
定义如下:
public String editItem(@RequestParam(value="item_id",required=true) String id) {
}
形参名称为id,但是这里使用value=" item_id"限定请求的参数名为item_id,所以页面传递参数的名必须为item_id。
注意:如果请求参数中没有item_id将跑出异常:
HTTP Status 500 - Required Integer parameter ‘item_id’ is not present
这里通过required=true限定item_id参数为必需传递,如果不传递则报400错误,可以使用defaultvalue设置默认值,即使required=true也可以不传item_id参数值
public String query(@RequestParam(value = "id",required = true,defaultValue = "1") Integer id){
System.out.println(id);
return "forward:/hello.html";
}
将pojo对象中的属性名于传递进来的属性名对应,如果传进来的参数名称和对象中的属性名称一致则将参数值设置在pojo对象中
public void query(@RequestParam(value = "id",defaultValue = "1") Integer id,User user){
user.setId(id);
System.out.println(user);
}
所谓包装pojo就是类似于以下方式的对象:
Public class QueryVo {
private Items items;
}
在实际开发过程中我们可能需要处理各种各样的数据类型,一些简单的数据类型SpringMVC已经帮我们处理好了,但是想Date这种比较复杂的数据类型就需要我们根据需求去自定义转换器,SpringMVC为我们提供了一个接口,我们可以自己去进行实现。
public class DateConverter implements Converter {
@Override
public Date convert(String s) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
return simpleDateFormat.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
Converter支持泛型,第一个泛型变量是要转换的参数,第二个是目标参数类型,例如上面就是将String转换为Date类型。
注意:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
<mvc:annotation-driven conversion-service="conversionService"/>
如果需要进行自定义绑定,那么只需要留下面的配置就可以了,上面的会产生冲突,使配置失效
需求:
在实际项目开发过程中,可能会需要批量增加或者删除信息,比如说批量删除购物车中的内容,或者学校系统批量增加学生信息。这时候就需要用到List集合来接受参数。
private List customList;
@RequestMapping(value = "/testList",method = {RequestMethod.GET,RequestMethod.POST})
public String testList(ItemsQueryAo queryAo){
//Stream流
Stream.of(queryAo.getCustomList()).forEach(System.out::println);
return "items/itemsList";
}
<%这里需要注意,customList是ItemsQueryAo中的字段名称,后面是序号%>
内置的参数绑定
HttpServletRequest、HttpServletResponse、HttpSession、Model
基本类型参数绑定
int、double/float、String、boolean
简单的pojo/domain
前端的input中name名称与pojo/domain中的字段相对应即可
复合的pojo/domain
为了便于程序的拓展与维护,我们往往会使用复合的pojo,复合的pojo中包含简单的pojo,在使用的时候前端页面需要:
pojo:
private ItemsCustom itemsCustom;
List类型绑定
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-validatorartifactId>
<version>6.1.4.Finalversion>
dependency>
<dependency>
<groupId>org.jboss.logginggroupId>
<artifactId>jboss-loggingartifactId>
<version>3.4.1.Finalversion>
dependency>
<dependency>
<groupId>javax.validationgroupId>
<artifactId>validation-apiartifactId>
<version>2.0.1.Finalversion>
dependency>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<property name="validationMessageSource" ref="messageSource"/>
bean>
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>classpath:Messagevalue>
list>
property>
<property name="defaultEncoding" value="UTF-8"/>
<property name="fileEncodings" value="utf-8"/>
<property name="cacheSeconds" value="120"/>
bean>
public void edits(@Validated ItemsCustom itemsCustom,BindingResult bindingResult){
if (bindingResult.hasErrors()){
for (ObjectError allError : bindingResult.getAllErrors()) {
System.out.println(allError.getCode());
}
}
}
@Size(min = 2,max = 15,message = "{mvc.items.name.length}")
private String name;
https://blog.csdn.net/yangqh521/article/details/81906944
编码问题:
<property name="defaultEncoding" value="UTF-8"/>
不适用于校验JavaBean中复合的JavaBean。
在实际开发中我们有时会用到数据回显,所谓的数据回显实际上就是将数据存放到request域中,这样数据就不会丢失,在jsp页面中这种回显数据的方式还是很常用的。主要可以通过三种方式来实现数据回显。
SpringMVC在默认情况下会将pojo数据存入request域中,key等于pojo类型(首字母小写)。
但是在一些情况下,可能jsp页面中使用的并非pojo类型(首字母小写),这时就需要使用一个注解:
@RequestAttribute("itemsCustom")
该注解用在方法上,作用是将方法的返回值存入到request域中
@ModelAttribute("itemsCustom")
public ItemsCustom getThis(){
return new ItemsCustom();
}
@RequestMapping("/sele")
public String toEdits(Model model){
model.addAttribute("itemsCustom",new ItemsCustom());
return "items/edit";
}
需要导入的jar包:
由于SpringMVC的上传是依赖于fileupload上传组件以及commons-io的,因此需要在pom.xml中添加相应的依赖。
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.6version>
dependency>
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.1version>
dependency>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="5242880"/>
<property name="defaultEncoding" value="UTF-8"/>
bean>
多部件解析器用来解析前端表单的file控件内容,需要在SpringMVC的配置文件中进行相应的配置。
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="files">
<input type="submit" value="提交">
form>
在表单设计时,我遇到了一个问题:本来只要设计enctype="multipart/form-data"
就可以了,但是在使用SpringMVC的过程中,我发现只设计成会报异常,必须指定method=“post”,不知道是什么愿意,记录一下。
@RequestMapping(value = "/uploading")
public void upload(@RequestPart(value = "files") MultipartFile files, HttpServletRequest request) throws IOException {
if (files != null){
String oldFileName = files.getOriginalFilename();
if (oldFileName != null && !oldFileName.trim().isEmpty()) {
String suffix = oldFileName.substring(oldFileName.indexOf("."));
if (!suffix.trim().isEmpty()) {
String newFileName = oldFileName.substring(0, oldFileName.indexOf("."));
int index = newFileName.lastIndexOf("//");
if (index != -1){
newFileName = newFileName.substring(index+1);
}
String newUUID = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
String newUUIDFileName = newFileName+newUUID+suffix;
String realPath = request.getServletContext().getRealPath("/WEB-INF/files");
Date date = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DATE);
StringBuilder sb = new StringBuilder();
String finalFilePath = sb.append(realPath).append("/").append(year).append("/").append(month)
.append("/").append(day).append("/").append(newUUIDFileName).toString();
File file = new File(finalFilePath);
file.mkdirs();
files.transferTo(file);
// IOUtils.copy(files.getInputStream(),new FileOutputStream(file));
//将数据写入数据库
}else{
//输出错误信息,图片格式无法识别
}
}else{
//输出错误信息,请选择图片!
}
}
}
主要就是通过参数绑定:MultipartFile
来进行解析。
注意:表单中的name也需要和MulitpartFile的名称相同,也可以通过RequestParam注解来自行指定。
SpringMVC进行json交互主要依赖jackson,所以需要在pom.xml中添加相应的依赖。同时如果没有使用mvc的注解驱动,那么需要自己在控制器适配器中注入相应的依赖。
对于前端的设计来说,有的框架会将所有的表单数据封装为json对象,发送给后端,后端收到的是字符串,这是通过这个注解可以自动将前端发来的json串转换为Java对象。如果使用使用key/value的传统方式进行前后端交互,那么就不用使用此注解。
和上面的相对应,该注解将返回值转换为json格式传递给前端解析。
RESTful架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
RESTful是一种规范,这种规范在URI路径上体现的比较明显,传统方式下我们传递参数往往通过以下的方式
/items/query.action?a=1&b=2
这种格式使用起来比较复杂,不直观,在使用RESTful规范后可以写成下面的格式
/items/query/1/2
这种方式更为简洁,直观。
<mvc:resources mapping="/js/" location="/js/**"/>
使用RESTful规范可能会导致静态资源无法访问,因此我们需要在配置文件中配置静态资源路径进行过滤。
@RequestMapping("/add/{a}/{b}")
public String add(@PathVariable int a,@PathVariable int b, Model model){
String result = "结果为:" + a + b;
model.addAttribute("msg",result);
return "test";
}
<servlet>
<servlet-name>springMvcservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springMVC-config.xmlparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>springMvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
上面注释掉的是之前的使用方式,通过后缀配置url,使用RESTful需要按照下方的方式进行配置。
拦截器的作用类似于之前的Filter,但是并不是Filter,SpringMVC使用了动态代理对方法进行增强,功能更为强大。
实现HandlerInterceptor接口:
public class HandlerInterceptor01 implements HandlerInterceptor {
/**
* 该方法是在Controller执行之前进行的,可以用来进行登录校验等工作,返回值为boolean类型,如果返回为true表示可以放行,否则则进行拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
return false;
}
/**
*进入Handler方法之后,返回modelAndView之前执行,可以用来设置一些页面公用的东西,比如路径导航等
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 执行Handler完成执行此方法,该方法可以用来进行全局异常处理,也可以记录日志等
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
}
}
SpringMVC拦截的是处理器映射器映射的路径即注解中RequestMapping中的路径,在配置过程中,可以配置多个拦截器。
这里有两种配置方式,一种是将自定义的拦截器注入到注解处理器映射器中,一种是配置类似全局拦截器的方式。这里只对第二种进行讲解。
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="zzuli.zw.learnSpringMVC.controller.HandlerInterceptor01"/>
mvc:interceptor>
mvc:interceptors>
这里可以配置多个拦截器,而且是顺序执行的。
进行登录认证,如果是公众网页直接放行,否则重定向到登录界面
@Controller
public class LoginController {
// 登陆
@RequestMapping("/login")
public String login(HttpSession session, String username, String password)
throws Exception {
// 调用service进行用户身份验证
// ...
// 在session中保存用户身份信息
session.setAttribute("username", username);
// 重定向到商品列表页面
return "redirect:/items/queryItems.action";
}
// 退出
@RequestMapping("/logout")
public String logout(HttpSession session) throws Exception {
// 清除session
session.invalidate();
// 重定向到商品列表页面
return "redirect:/items/queryItems.action";
}
}
public class LoginInterceptor implements HandlerInterceptor {
//进入 Handler方法之前执行
//用于身份认证、身份授权
//比如身份认证,如果认证通过表示当前用户没有登陆,需要此方法拦截不再向下执行
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//获取请求的url
String url = request.getRequestURI();
//判断url是否是公开 地址(实际使用时将公开 地址配置配置文件中)
//这里公开地址是登陆提交的地址
if(url.indexOf("login.action")>=0){
//如果进行登陆提交,放行
return true;
}
//判断session
HttpSession session = request.getSession();
//从session中取出用户身份信息
String username = (String) session.getAttribute("username");
if(username != null){
//身份存在,放行
return true;
}
//执行这里表示用户身份需要认证,跳转登陆页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp")
.forward(request, response);
//return false表示拦截,不向下执行
//return true表示放行
return false;
}