spring web 知识点过一遍
初始化spring mvc的工作
1.初始化Spring MVC的DispatcherServlet;
2.搭建转码过滤器,保证客户端请求进行正确地转码;
3.搭建视图解析器(view resolver),告诉Spring去哪里查找视图,以及它们是使用哪种方言编写的(JSP、Thymeleaf模板等);
4.配置静态资源的位置(CSS、JS);
5.配置所支持的地域以及资源bundle;
6.配置multipart解析器,保证文件上传能够正常工作;
7.将Tomcat或Jetty包含进来,从而能够在Web服务器上运行我们的应用;
8.建立错误页面(如404)。
springmvc 工作过程
Spring web mvc模式是围绕 DispatcherServlet 设计的,工作过程
1.用户通过浏览器向服务器发送请求,请求会被Spring MVC的前端控制器DispatcherServlet所拦截。
2.DispatcherServlet拦截到请求后,会调用HandlerMapping处理器映射器。
3.处理器映射器根据请求URL找到具体的处理器,生成处理器对象及处理器拦截器(如果有就生成)一并返回给DispatcherServlet。
4.DispatcherServlet会通过返回信息选择合适的HandlerAdapter(处理器适配器)。
5.HandlerAdapter会调用并执行Handler(处理器),这里的处理器就是程序中编写的Controller类,也被称为后端控制器。
6.Controller执行完成后,会返回一个ModelAndView对象,该对象中包含视图名或包含模型与视图名。
7.HandlerAdapter将ModelAndView对象返回给DispatcherServlet。
8.DispatcherServlet会根据ModelAndView对象选择一个合适的ViewResolver(视图解析器)。
9.ViewResolver解析后,会向DispatcherServlet中返回具体的View(视图)。
10.DispatcherServlet对View进行渲染(即将模型数据填充至视图中)。
11.视图渲染结果会返回给客户端浏览器显示
静态资源
我们的静态资源需要放在类路径中,并且要位于以下4个目录中的任意一个之中,
“/META-INF/resources/”
“/resources/”
“/static/”
“/public/”
WebJars
是JAR包格式的客户端JavaScript库,可以通过Maven中央仓库来获取。它们包含了Maven项目文件,这个文件允许定义传递性依赖,能够用于所有基于JVM的应用之中
嵌入式Servlet容器(Tomcat)的配置
对Servlet容器(Tomcat)的所有配置都会EmbeddedServletContainerAutoConfiguration类进行。
可以将Spring Boot与Tomcat、tc-server、Jetty或者Undertow结合使用。服务器可以很容易地进行替换,只需将spring-boot-starter-tomcat JAR依赖移除掉,并将其替换为Jetty或Undertow对应的依赖即可
spring mvc 如何使用
// 1. 安装依赖<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.2.5.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.2.5.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.5.RELEASE</version></dependency>// 2 web.xml 中配置 这里的ervelet的class不是具体的实现类了,而是DispatcherServlet这个类,会去找当前目录下的 servletname + servlet.xml 配置<servlet><servlet-name>HelloWeb</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>HelloWeb</servlet-name><url-pattern>/hello</url-pattern></servlet-mapping>// 3 HelloWeb-servlet.xml 配置// 开启组件扫描<context:component-scan base-package="web" />// 视图如何渲染<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/" /><property name="suffix" value=".jsp" /></bean>// 4 定义controller
@Controller
@RequestMapping("/hello")
public class HelloController{@RequestMapping(method = RequestMethod.GET)public String printHello(ModelMap model) {model.addAttribute("message", "Hello Spring MVC Framework!");return "hello";}
}// 5 jsp 页面
<%@ page contentType="text/html; charset=UTF-8" %>
<html>
<head><title>Helldddo World</title>
</head>
<body>
<h2>${message}</h2>
</body>
</html>
spring 数据绑定的过程
1 . Spring MVC将ServletRequest对象传递给DataBinder。
2 . 将处理方法的入参对象传递给DataBinder。
3 . DataBinder调用ConversionService组件进行数据类型转换、数据格式化等工作,并将ServletRequest对象中的消息填充到参数对象中。
4 . 调用Validator组件对已经绑定了请求消息数据的参数对象进行数据合法性校验。
5 . 校验完成后会生成数据绑定结果BindingResult对象,Spring MVC会将BindingResult对象中的内容赋给处理方法的相应参数
spring 数据绑定1 - 简单类型
@GetMapping("/test1")public String handleTest1(Integer id){System.out.println(id);return "test";}
spring 数据绑定2 - 绑定数组
// 接收数组 访问 /test3?ids=1,2(/test3?ids=1&ids=2也可以)@GetMapping("/test3")public String test3(int[] ids){System.out.println(ids[0]);System.out.println(ids[1]);return name;}
spring 数据绑定3 - 间接绑定
// 间接绑定 Spring MVC提供了@RequestParam注解来进行间接数据绑定。
// test2?vid=111@GetMapping("/test2")public String handleTest2(// @RequestParam( value = "vid") Integer id 可省略@RequestParam("vid") Integer id,@RequestParam(value = "size", defaultValue = "10") Integer size){System.out.println(id);return "test";}
spring 数据绑定4 - Restfully
@GetMapping(value = "/test6/{id}")public String handleTest6(@PathVariable String id){System.out.println(id);return id;}
spring 数据绑定5 - 使用servlet
@GetMapping("/test")public ModelAndView handleTest(HttpServletRequest req){// 获取一个参数的值System.out.println(req.getParameter("id"));// 所有参数列表 返回的是一个枚举Enumeration list1 = req.getParameterNames();while(list1.hasMoreElements()) {StringBuilder paramName = new StringBuilder((String) list1.nextElement());String[] paramValue = req.getParameterValues(paramName.toString());paramName.append("=[");for (String j : paramValue) {paramName.append(j);paramName.append(',');}paramName.append("]");System.out.println(paramName.toString());}ModelAndView m = new ModelAndView();m.addObject("name1", "huahua");m.setViewName("test");return m;}
spring 数据绑定6 - 前后交互ajax
// 前后端交互可以直接绑定一个pojo @RequestBody得到json参数var d = {id : 1, userName : "huahua", passWord: '123' , realName: 'zyh'}$.post({url: '/test4',contentType: "application/json",data: JSON.stringify(d),success: function (e) {console.log(e);}})@PostMapping("/test4")public String test4(@RequestBody User user){System.out.println(user);return "ok";}
@ModelAttribute 注解使用
// 1. 这里的 @ModelAttribute 在请求之前 对model做一些前置处理@ModelAttributepublic void setData(@RequestParam(defaultValue = "default", required = false) String type, Model model) {System.out.println(type);model.addAttribute("types", type);}// 2. 这里的 @ModelAttribute 在请求之前 对model加一个名字是value1,值是请求参数val的值@ModelAttribute("value1")public String setData1(@RequestParam(defaultValue = "default1", required = false) String val, Model model) {return val;}// 3. 这里的 @ModelAttribute 不指定返回属性名字,就用返回类的名字转小写,作为属性名字@ModelAttributepublic Student1 setData2() {Student1 s = new Student1();s.setName("student-name-huahua");return s;}// 4 这种返回的不是视图名字,而是model的值,视图名字是student1,model.setAttr("name3", "values is names")@RequestMapping(value = "/student1", method = RequestMethod.GET)@ModelAttribute("name3")public String setData3(){return "values is names";}// 5 作为函数的参数,取的是getD方法的返回值,放到model里@ModelAttribute("user")public Student1 getD(){Student1 s = new Student1();s.setName("s1");s.setAge(11);return s;}@RequestMapping(value = "/data", method = RequestMethod.GET)public String data(@ModelAttribute("user") Student1 s) {s.setName("name set in RequestMapping");return "data";} // 6 作为函数参数 从Form表单或URL参数中获取@Controllerpublic class HelloWorldController {@RequestMapping(value = "/helloWorld")public String helloWorld(@ModelAttribute Student1 user) {return "helloWorld";}}// 7 作为方法返回值,就是把user2放到model中@RequestMapping(value = "/data", method = RequestMethod.GET)public @ModelAttribute("user2") Student1 data(@ModelAttribute Student1 s1) {return new Student1().setName("huahua");}
JSON数据转换
- 为实现浏览器与控制器类(Controller)之间的数据交互,Spring提供了一个HttpMessageConverter接口来完成此项工作。该接口主要用于将请求信息中的数据转换为一个类型为T的对象,并将类型为T的对象绑定到请求方法的参数中,或者将对象转换为响应信息传递给浏览器显示
- 其中MappingJacksona2HttpMessageConverter是Spring MVC默认处理JSON格式请求响应的实现类
- 要用到两个重要的JSON格式转换注解@RequestBody和@ResponseBody
@Controller
它返回的是视图(View)名
@RestController
Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式
@ResponseBody
当想把某个控制器的返回转变为JSON数据集时,只需要在方法上标注@ResponseBody注解即可。
在进入控制器方法前,当遇到@ResponseBody,处理器就会记录这个方法响应类型为JSON数据集。当执行完控制器返回后,处理器会启用结果解析器(ResultResolver)去解析这个结果,它会去轮询注册给Spring MVC的HttpMessageConverter接口的实现类。因为MappingJackson2HttpMessageConverter这个实现类已经被Spring MVC所注册,加上Spring MVC将控制器的结果类型标明为JSON,所以就匹配上了,于是通过它就在处理器内部把结果转换为了JSON。
拦截器
Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并做相应的处理。例如通过拦截器可以进行权限验证、判断用户是否已登录等。当请求来到DispatcherServlet时,它会根据HandlerMapping的机制找到处理器,这样就会返回一个HandlerExecutionChain对象,这个对象包含处理器和拦截器。
一种是通过实现 HandlerInterceptor接口或者继承HandlerInterceptor接口的实现类(如HandlerInterceptorAdapter)来定义。
一种是通过实现 WebRequestInterceptor接口或继承WebRequestInterceptor接口的实现类来定义
spring boot项目中使用
1、创建我们自己的拦截器类并实现 HandlerInterceptor 接口。
2、创建一个Java类继承WebMvcConfigurerAdapter,并重写 addInterceptors 方法。
3、实例化我们自定义的拦截器,然后将对像手动添加到拦截器链中(在addInterceptors方法中添加)
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @author starbooks*/
public class MyHandlerInterceptor implements org.springframework.web.servlet.HandlerInterceptor {// 其返回值为false时,会中断后续的所有操作@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("=====>(1)在请求处理之前调用,即在Controller方法调用之前!");return true;}// 该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步的修改@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("=====>(1)在请求处理之后调用,即在controller方法执行之后调用");}// 该方法在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("=====>(1)在整个请求之后调用,即在dispatcherServlet渲染了对应的视图之后(主要是进行资源清理工作)");}
}@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurerAdapter{@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/**");super.addInterceptors(registry);}
}
视图
分为逻辑视图和非逻辑视图,逻辑视图是需要视图解析器(ViewResolver)进行进一步定位的。对于非逻辑视图,则并不需要进一步地定位视图的位置,它只需要直接将数据模型渲染出来即可。
实现PDF导出功能
导包
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf --><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13</version></dependency><dependency><groupId>org.xhtmlrenderer</groupId><artifactId>core-renderer</artifactId><version>R8</version></dependency>
package com.starbooks.service;import java.util.Map;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import com.lowagie.text.Document;
import com.lowagie.text.pdf.PdfWriter;public interface PdfExportService {public void make(Map<String, Object> model, Document document,PdfWriter writer, HttpServletRequest request,HttpServletResponse response);}
package com.starbooks.view;import java.util.Map;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import com.starbooks.tmail.service.PdfExportService;
import org.springframework.web.servlet.view.document.AbstractPdfView;import com.lowagie.text.Document;
import com.lowagie.text.pdf.PdfWriter;public class PdfView extends AbstractPdfView {// 导出服务接口private PdfExportService pdfExportService = null;// 创建对象的时候载入导出服务接口public PdfView(PdfExportService pdfExportService) {this.pdfExportService = pdfExportService;}@Overrideprotected void buildPdfDocument(Map<String, Object> model, Document document,PdfWriter writer,HttpServletRequest request,HttpServletResponse response) throws Exception {// 调用导出服务接口类pdfExportService.make(model, document, writer, request, response);}
}
// 导出PDF接口@GetMapping("/pdf")public ModelAndView exportPdf(String userName, String note) {// 查询用户信息列表List<User> userList = userService.findUsers();// 定义PDF视图View view = new PdfView(exportService());ModelAndView mv = new ModelAndView();// 设置视图mv.setView(view);// 加入数据模型mv.addObject("userList", userList);return mv;}// 导出PDF自定义@SuppressWarnings("unchecked")private PdfExportService exportService() {// 使用Lambda表达式定义自定义导出return (model, document, writer, request, response) -> {try {// A4纸张document.setPageSize(PageSize.A4);// 标题document.addTitle("用户信息");// 换行document.add(new Chunk("\n"));// 表格,3列PdfPTable table = new PdfPTable(3);// 单元格PdfPCell cell = null;// 字体,定义为蓝色加粗Font f8 = new Font();f8.setColor(Color.BLUE);f8.setStyle(Font.BOLD);// 标题cell = new PdfPCell(new Paragraph("id", f8));// 居中对齐cell.setHorizontalAlignment(1);// 将单元格加入表格table.addCell(cell);cell = new PdfPCell(new Paragraph("user_name", f8));// 居中对齐cell.setHorizontalAlignment(1);table.addCell(cell);cell = new PdfPCell(new Paragraph("note", f8));cell.setHorizontalAlignment(1);table.addCell(cell);// 获取数据模型中的用户列表List<User> userList = (List<User>) model.get("userList");for (User user : userList) {document.add(new Chunk("\n"));cell = new PdfPCell(new Paragraph(user.getId() + ""));table.addCell(cell);cell = new PdfPCell(new Paragraph(user.getUserName()));table.addCell(cell);String note = "note";cell = new PdfPCell(new Paragraph(note));table.addCell(cell);}// 在文档中加入表格document.add(table);} catch (DocumentException e) {e.printStackTrace();}};}
文件上传
前端页面,我这里是thymeleaf
<form th:action="@{/upload}" method="post" enctype="multipart/form-data"><div><label for="file">file</label><input type="file" id="file" name="file"></div><button type="submit">submit</button>
</form>
方法一,最简单的一种
@PostMapping("/upload")public String upload2(@RequestParam("file") MultipartFile file, HttpServletRequest req) throws IOException {file.transferTo(new File("C:\\Users\\ASUS\\Desktop\\uploads\\", UUID.randomUUID() + "upload.png"));return "upload";}
方法二
application.properties 配置 # 上传文件路径
spring.servlet.multipart.location=C:\\Users\\ASUS\\Desktop\\upload
# 上传文件总的最大值
spring.servlet.multipart.max-request-size=10MB
# 单个文件的最大值
spring.servlet.multipart.max-file-size=10MB
使用HttpServletRequest作为参数
@PostMapping("/upload1")@ResponseBodypublic String uploadRequest(HttpServletRequest request) {MultipartHttpServletRequest mreq = null;// 强制转换为MultipartHttpServletRequest接口对象if (request instanceof MultipartHttpServletRequest) {mreq = (MultipartHttpServletRequest) request;} else {return "上传失败";}// 获取MultipartFile文件信息MultipartFile mf = mreq.getFile("file");// 获取源文件名称String fileName = mf.getOriginalFilename();File file = new File(fileName);try {// 保存文件mf.transferTo(file);} catch (Exception e) {e.printStackTrace();return "上传失败";}return "上传成功";}
使用Spring MVC的MultipartFile类作为参数
@PostMapping("/upload2")@ResponseBodypublic String uploadMultipartFile(MultipartFile file) {String fileName = file.getOriginalFilename();File dest = new File(fileName);try {file.transferTo(dest);} catch (Exception e) {e.printStackTrace();return"上传失败";}return "上传成功";}
uploadPart方法是使用Servlet的API,可以使用其write方法直接写入文件,这也是推荐的方式
@PostMapping("/upload3")@ResponseBodypublic String uploadPart(Part file) {// 获取提交文件名称String fileName = file.getSubmittedFileName();try {file.write(fileName);} catch (Exception e) {e.printStackTrace();return "上传失败";}return "上传成功";}
操作会话对象
- @SessionAttribute应用于参数,它的作用是将HttpSession中的属性读出,赋予控制器的参数;
- @SessionAttributes则只能用于类的注解,它会将相关数据模型的属性保存到Session中
session
当用户第一次通过浏览器使用用户名和密码访问服务器时,服务器会验证用户数据,验证成功后在服务器端写入session数据,向客户端浏览器返回sessionid,浏览器将sessionid保存在cookie中,当用户再次访问服务器时,会携带sessionid,服务器会拿着sessionid从服务器获取session数据,然后进行用户信息查询,查询到,就会将查询到的用户信息返回,从而实现状态保持。缺点有:
1、服务器压力增大
通常session是存储在内存中的,每个用户通过认证之后都会将session数据保存在服务器的内存中,而当用户量增大时,服务器的压力增大。
2、session是基于cookie的
session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。expires进行具体的日期设置,如果没设置,默认是关闭浏览器时失效。
3、扩展性不强
如果将来搭建了多个服务器,虽然每个服务器都执行的是同样的业务逻辑,但是session数据是保存在内存中的(不是共享的),用户第一次访问的是服务器1,当用户再次请求时可能访问的是另外一台服务器2,服务器2获取不到session信息,就判定用户没有登陆过。(解决方法,session持久化)
4、session这种会话存储方式方式只适用于客户端代码和服务端代码运行在同一台服务器上
token
与session的不同主要认证成功后,会对当前用户数据进行加密,生成一个加密字符串token,返还给客户端(服务器端并不进行保存
浏览器会将接收到的token值存储在Local Storage中,(通过js代码写入Local Storage,通过js获取,并不会像cookie一样自动携带)
服务器对浏览器传来的token值进行解密,解密完成后进行用户数据的查询,如果查询成功,则通过认证,实现状态保持。
Spring Security
导包
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
导入包之后,会默认一个登陆页,用户名是user,密码在日志里
Spring Security也是基于这个原理,在进入到DispatcherServlet前就可以对Spring MVC的请求进行拦截,然后通过一定的验证,从而决定是否放行请求访问系统。
为了对请求进行拦截,Spring Security提供了过滤器DelegatingFilterProxy类给予开发者配置。
FilterChainProxy对象加入自定义的初始化,Spring Security提供了SecurityConfigurer接口,通过它就能够实现对Spring Security的配置。只是有了这个接口还不太方便,因为它只是能够提供接口定义的功能,为了更方便,Spring对Web工程还提供了专门的接口WebSecurityConfigurer,并且在这个接口的定义上提供了一个抽象类WebSecurityConfigurer Adapter
public class TmailApplication extends WebSecurityConfigurerAdapter {public static void main(String[] args) {SpringApplication.run(TmailApplication.class, args);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 密码编码器PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();// 使用内存存储InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> userConfig =auth.inMemoryAuthentication()// 设置密码编码器.passwordEncoder(passwordEncoder);// 注册用户admin,密码为abc,并赋予USER和ADMIN的角色权限userConfig.withUser("admin")// 可通过passwordEncoder.encode("abc")得到加密后的密码.password("$2a$10$5OpFvQlTIbM9Bx2pfbKVzurdQXL9zndm1SrAjEkPyIuCcZ7CqR6je").authorities("ROLE_USER", "ROLE_ADMIN");// 注册用户myuser,密码为123456,并赋予USER的角色权限userConfig.withUser("user")// 可通过passwordEncoder.encode("123456")得到加密后的密码.password("$2a$10$ezW1uns4ZV63FgCLiFHJqOI6oR6jaaPYn33jNrxnkHZ.ayAFmfzLS").authorities("ROLE_USER");}@Overrideprotected void configure(HttpSecurity http) throws Exception {http// 访问/admin下的请求需要管理员权限.authorizeRequests().antMatchers("/admin/**").access("hasRole('ADMIN')")// 通过签名后可以访问任何请求.and().authorizeRequests().antMatchers("/**").permitAll()// 设置登录页和默认的跳转路径.and().formLogin().loginPage("/login").defaultSuccessUrl("/home")// 登出页面和默认跳转路径.and().logout().logoutUrl("/logout").logoutSuccessUrl("/home");}protected void configureHttpSecurity(HttpSecurity http) throws Exception {// 限定签名后的权限http.authorizeRequests()// 限定"/user/welcome"请求赋予角色ROLE_USER或者ROLE_ADMIN.antMatchers("/user/welcome", "/user/details").hasAnyRole("USER", "ADMIN")// 限定"/admin/"下所有请求权限赋予角色ROLE_ADMIN.antMatchers("/admin/**").hasAuthority("ROLE_ADMIN")// 其他路径允许签名后访问.anyRequest().permitAll()/** and代表连接词 **/// 对于没有配置权限的其他请求允许匿名访问.and().anonymous()// 使用Spring Security默认的登录页面.and().formLogin()// 启动HTTP基础验证.and().httpBasic();http.authorizeRequests().regexMatchers("/user/welcome", "/user/details").hasAnyRole("USER", "ADMIN").regexMatchers("/admin/.*").hasAuthority("ROLE_ADMIN").and().formLogin().and().httpBasic();http.csrf().disable().authorizeRequests()// 使用Spring表达式限定只有角色ROLE_USER或者ROLE_ADMIN.antMatchers("/user/**").access("hasRole('USER') or hasRole('ADMIN')")// 设置访问权限给角色ROLE_ADMIN,要求是完整登录(非记住我登录).antMatchers("/admin/welcome1").access("hasAuthority('ROLE_ADMIN') && isFullyAuthenticated()")// 限定"/admin/welcome2"访问权限给角色ROLE_ADMIN,允许不完整登录.antMatchers("/admin/welcome2").access("hasAuthority('ROLE_ADMIN')")// 使用记住我的功能.and().rememberMe()// 使用Spring Security默认的登录页面.and().formLogin()// 启动HTTP基础验证.and().httpBasic();}
spring 异步线程池
Spring中存在一个AsyncConfigurer接口,是一个可以配置异步线程池的接口
因此需要Java配置文件实现AsyncConfigurer接口,实现getAsyncExecutor方法返回的线程池,这样Spring就会将使用这个线程池作为其异步调用的线程。
为了使异步可用,Spring还提供一个注解@EnableAsync,如果Java配置文件标注它,那么Spring就开启异步可用,就可以使用注解@Async驱动Spring使用异步调用。
配置文件如下
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {// 定义线程池@Overridepublic Executor getAsyncExecutor() {// 定义线程池ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();// 核心线程数taskExecutor.setCorePoolSize(10);// 线程池最大线程数taskExecutor.setMaxPoolSize(30);// 线程队列最大线程数taskExecutor.setQueueCapacity(2000);// 初始化taskExecutor.initialize();return taskExecutor;}
}
注解@EnableAsync代表开启Spring异步。这样可以使用@Async驱动Spring使用异步,但是异步需要提供可用线程池,所以这里的配置类还会实现AsyncConfigurer接口,然后覆盖getAsyncExecutor方法,这样就可以自定义一个线程池。因此当方法被标注@Async时,Spring就会通过这个线程池的空闲线程去运行该方法。
public interface AsyncService {// 模拟报表生成的异步方法public void generateReport();
}@Service
public class AsyncServiceImpl implements AsyncService {@Override@Async // 声明使用异步调用public void generateReport() {// 打印异步线程名称System.out.println("报表线程名称:"+ "【" + Thread.currentThread().getName() +"】");}}@RestController
@RequestMapping("/async")
public class AsyncController {// 注入异步服务接口@Autowiredprivate AsyncService asyncService = null;@GetMapping("/page")public String asyncPage() {System.out.println("请求线程名称:" + "【" + Thread.currentThread().getName() + "】");// 调用异步服务asyncService.generateReport();return "async";}
}
异步消息
为了给其他系统发送消息,Java引入了JMS(Java Message Service,Java消息服务)。JMS按其规范分为点对点和发布订阅两种形式。
点对点就是将一个系统的消息发布到指定的另外一个系统,这样另外一个系统就能获得消息,从而处理对应的业务逻辑;
发布订阅模式是一个系统约定将消息发布到一个主题(Topic)中,然后各个系统就能够通过订阅这个主题,根据发送过来的信息处理对应的业务。
工作中实现JMS服务的规范有很多,其中比较常用的有传统的ActiveMQ和分布式的Kafka。为了更为可靠和安全,还存在AMQP协议(Advanced MessageQueuing Protocol),实现它的有RabbitMQ等。
ActiveMQ
activeMQ是开源的,多协议,基于java的消息服务器。可以使用C、Python等其他语言连接。
STOMP协议
STOMP即Simple Text Orientated Messaging Protocol,简单(流)文本定向消息协议。
它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。
STOMP协议的前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计。
STOMP服务器有 Apache Apollo、Apache ActiveMQ、RabbitMQ
MQTT协议
Message Queuing Telemetry Transport,消息队列遥测传输协议
基于发布订阅模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。
优点,可以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。
作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
ActiveMQ 使用
1 . 官网下载
2 . 解压下载好的文件,点击 bin/win64/activemq.bat
,
打开 http://127.0.0.1:8161/admin/
, 用户名 admin
密码 admin
3 . 导包
<!-- activemq --><!-- 依赖starter 可以实现自动配置--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-activemq</artifactId>
</dependency><!--依赖于连接池,这样就可以启用JMS连接池了 -->
<dependency><groupId>org.apache.activemq</groupId><artifactId>activemq-pool</artifactId><version>5.15.9</version>
</dependency><!-- activemq -->
spring boot 2.1 之后,ActiveMQ Pooling改变
移除了activemq-pool并使用pooled-jms替代,pooled-jms提供了和activemq-pool相同的功能,并且符合JMS2.0的规范,maven依赖如下:
// 以前是这个
<dependency><groupId>org.apache.activemq</groupId><artifactId>activemq-pool</artifactId><version>5.15.9</version>
</dependency>// 现在是这个
<dependency><groupId>org.messaginghub</groupId><artifactId>pooled-jms</artifactId>
</dependency>
创建service以及实现
public interface ActiveMqService {public void sendMessage(String message);public void receiveMessage(String message);
}@Service
public class ActiveMqServiceImpl implements ActiveMqService {@Autowiredprivate JmsTemplate jmsTemplate;@Overridepublic void sendMessage(String message) {System.out.println("发送消息:【" + message + "】");// 在默认的情况下,JmsTemplate会提供一个SimpleMessageConverter去提供转换规则,它实现了MessageConverter接口jmsTemplate.convertAndSend(message);// jmsTemplate.convertAndSend(address, message);}//JmsListener注解 监听地址发送过来的消息@Override@JmsListener(destination = "${spring.jms.template.default-destination}")public void receiveMessage(String message) {System.out.println("接收到消息:【" + message + "】");}
}
在controller中使用
@Controller
@RequestMapping("/activemq")
public class ActiveMqController {// 注入服务对象@Autowiredprivate ActiveMqService activeMqService = null;// 注入服务对象@Autowiredprivate ActiveMqUserService activeMqUserService = null;// 测试普通消息的发送@ResponseBody@GetMapping("/msg")public Map<String, Object> msg(String message) {activeMqService.sendMessage(message);return result(true, message);}
}
AMQP
也是一种常用的消息协议。AMQP是一个提供统一消息服务的应用层标准协议,基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品、不同开发语言等条件的限制。
参考资料
- 精通Sprin
- g MVC 4-Geoffroy Warin
- 深入浅出Spring Boot 2.x-杨开振
推荐书
- 领域驱动设计
spring web 知识点过一遍相关推荐
- Spring进阶知识点
Spring进阶知识点 注意:Spring这块可以参考网上的面试题复习. 重点:IOC,AOP,事务,事务传播行为,MVC运行流程. 1. Spring 1.1 复习Spring 什么是 Spring ...
- SpringBoot | 第三十三章:Spring web Servcies集成和使用
前言 最近有个单位内网系统需要对接统一门户,进行单点登录和待办事项对接功能.一般上政府系统都会要求做统一登录功能,这个没啥问题,反正业务系统都是做单点登录的,改下shiro相关类就好了.看了接入方案, ...
- Spring Web(第一部分)
1. Spring Web MVC Spring Web MVC是在Servlet API上构建的原始Web框架,从一开始就包含在Spring框架中.其正式名称"Spring Web MVC ...
- Spring Web MVC(一)
概述 Spring Web MVC框架的特点 五大核心组件 编程步骤 五大核心组件 DispatcherServlet前端控制器 WebApplicationContext中特殊的bean 处理过程 ...
- 用Spring Web Flow和Terracotta搭建Web应用
什么是Spring Web Flow? Spring Web Flow是Spring Framework中的web应用组件,它提供了一种编写有状态和基于会话的web应用的简便手段.Spring Web ...
- 【Spring Web MVC】Spring Web MVC 注解开发环境搭建
为什么80%的码农都做不了架构师?>>> 1.创建maven项目 创建一个名为:springwebmvc-first的maven项目 2.添加依赖包 要使用springWebM ...
- MESSL(maven + extjs + spring portlet mvc + spring web flow + liferay )整合架构 5
流控制文件很简单,就是根元素是<view>,然后用<view-state>来代表一个一个的页面,用<transition>来代表从一个状态到另外一个状态的跳转,如果 ...
- spring_了解Spring Web应用程序体系结构:经典方法
spring 每个开发人员必须了解两件事: 架构设计是必要的. 精美的架构图并未描述应用程序的真实架构. 真正的体系结构是从开发人员编写的代码中找到的,如果不设计应用程序的体系结构,最终将得到一个具有 ...
- spring初始化web_了解Spring Web初始化
spring初始化web 几年前,我们大多数人习惯到处编写XML配置文件,甚至可以设置简单的Java EE应用程序. 如今,使用Java或Groovy来配置项目已成为首选方式–您只需要看一下Sprin ...
最新文章
- 平均分辨准确率对网络隐藏层节点数的非线性变化关系03
- 写 Python 时的 5 个坏习惯
- arcgis鹰眼图问题
- SSO单点登录系统的设计与实现
- Python中显示图片
- 忘记背后 努力面前 向着标杆直跑!(转)
- 《青玉案·元夕》——辛弃疾
- cheeta(cheetah mobile官方)
- Openlayers记录(七)利用ol3进行缓冲区的空间相交分析
- 字典树学习 根据前缀词根建立字典树
- html之响应式(自适应)网页设计
- 卡尔曼滤波(kalman)相关理论以及与HMM、最小二乘法关系
- Java读取hdfs文件权限问题
- uniapp 显示消息提示框 操作
- 农林牧渔行业S2B2B系统高效链接上下游需求,加速平台供应链周转
- 华为笔记本软件商店_华为要消灭流氓软件?干净的电脑应用商店来了!
- Echarts 柱状图柱体颜色渐变效果
- [攻略][Python]给array类型的数据添加方括号、去掉方括号
- 【PBL项目实战】户外智慧农场项目实战系列——2.产品与设备的新建及与云端可视化应用的关联
- win11展开右键菜单(还原为win10状态)的方法
热门文章
- 重要:【企业微信】加好友,要开始收费了!
- 计算机辅助工业设计教案,工业设计手绘教学范文
- 碧蓝航线8.20服务器维护,碧蓝航线8.20更新公告_8月20日更新内容一览
- 倒计时1天!电信、联通正式停售达量限速套餐,网友:早办好了
- #python列表语法规则详解
- springboot 在linux后台运行
- 用python做了一个绝地求生外g???
- linux怎么看mysql地址端口通不通_linux 测试端口通不通(四种方法)
- CSS3快速入门:三、美化网页
- 202320读书笔记|《宋词》——竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生