初始化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做一些前置处理
@ModelAttribute
public 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 不指定返回属性名字,就用返回类的名字转小写,作为属性名字
@ModelAttribute
public 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参数中获取
@Controller
public 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时,会中断后续的所有操作
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("=====>(1)在请求处理之前调用,即在Controller方法调用之前!");
return true;
}
// 该方法会在控制器方法调用之后,且解析视图之前执行。可以通过此方法对请求域中的模型和视图做出进一步的修改
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("=====>(1)在请求处理之后调用,即在controller方法执行之后调用");
}
// 该方法在整个请求完成,即视图渲染结束之后执行。可以通过此方法实现一些资源清理、记录日志信息等工作
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("=====>(1)在整个请求之后调用,即在dispatcherServlet渲染了对应的视图之后(主要是进行资源清理工作)");
}
}
@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurerAdapter{
@Override
public 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;
}
@Override
protected 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")
@ResponseBody
public 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")
@ResponseBody
public 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")
@ResponseBody
public 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);
}
@Override
protected 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");
}
@Override
protected 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 {
// 定义线程池
@Override
public 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 {
// 注入异步服务接口
@Autowired
private 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 {
@Autowired
private JmsTemplate jmsTemplate;
@Override
public 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 {
// 注入服务对象
@Autowired
private ActiveMqService activeMqService = null;
// 注入服务对象
@Autowired
private 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-杨开振
推荐书
- 领域驱动设计