本文主要是介绍【java学习】Spring MVC(Model View Controller)、ApplicationRunner,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1,概念
SpringMVC:
Spring推出的基于Servlet标准的MVC框架实现
1)Spring MVC特性
- Spring MVC提供了一种绑定机制(请求参数名称与Java类的属性相匹配即可),通过该机制可以从用户请求中提取数据,然后将数据转换为预定义的数据格式,最后映射到一个模型类,从而创建一个对象。
- Spring MVC还是非侵入式的,因为业务逻辑代码与框架本身是分离的。
2,Spring MVC Annotation
注解 | 场景说明 | 备注 |
---|---|---|
@EnableWebMvc | 在配置类中开启Web MVC的配置支持。 | |
@Controller | ||
@RequestMapping | 用于映射web请求,包括访问路径和参数。 | |
@ResponseBody | 支持将返回值放到response内,而不是一个页面,通常用户返回json数据。 | |
@RequestBody | 允许request的参数在request体中,而不是在直接连接的地址后面。(放在参数前) | |
@PathVariable | 用于接收路径参数 | |
@RestController | 组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。 | Spring4之后加入的注解 |
@ControllerAdvice | 全局异常处理;全局数据绑定;全局数据预处理 | |
@ExceptionHandler | 用于全局处理控制器里的异常。 | |
@InitBinder | 用来设置WebDataBinder,WebDataBinder用来自动绑定前台请求参数到Model中。 | |
@ModelAttribute | 用于定义Controller方法执行之前,对数据模型的操作。 | |
@ResponseStatus | 在接口使用@ResponseStatus,在处理方法正确执行的前提下,后台返回HTTP响应的状态码为@ResponseStatus指定的状态码,但是浏览器依然可以正常渲染视图(在不使用@ResponseStatus的reson属性情况下)。加在@ExceptionHandler下方,该方法在捕获异常后,后台返回HTTP响应的状态码为@ResponseStatus指定的状态码,但是浏览器依然可以正常渲染视图(在不使用@ResponseStatus的reson属性情况下)。 | @ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = “这是一个异常捕获”) |
1)@Controller(控制组件)
用于标记在一个类上,使用它标记的类就是一个springmcv Controller对象,分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping注解。@Controller只是定义了一个控制器类,而使用了@RequestMapping注解的方法才是真正处理请求的处理器。
1>与@RestControlle区别
@Controller:它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model返回给对应的View进行展示,即该注解返回的是一个页面。
@RestControlle:返回的是json数据。
2>跨域:@CrossOrigin
跨域:
请求url的协议、域名、端口三者之间任意一个与当前页面url不同。
浏览器支持的是同源策略,不允许跨域是浏览器基本的安全策略。
@CrossOrigin
中的2个参数:
- origins : 允许可访问的域列表
- maxAge:准备响应前的缓存持续的最大时间(以秒为单位)。
如果您正在使用Spring Security,请确保在Spring安全级别启用CORS,并允许它利用Spring MVC级别定义的配置。
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and()...}
}
//为整个controller启用@CrossOrigin
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {@GetMapping("/{id}")public Account retrieve(@PathVariable Long id) {// ...}
}@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {//为单个方法启用跨域@CrossOrigin(origins = "http://domain2.com")@GetMapping("/{id}")public Account retrieve(@PathVariable Long id) {// ...}@DeleteMapping("/{id}")public void remove(@PathVariable Long id) {// ...}
}
2)@RequestMapping
用于标记在一个方法或类上,用来处理请求地址映射的注解,用于类上,表示类中所有响应请求处理的方法都是以该地址作为父路径,返回值会通过视图解析器解析为实际的物理视图,然后做转发操作。
属性 | 说明 | 备注 |
---|---|---|
value | 指定请求的实际地址 | 多个url映射:@GetMapping(value = {"/check-list/{type}", "/check-map/{type}"}) |
method | 指定请求的method类型 | |
consumes | 指定处理请求的内容提交类型(Content-Type),例如application/josn,text/html | |
produces | 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回 | |
param | 指定request中必须包含的参数值 | |
headers | 指定request中必须包含某些指定的header值,才能让改方法处理请求 |
spring4.3引入:@GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping
,@RequestMapping是他们的父类注解。
org.springframework.web.bind.annotation
@GetMapping
相当于@RequestMapping(method = RequestMethod.GET)
@PostMapping
相当于@RequestMapping(method = RequestMethod.POST)
同时又引入了三个参数注解:
1>@PathVaribale
对应http访问的路径参数。
@GetMapping(value = "/user/{id}")
public ResponseMessage getUser(@PathVariable Long id){
}
2>@RequestParam
适合参数较少,参数固定的情况。
对应http访问的query参数。
属性 | 说明 |
---|---|
value | 请求参数名(必须配置) |
required | 是否必需,默认为 true,即 请求中必须包含该参数,如果没有包含,将会抛出异常(可选配置) |
defaultValue | 默认值,如果设置了该值,required 将自动设为 false,无论你是否配置了required,配置了什么值,都是 false(可选配置) |
3>@RequestBody
org.springframework.web.bind.annotation
适用于复杂的数据结构,比如用xml或者json定义的复杂对象。
对应的是http request payload,数据在http请求的body上,也就是在HttpServletRequest.getInputStream里面。
@PostMapping(value = "")
public ResponseMessage modifyUser(@RequestBody User user){
}
4>@RequestHeader
org.springframework.web.bind.annotation
@RequestMapping("/getSingleHeader")
public Map<String, Object> getSingleHeader(@RequestHeader("user-id") String userId){Map<String, Object> result = new HashMap<>();result.put("code", 0);result.put("msg", "success");result.put("userId", userId);return result;
}//一次性获取所有请求头--MultiValueMap
@RequestMapping("/listHeaders")
public Map<String, Object> listHeaders(@RequestHeader MultiValueMap<String, String> headers) {Map<String, Object> result = new HashMap<>();headers.forEach((key, value) -> {// 日志中输出所有请求头System.out.println(String.format("Header '%s' = %s", key, value));});result.put("code", 0);result.put("msg", "success");result.put("headers", headers);return result;
}//一次性获取所有请求头--HttpHeaders (底层就是MultiValueMap实现的)
@RequestMapping("/getAllHttpHeaders")
public Map<String, Object> getAllHttpHeaders(@RequestHeader HttpHeaders headers) {headers.forEach((key, value) -> {// 日志中输出所有请求头System.out.println(String.format("getAllHttpHeaders '%s' = %s", key, value));});Map<String, Object> result = new HashMap<>();result.put("code", 0);result.put("msg", "success");result.put("headers", headers);return result;
}
5>@CookieValue
作用:用来获取Cookie中的值;
@RequestMapping("/testCookieValue")public String testCookieValue(@CookieValue("JSESSIONID") String sessionId) {System.out.println("JSESSIONID = " + sessionId);return "success";}
6>@SessionAttributes
将值放到session作用域中,写在class上面。
@SessionAttributes(value = {"user"}, types = {String.class})
3)@ModelAttribute
- 通常被用来填充一些公共需要的属性或数据;
比如权限的验证(也可以使用Interceptor)等。 - 先执行@ModelAttribute方法,再执行Controller方法。
1>实现
- 注释方法并定义模型数据
在没有@RequestMapping标注的方法上建立模型数据,抽象一个公共BaseController。
abstract public class BaseController{@ModelAttribute("account")public User newUser(@RequestHeader HttpHeaders header){User user = new User();user.setName("myUser");user.setId(1);return user;}
}
- 注释参数引用模型数据
@Controller
public class MyController extends BaseController { @RequestMapping("user")@ResponseBodypublic void getUser(@ModelAttribute("account")User user){System.out.println(user.toString());}
}
4)@Transactional
5)@ControllerAdvice及@ExceptionHandler
@ControllerAdvice就是@Controller 的增强版。@ControllerAdvice主要用来处理全局数据,一般搭配@ExceptionHandler、@ModelAttribute以及@InitBinder使用。
6)@InitBinder
用于在@Controller中标注于方法上,表示为当前控制器注册一个属性编辑器,只对当前的Controller有效。@InitBinder标注的方法必须有一个参数WebDataBinder。所谓的属性编辑器可以理解就是帮助我们完成参数绑定。
//例如当请求是/test?name=%20zero%20&date=2018-05-22时,会把zero绑定到name,再把时间串格式化为Date类型,再绑定到date。@ResponseBody@RequestMapping(value = "/test")public String test(@RequestParam String name,@RequestParam Date date) throws Exception {System.out.println(name);System.out.println(date);return name;}@InitBinderpublic void initBinder(WebDataBinder binder){//把String类型的参数先trim再绑定binder.registerCustomEditor(String.class,new StringTrimmerEditor(true));//对于Date类型的参数会先格式化在绑定binder.registerCustomEditor(Date.class,new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));}
这里的@InitBinder方法只对当前Controller生效,要想全局生效,可以使用@ControllerAdvice。通过@ControllerAdvice可以将对于控制器的全局配置放置在同一个位置,注解了@ControllerAdvice的类的方法可以使用@ExceptionHandler,@InitBinder,@ModelAttribute注解到方法上,这对所有注解了@RequestMapping的控制器内的方法有效。
@ControllerAdvice
public class GlobalControllerAdvice {@InitBinderpublic void initBinder(WebDataBinder binder) {binder.registerCustomEditor(String.class,new StringTrimmerEditor(true));binder.registerCustomEditor(Date.class,new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));}
}
3,Dispatcher Servlet(前端控制器)
Dispatcher Servlet是Spring web的核心元素,跟之前的Servlet作用相似,都是用来接收用户请求,然后去后台匹配合适的handler。
Tomcat:
The Apache Tomcat software is an open source implementation of the Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket technologies.
Servlet:
1.Servlet不是框架,它是java库里面的一个类,Servlet是服务器端运行的一个程序。
2.当web容器启动的时候并且执行的时候,Servlet类就会被初始化。
3.用户通过浏览器输入url时,请求到达Servlet来接收并且根据servlet配置去处理。
通常项目中会用到不同的web容器,我这里用到是比较常见的Tomcat。在eclipse里面创建一个java web项目,会有一个WEB-INF的文件夹,为了不轻易被外界访问到,这个文件夹底下的文件都是受保护的。文件夹中包括了一个很重要的配置文件,web.xml,我们要实现的不同Servlet也要在这里配置才能使用。
1)主要工作流
Controller接受了一个Request,分发给了一个RequestHandler,并返回一个Response对象。
①前端请求到达DispatcherServlet
②DispatcherServlet请求HandlerMappering(处理器映射) 查找Handler
DispatcherServlet 对请求URL进行解析,得到请求资源标识符(URI),然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括一个Handler处理器对象、多个HandlerInterceptor拦截器对象),最后以HandlerExecutionChain对象的形式返回。
Dispatcher Servlet扫描被@Controller注解的类,从而将web请求映射到被@RequestMapping注解的方法上。
@PathVariable注解相关参数,将一个方法参数绑定到一个url模板。
//@PathVariable用法
@RequestMapping(value = "/test/{id}",method = RequestMethod.DELETE)
public Result test(@PathVariable("id")String id) //@RequestParam用法,注意这里请求后面没有添加参数
@RequestMapping(value = "/test",method = RequestMethod.POST)
public Result test(@RequestParam(value="id",required=false,defaultValue="0")String id)
注意上面@RequestParam用法当中的参数。
@ExceptionHandler 可以定义方法来处理控制器类中发生的异常。
③DispatcherServlet 执行handler
根据获得的Handler,选择一个合适的HandlerAdapter。提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
④Handler执行完成后,返回ModelAndView对象
根据返回的ModelAndView,选择一个适合的ViewResolver返回给DispatcherServlet;
ViewResolver(视图解析器) 结合Model和View,来渲染视图,最后将渲染结果返回给客户端。
⑦DispatcherServlet将模型传递给视图,并在浏览器中展示视图
4,过滤器(Filter)
1)概念
过滤器是JavaWeb的三大组件之一,是实现Filter接口的Java类。
过滤器是实现对请求资源(jsp、servlet、html)的过滤功能,是一个运行在服务器的程序,优先于请求资源(jsp、servlet、html)之前执行。
当浏览器发送请求给服务器的时候,先执⾏过滤器,然后才访问Web的资源。服务器响应Response,从Web资源抵达浏览器之前,也会途径过滤器。
在很多Web开发中,都会用到过滤器(Filter),如参数过滤、防止SQL注入、防止页面攻击、过滤敏感字符、解决网站乱码、空参数矫正、Token验证、Session验证、点击率统计等。
2)场景
针对所有接口做全局过滤。
3)方法
Filter抽象类包含三个方法:
1> init()
该方法在容器启动初始化过滤器时被调用,它在Filter的整个生命周期只会被调用一次,这个方法必须执行成功,否则过滤器会不起作用。
2>doFilter()
容器中的每一次请求都会调用该方法,FilterChain用来调用下一个过滤器Filter。
@Override
public void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException{//请求(request)处理逻辑//请求(request)封装逻辑//chain重新写回request和response
}
其中,参数FilterChain是一个接口,里面又定义了doFilter()方法,这是因为在Java中使⽤了链式结构。把所有的过滤器都放在FilterChain⾥边,如果符合条件,就执⾏下⼀个过滤器(如果没有过滤器了,就执⾏⽬标资源)。
3>destroy()
容器销毁时被调用。一般在方法中销毁或关闭资源,也只会被调用一次。
4>demo
@Slf4j
@Order(1) //如果有多个Filter,则序号越小,越早被执行
//@Component//无需添加此注解,在启动类添加@ServletComponentScan注解后,会自动将带有@WebFilter的注解进行注入!
//这里的urlPatterns为接口里的路径过滤条件
@WebFilter(filterName = "timeFilter", urlPatterns = "/api/filter/*")
public class TimeFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info("初始化过滤器:{}", filterConfig.getFilterName());}@Overridepublic void destroy() {log.info("销毁过滤器");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {log.info("开始执行");long startTime = System.currentTimeMillis();filterChain.doFilter(servletRequest, servletResponse);long endTime = System.currentTimeMillis();log.info("请求:{},耗时:{}ms", getUrlFrom(servletRequest), (endTime - startTime));log.info("结束执行");}private String getUrlFrom(ServletRequest servletRequest) {if (servletRequest instanceof HttpServletRequest) {return ((HttpServletRequest) servletRequest).getRequestURL().toString();}return "";}
}
在启动类添加一个注解,找到定义的拦截器:
@ServletComponentScan(basePackages = "com.luo.filter")
测试接口:
@RestController
@Slf4j
@RequestMapping("/api/filter")
public class FilterUserController {@GetMapping("/getUserList")public List<String> getUser() {log.info("开始业务逻辑处理。");List<String> list = new ArrayList<>();list.add("张三");list.add("李四");list.add("王五");log.info("业务逻辑处理结束。");return list;}
}
5,拦截器(Interceptor)
1)概念
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。
拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor,一个请求也可以触发多个拦截器,而每个拦截器的调用会依据它的声明顺序依次执行。
2)场景
3)api
HandlerInterceptor 抽象类;包含三个方法:
1>preHandle()
这个方法将在请求处理之前进行调用。「注意」:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
2>postHandle()
只有在 preHandle() 方法返回值为true 时才会执行。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。「有意思的是」:postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
3>afterCompletion()
只有在 preHandle() 方法返回值为true 时才会执行,在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。
4)demo
SpringBoot实现一个登录拦截器:用户在访问首页接口,先判断一下session,如果session中有user的信息,说明用户已经登录过了,能正常访问首页接口,否则跳转到登录页面,让用户进行登录。
拦截器类:实现handlerInterceptor接口。
@Component
@Slf4j
public class UserInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//业务拦截相关规则//从session中获取用户的信息InterceptorUserEntity user = (InterceptorUserEntity) request.getSession().getAttribute("user");//判断用户是否登录if (null == user) {response.sendRedirect(request.getContextPath() + "/api/interceptor/login");return false;}//需要返回true,否则请求不会被控制器处理return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后),如果异常发生,则该方法不会被调用");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("在整个请求结束之后被调用,也就是在DispatcherServlet渲染了对应的视图之后执行(主要是用于进行资源清理工作)");}
}
使用@Configuration注解写一个拦截器的配置文件:
//.addPathPatterns表示作用范围。(只在这个interceptor下的所有接口进行拦截)
//.excludePathPatterns表示放行。这里把登录页面和已登录完成(setSession)放行。
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Autowiredprivate UserInterceptor userInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(userInterceptor).addPathPatterns("/api/interceptor/**").excludePathPatterns("/**/login", "/**/setSession");}
}
测试代码:
@Data
public class InterceptorUserEntity {private Integer id;private String name;
}@RestController
@Slf4j
@RequestMapping("/api/interceptor")
public class InterceptorUserController {@GetMapping("/setSession")@ResponseBodypublic Object setSession(HttpServletRequest request) {//将用户信息存放到session中InterceptorUserEntity user = new InterceptorUserEntity();user.setId(001);user.setName("张三");request.getSession().setAttribute("user", user);return "已进行登录!";}/*** 用户登录后跳转到首页** @return*/@GetMapping("/index")public Object index() {return "这里是首页!";}/*** 登录页面** @return*/@GetMapping("/login")public Object login() {return "请进行登录!";}
}
6,过滤器与拦截器的区别
1)相同点
过滤器与拦截器都体现了AOP的编程思想,都可以实现例如日志、登录鉴权等功能。
2)不同点
①:拦截器是基于java的反射机制(动态代理)的实现,而过滤器是基于函数的回调。
②:拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。
③:拦截器只对Controller请求起作用,而过滤器则可以对几乎所有的请求起作用。
④:拦截器可以访问Controller上下文、值、栈里面的对象,而过滤器不可以。
⑤:在spring容器的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
⑥:拦截器可以获取IOC容器中的各个bean,而过滤器不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
3)触发机制不同
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。
7,ApplicationRunner
@FunctionalInterface
public interface ApplicationRunner {void run(ApplicationArguments args) throws Exception;
}
- spring程序启动后,run方法会被自动调用。
- 执行顺序:
- 场景:初始化操作。
实例化Kafka客户端,异步加载缓存。
1)使用
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;@Component
public class StartRegisterSchedule implements ApplicationRunner {@AutowiredRestTemplate restTemplate;@Overridepublic void run(ApplicationArguments args) {// 获取命令行参数String[] sourceArgs = args.getSourceArgs();List<String> nonOptionArgs = args.getNonOptionArgs();Set<String> optionNames = args.getOptionNames();}
}
2)问题
- ApplicationRunner没有调用?
可能其他启动任务非异步调用被阻塞了。
8,幂等性
1>概念
某个操作,任意多次执行所产生的影响均与一次执行的影响相同。
举例:多次支付同一笔订单。
2>场景及处理方式
- 前端未做限制,导致用户重复提交
- 使用浏览器后退,或者按F5刷新,或者使用历史记录,重复提交表单
- 网络波动,引起重复请求
- 超时重试,引起接口重复调用
- 定时任务设置不合理,导致数据重复处理
- 使用消息队列时,消息重复消费
4>解决方案
- 前端重定向
页面重定向(PRG),当用户进行表单提交时,会重定向到另外一个提交成功页面,而不是停留在原先的表单页面。这样就避免了用户刷新导致重复提交。同时防止了通过浏览器按钮前进/后退导致表单重复提交。 - 后端一锁二判三更新
约定一个唯一ID作为幂等号。
如主键、操作流水、状态机等。
另外,在返回参数中标记是因为幂等成功(responseCode=DUPLICATED)。
1)加锁
对幂等号加锁。
用redis分布式锁:非阻塞高效互斥锁。
2)状态校验
基于状态机、流水表、唯一索引等等进行重复操作的判断。
3)更新时将数据进行持久化。
数据库要定义好唯一约束,控制脏数据入库。
这篇关于【java学习】Spring MVC(Model View Controller)、ApplicationRunner的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!