本文是Spring第三讲:SpringMVC 从入门到精通
文章目录
-
-
- 1、SpringMVC 执行流程及工作原理 喜闻乐见的面试题
- 2、SpringMVC常用注解都有哪些?
- 3、如何解决get和post乱码问题?
- 4、 参数绑定(从请求中接收参数) 重点
- 5、Rest和Http什么关系? 大家都说Rest很轻量,你对Rest风格如何理解?
- 6、 SpringMVC或Struts处理请求流程区别?
- 7、SpringMVC全局异常处理/整个系统只有一个
- 8、SpringMvc 拦截器用过吗?什么场景会用到,过滤器,拦截器,监听器有什么区别?
- 9、SpringMVC的定制配置
- 10、@DateTimeFormat 和 @JsonFormat 注解
-
1、SpringMVC 执行流程及工作原理 喜闻乐见的面试题
1、springMvc是什么?(市场占有率40%,web开发当之无愧的霸主)
- 一个表现层框架,就是从请求中接收传入的参数,然后将处理后的结果数据返回给页面展示
- a. 用户向服务器发送请求,请求被springMVC前端控制器DispatchServlet捕获;
- b. DispatcherServle对请求URL进行解析,得到请求资源标识符(URL),然后根据该URL调用HandlerMapping将请求映射到处理器HandlerExcutionChain;
- c. DispatchServlet根据获得Handler选择一个合适的HandlerAdapter适配器处理;
- d. Handler对数据处理完成以后将返回一个ModelAndView对象给DispatchServlet;
- e. Handler返回的 ModelAndView只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet 通过ViewResolver视图解析器将逻辑视图转化为真正的视图View;
- h. DispatcherServle通过model解析出ModelAndView中的参数进行解析最终展现出完整的view并返回给客户端;
待补充
启动流程和运行流程有何区别?20181222
待补充
2、SpringMVC常用注解都有哪些?
1、 @RequestMapping 用来处理请求地址映射的注解,可用于类或方法上
注解在方法上的@RequestMapping路径会继承注解在类上的路径
- 示例: @RequestMapping("/anno") 映射此类的访问路径是**/anno**
- @RequestMapping(produces=“application/json; charset=UTF-8”) 返回值是json对象,字符集是UTF-8
- @RequestMapping(value="https://www.it610.com/pathvar/{str};
produces=“application/json;
charset=UTF-8”)
method1(@PathVariable String str,HttpServletRequest request) //接受路径参数,并在方法参数前结合@PathVariable使用,访问路径是 /anno/pathvar/xx - @RequestMapping(value="https://www.it610.com/requestParam;
produces=“application/json;
charset=UTF-8”)
method2(Long id,HttpServletRequest request) //访问路径是**/anno/requestParam?id=1** - @RequestMapping(value="https://www.it610.com/obj;
produces=“application/json;
charset=UTF-8”)
method2(DemoObj obj,HttpServletRequest request) //解释参数到对象 访问路径是 /anno/obj?id=1&name=xx - @RequestMapping(value=https://www.it610.com/article/{"/name1","/name2"}; produces=“application/json; charset=UTF-8”) //不同路径到相同的方法
- @ResponseBody 注解实现将 controller 方法返回对象转化为 json 响应给客户 可以放置在方法上方或返回值类型之前
- 返回值放在response体内
- Dispatcher Servlet会自动扫描注解了此注解的类,并将Web请求映射到注解了@RequestMapping的方法上
- 相同点:两者都可以写在字段和 setter 方法上。两者如果都写在字段上,那么就不需要再写 setter 方法
- 不同点:@Autowired 为 Spring 提供的注解,需要导入包(.annotation.Autowired)
- @Autowired 注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许 null值,可以设置它的 required 属性为 false。
如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用 - @Resource 不是Spring的注解,默认按照 ByName 自动注入,需要导入包 javax.annotation.Resource。 @Resource有两个重要的属性:name 和 type,而 Spring 将@Resource 注解的 name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型。所以,如果使用 name 属性,则使用 byName 的自动注入策略
- 推荐使用@Autowired注解,因为按照类型装配依赖对象不会有任何问题出现。
- 如/news/001,可接受001作为参数,此注解放置在参数前
@requestParam 主要用于在 SpringMVC 后台控制层获取参数,类似一种是 request.getParameter(“name”),他有三个常用参数 defaultValue,required,value
7、@Component 相当于通用的注解,当不知道一些类归到哪个层时使用,但是不建议
8、@RestController (组合注解@Controller和@ResponseBody)
- 注意:1、jackson对象和json做转换时一定需要此空参构造
- 我们在项目中一般会在 springMvc.xml 中通过开启 < mvc:annotation-driven>来实现注解处理器和适配器的开启。
- 使用@EnableWebMvc注解会开启一些默认的配置,如viewResolver或MessageConverter
1、解决post请求乱码:
- 我们可以在web.xml里边配置一个CharacterEncodingFilter过滤器。设置为utf-8
在web.xml文件中filter的位置加上如下内容:
encodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
encodingFilter
*
4、 参数绑定(从请求中接收参数) 重点
1、默认类型:
- 在controller方法中可以有也可以没有,看自己需求随意添加. httpservletRqeust, httpServletResponse, httpSession, Model(ModelMap其实就是Mode的一个子类,一般用的不多)
3、pojo类型:页面上input框的name属性值必须要等于pojo的属性名称
4、vo类型(view object 用于表现层):页面上input框的name属性值必须要等于vo中的属性.属性.属性…
5、自定义转换器converter:
- 作用:由于springMvc无法将string自动转换成date,所以需要自己手动编写类型转换器,需要编写一个类实现Converter接口
- 在springMvc.xml中配置自定义转换器
- 在springMvc.xml中将自定义转换器配置到注解驱动上
1、Rest是一种web服务实现方式,Http接口按照Rest风格设计就是 restful http
- rest风格,一种更为简洁良好的设计风格,可以遵守,也可不必
- rest这个词,是由fielding的博士论文中提出的,翻译成中文是表现层状态转换。表现层指的是资源(图片、歌曲、文本等)的表现层,可以使用url来对其进行访问。
- restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格,是对http协议的诠释。
例如:@RequestMapping(value="https://www.it610.com/viewItems/{id}")
- {xxx}是占位符,在方法中使用@pathVariable可以获取{xxx}中的变量
- restful风格其中的一个思想是:通过http请求对应的post、get、put、delete方法,来完成对应的curd操作。
- 网络上的所有事物都可以被抽象化为资源(根据请求头信息,返回xml或json)
- 每个资源有唯一的资源标识符
- 同一资源具有多种表现形式
- 对资源的各种操作不会改变资源标识符
- 所有的操作都是无状态的
http://example.com/users/
GET: 获取一个资源
POST:创建一个新的资源
PUT:修改一个资源的状态
DELETE:删除一个资源
之前的操作 | RESTful的用法 | 幂等 | 安全 |
---|---|---|---|
http://127.0.0.1/user/query/1 GET查询 | http://127.0.0.1/user/1 GET | 是 | 是 |
http://127.0.0.1/user/save POST增加 | http://127.0.0.1/user POST | 否 | 否 |
http://127.0.0.1/user/update POST修改 | http://127.0.0.1/user PUT | 是 | 否 |
http://127.0.0.1/user/delete GET/POST删 | http://127.0.0.1/user DELETE | 是 | 否 |
- 对于同一REST接口的多次访问,得到的资源状态是相同的
有些接口可天然实现幂等性,比如查询接口:增加、更新、删除都要保证幂等性。那么如何来保证幂等性呢?
- 1、全局唯一ID 在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果存在则表示该方法已经执行;否则,把全局ID,存储到存储系统弄中,比如数据库,redis等
- 2、去重表 //适用于有唯一标识场景 如订单ID
- 3、多版本控制 //适合在更新的场景中,在更新的接口中增加一个版本号,来做幂等
- 4、状态机控制 //适合在有状态机流转的情况 在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等
比如订单的创建为0,付款成功为100。付款失败为99
- url组成 网络协议http/https 服务器地址 接口名称 ?参数列表
- url定义限定 不要使用过大写字母 使用中线-代替下划线_ 参数列表应该被encode过
- springmvc原生态的支持rest风格的架构
- 涉及到的注解:@requestMapping 请求的url
@pathVariable 参数
@responseBody 响应的json数据
- 【深入理解Spring生态|Spring第三讲(SpringMVC 从入门到精通)】1、advanced REST client
是chrome浏览器下的一个插件,通过它能够发送http,https,websocket请求,这样就不必通过写一个jsp页面来实现请求,节省时间
- 2、HttpClient工具类
模拟了浏览器的行为,如果我们需要在后端向某一地址提交数据获取结果,就可以使用HttpClient
- 3、我们公司常使用的工具
restlet插件 postman插件 用于模拟浏览器的请求
@responsebody(method=requestMethod.put)
public responseBodyEntity updateUser(User user){
try{
integer count = this.userservice.updateUser(user);
if(count.intValue() == 1){
//响应 204
return responseEntity.status(HttpStatus.NO_CONTENT).build();
}catch(exception e){e.printStackTrace}
//新增失败500
return responseEntity.status(HttpStatus.INTERNAL_SERVERR_ERROR).build();
}
}
默认情况下,put请求时无法提交表单数据的,需要在web.xml中添加过滤器解决
httpmethodfilter>
org.springframework.web.filter.httpputformContentFilter>
删除资源时,将post请求转化为DELETE或者是put要用-method指定真正的请求方法
6、 SpringMVC或Struts处理请求流程区别?
SpringMVC通过ModelAndView,把结果集拿到以后,从配置文件里获取对应的bean,类反射
区别:
- springMvc是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,参数的传递是直接注入到方法中的,是该方法独有的
- struts2是类级别的拦截,一个类对应一个request上下文,struts是在接受参数的时候,可以用属性来接受参数,这就说明参数是让多个方法共享的,这也就无法用注解或其他方式标识其所属方法了。
- 1、SpringMvc的入口是一个servlet即前端控制器,而Struts2入口是一个filter过滤器;
- 2、SpringMvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议多例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例;
- 3、Struts2 采用值栈存储请求和响应的数据,通过OGNL存取数据,Springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象, 最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用JSTL表达式
7、SpringMVC全局异常处理/整个系统只有一个
使用场景:
- 在做前后端分离的项目时,后端通常都会拆分成多个独立的微服务,这时候就会涉及每个服务返回给前端的数据格式问题了。通过SpringMVC实现一个比较常用的数据格式,统一所有服务的返回值格式。
- 第一部分: 请求处理是否成功,
- 第二部分:服务处理结果编码,
- 第三部分:编码对应的文本信息,
- 第四部分:返回值
- 如果我们在controller中通过try catch来处理异常的话,会出现一个问题就是每个函数里都加一个Try catch,代码会变的很
乱。
- 1)需要实现一个接口 HandlerExceptionResolver TODO 补充demo
- 2)需要在springMvc中配置。
处理逻辑:捕获整个系统中发生的异常。 - 1、异常写入日志文件 ?
- 2、及时通知开发人员。发邮件、短信。?
- 3、展示一个错误页面,例如:您的网络故障,请重试。
- @ExceptionHandler(value = https://www.it610.com/article/Exception.class)
使用 RestControllerAdvice 来捕获全局异常Demo:
public class CallResultMsg {
private boolean result;
private int code;
private String message;
private T data;
}public enum CodeAndMsg {
SUCCESS(1000, "SUCCESS"),
METHODFAIL(2000, "ENCOUNTER AN ERROR WHEN EXECUTE METHOD"),
UNKNOWEXCEPTION(3000, "THIS IS AN UNKNOW EXCEPTION");
private int code;
private String msg;
CodeAndMsg(int code, String msg){
this.code = code;
this.msg = msg;
}
}@RestControllerAdvice(basePackages = {
"cn.gov.zcy.service.item.web.controller",
"cn.gov.zcy.service.warehouse.web.controller"})
public class ControllerErrorHandler {@ResponseStatus(HttpStatus.OK)
public CallResultMsg sendSuccessResponse(){
return new CallResultMsg(true, CodeAndMsg.SUCCESS, null);
}@ResponseStatus(HttpStatus.OK)
public CallResultMsg sendSuccessResponse(T data) {
return new CallResultMsg(true, CodeAndMsg.SUCCESS, data);
}@ExceptionHandler(UserDefinedException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public CallResultMsg sendErrorResponse_UserDefined(Exception exception){
return new CallResultMsg(false, ((UserDefinedException)exception).getException(), null);
}@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public CallResultMsg sendErrorResponse_System(Exception exception){
if (exception instanceof UserDefinedException) {
return this.sendErrorResponse_UserDefined(exception);
}return new CallResultMsg(false, CodeAndMsg.UNKNOWEXCEPTION, null);
}
}
通过上面的一波操作,我们的controller中就不需要再去写大量的try-catch了,RestControllerAdvice会自动帮助catch,并匹配相应的ExceptionHandler,然后重新封装异常信息,返回值,统一格式返回给前端。
8、SpringMvc 拦截器用过吗?什么场景会用到,过滤器,拦截器,监听器有什么区别?
1、拦截器interceptor:
- 是指通过统一拦截,从浏览器发往服务器的请求来完成功能的增强。
- 在面向切面编程中应用
- 使用场景:解决请求的共性问题(乱码问题,权限验证问题)
- 实现了javax.servlet.Filter接口
- 主要的用途是过滤字符编码、做一些业务逻辑判断等
- 原理:在web.xml文件配置好要拦截的客户端请求,它都会帮你拦截到请求,此时你就可以对请求或响应(Request、Response)统一设置编码,简化操作
- 生命周期:随web应用启动而启动的,只初始化一次,以后就可以拦截相关请求,只有当web应用停止或重新部署的时候才销毁
- 实现了javax.servlet.ServletContextListener 接口
- 主要作用是:做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等
- 生命周期:它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁
- 1.拦截器是基于java反射机制的,而过滤器是基于函数回调的;
- 2.过滤器依赖于servlet容器,而拦截器不依赖于servlet容器;
- 3.拦截器只能对Action请求起作用,而过滤器则可以对几乎所有请求起作用;
- 4.拦截器可以访问Action上下文、值栈里的对象,而过滤器不能;
- 5.在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次
我们定义一个配置类MyMvcConfig,继承WebMVCConfigurerAdapter,并在此类中使用@EnableWebMvc注解,开启对springMVC的配置支持
1、静态资源映射 在配置里重写addResourceHandlers方法来实现
@override
public void addResourceHandlers(ResourceHandlerRegistry registry){
registry.addResourceHandler("/asserts/**").addResourceLocation("classpath:/asserts/");
//前者是对外暴露的访问路径后者是文件放置的目录
}
2、拦截器设置Interceptor
- 实现对每一个请求处理前后进行相关的业务处理,类似servlet中的Filter
- 先让普通的Bean实现HandlerInterceptor接口或是继承HandlerInterceptorAdapter类来实现自定义拦截器,然后重写WebMvcConfigurerAdapter(配置类)的addInterceptors方法来注册自定义的拦截器
- 注解了@ControllerAdvice的类的方法可使用@ExceptionHandler @InitBinder @ModelAttribute注解到方法上
@ExceptionHandler(value=https://www.it610.com/article/xx.class) 用于全局处理控制器里的异常,更人性化的将异常输出给用户
例如:
@ExceptionHandler(value=https://www.it610.com/article/Exception.class)//value为拦截所有的异常
public ModelAndView exception(Exception exception, webRequestrequest){
ModelAndView modelAndView =new ModelAndView("error");
modelAndView.addObject("errorMessage",exception.getMessage());
return modelAndView;
}
- @InitBinder 用于设置WebDataBinder,自动绑定前台请求参数到Model中
例如:
@InitBinder
public void initBinder(WebDataBinder webDataBinder){
webDataBinder.setDisallowedFields("id");
}
- @ModelAttribute 让全局的@RequestMapping都能拿在此处设置的键值对
例如:
@ModelAttribute
public void addAttributes(Model model){
model.addAttributes("msg","额外信息");
}
(2)那么,在使用控制器时:
@Controller
public class AdviceController{
@RequestMapping("/advice")
public String getSomething(@ModelAttribute("msg") String msg,DemoObj obj){
throw new IllegalArgumentException("参数有误/"+"来自@ModelAttribute:"+msg);
}
}
(3)异常展示页面 在src/main/resources/views下,新建error.jsp
${errorMessage} //运行时发现 id被过滤掉了,且获得了@ModelAttribute的msg信息
4、其他配置
- 快捷的ViewController //配置页面转向 registry.addViewController("/index").setViewName("/index"); //前面是路径,后面是页面
- 路径匹配参数设置 使用configurePathMatch方法不可忽视"."后面的参数 Configurer。setUseSuffixPatternMatch(false);
- (1)添加文件上传依赖 commons-fileupload/commons-io
- (2)上传页面,在src/main/resources/views下新建upload.jsp
- (3)添加转向到upload页面的ViewController
public void addViewController(ViewControllerRegistry registry){
registry.addViewController("/index").setViewName("/index");
registry.addViewController("/toUpload").setViewName("/upload");
}
- (4)MultipartResolver配置(配置类中)
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1000000);
return multipartResolver;
}
- (5)控制器
@Controller
public class UploadController{
@RequestMapping(value="https://www.it610.com/upload",method="RequestMethod.POST")
public @ResponseBody String upload(MultipartFile file){
try{
FileUtils.writeByteArrayToFile(new File("e:/upload/"+file.getOriginalFileName()),file.getBytes());
//快速写文件到磁盘
return "ok";
}catch(IOException e){
e.prrintStackTrace();
return "wrong";
}
}
}
6、自定义HttpMessageConvertor 用于处理request和response里的数据
10、@DateTimeFormat 和 @JsonFormat 注解
定义一个pojo,它有一个 java.util.Date 类型的属性 date。
import java.util.Date;
public class DateVo {
private Date date;
public void setDate(Date date){
this.date = date;
}
public Date getDate(){
return date;
}
}
定义一个Controller
@RestController
@RequestMapping("/date/")
public class DateController {
@RequestMapping("test")
public DateVo getDate(DateVo vo){
System.out.println("date1:"+vo.getDate());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = sdf.format(vo.getDate());
System.out.println("date2:"+date);
DateVo vo2 = new DateVo();
vo2.setDate(new Date());
return vo2;
}
}
访问 /date/test ,并传入参数:2018-08-02 22:05:55
发现并不能访问成功,会抛出异常
文章图片
因为传入的参数是 String 类型的,而用来接收参数的 DateVo 的 date 属性是 java.util.Date 类型的,类型无法转换。
- 入参格式化
这时,就可以使用 Spring 的@DateTimeFormat
注解格式化参数,来解决上述问题
public class DateVo {
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
private Date date;
public void setDate(Date date){
this.date = date;
}
public Date getDate(){
return date;
}
}
再像上面一样访问 /date/test ,并传入参数:2018-08-02 22:05:55,将在控制台上打印:
- date1:Thu Aug 02 22:05:55 CST 2018
- date2:2018-08-02 22:05:55
因为 @DateTimeFormat 注解的 pattern 属性值指定的日期时间格式并不是将要转换成的日期格式,这个指定的格式是和传入的参数对应的,假如注解为:
@DateTimeFormat(pattern=“yyyy/MM/dd HH:mm:ss”)
则传入的参数应该是这样的:2018/08/02 22:05:55,否则会抛出异常。
- 出参格式化
在上述示例中,调用接口的返回结果为:
- “date”: “2018-08-01T14:25:31.296+0000”
这个格式并不是我们想要的,那么如何将其进行格式化?这时就需要用到 jackson 的@JsonFormat 注解。
public class DateVo {
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
@JsonFormat(
pattern = "yyyy-MM-dd HH:mm:ss",
timezone = "GMT+8"
)
private Date date;
public void setDate(Date date){
this.date = date;
}
public Date getDate(){
return date;
}
}
这样,就能返回正确的结果了。
推荐阅读
- Spring-data-JPA详细介绍,增删改查实现
- 使用Salesforce AppExchange在企业中快速获胜
- Magento简介(在顶级电子商务生态系统中导航)
- 吸引,管理和留住软件开发人员的提示
- Trello vs. Jira(从开发人员的角度进行比较)
- 缩小差距(DevOps通信的重要性)
- 面试官(如何保证用户模块的数据安全(说说你的解决方案))
- Java/Spring/Dubbo三种SPI机制,到底谁更好()
- CPT210设计分析