1.SpringBoot分模块javascript
分模块就是将一个项目分红多个模块,即maven项目。html
1)首先建立一个springboot的项目:前端
第一步:选择springboot的项目java
第二步:填写项目的相关信息,主要是下图的红框部分,改为本身的便可,这里就使用默认的,项目名是demomysql
第三步:选择所须要的依赖,这里就只添加web和lombok,其余的后面须要再进行依赖jquery
点击完成后,等待加载完成。nginx
2)建立一个项目启动器:git
第一步:选中刚建的项目,右键建立一个maven的模块,填写模块名称,这里就为project-startgithub
若是这个模块名称有-,那么在点击下一步后须要注意模块的名称,两个模块的名字必须同样。web
第二步:建立完成后,在此模块的main>java和test>java 下新建原父模块同名的包,这里是com.example.demo。
第三步:把java中的启动类拖到这个模块的包下,test中的测试类也是同样,application.properties也拖过来:
第三步:删除父工程的src目录,若是不须要mvnw,也可删除相关的文件。
3)新建一个web的模块,用于与页面交互:
第一步:新建一个maven的模块,名字为web-project
第二步:在project-start的pom.xml文件中添加web-project的依赖:


<dependencies> <dependency> <groupId>com.example</groupId> <artifactId>web-project</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies>
第三步:在main>java的目录下建立一个包,包名必须为com.example.demo。而后在这个包下再建其余的包和类便可。这里就在包下新建一个test.UserController的类,里面的内容以下:


package com.example.demo.test; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @RequestMapping("get") public String get(){ return "123哈哈哈"; } }
第四步:启动springboot。若是整个项目建立的没有问题,那么下面红色框里是正常的,没有×号。点击右边的三角符号启动项目,那么整个项目都会进行编译运行
第五步:在浏览器输入localhost:8080/get,便可显示123哈哈哈。此时多模块的项目已经建立完成。若还须要其余模块,就直接建立模块,而后按照3的步骤便可。
注意:以上说的几个包名必定要注意,必须相同,不然是有问题的。
2.SpringBoot整合视图层技术(模板引擎)开发
整合Thymeleaf:Thymeleaf是新一代的java模板引擎,相似于FreeMarker。官网:https://www.thymeleaf.org。使用方式以下:
1)添加依赖
建立一个springboot的工程,能够选择一个web来建立。而后加入下面的依赖:


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
2)建立实体类Book:


package com.example.demo; public class Book { private int id; private String name; private String author; //setter和getter方法在此略 }
3)建立控制器BookController:


package com.example.demo; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.servlet.ModelAndView; import java.util.ArrayList; import java.util.List; @Controller public class BookController { @GetMapping("getBooks") public ModelAndView get(){ List<Book> books=new ArrayList<>(); Book b1=new Book(); b1.setId(1001); b1.setName("西游记"); b1.setAuthor("张三"); Book b2=new Book(); b2.setId(1002); b2.setName("水浒传"); b2.setAuthor("李四"); books.add(b1); books.add(b2); ModelAndView modelAndView=new ModelAndView(); modelAndView.addObject("books",books); modelAndView.setViewName("books"); return modelAndView; } }
4)建立视图
在资源目录的templates目录下新建book.html,内容以下:


<!DOCTYPE html> <!--导入thymeleaf的命名空间--> <html lang="en" xmlns:th="http:///www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>书籍列表</title> </head> <body> <table> <tr> <td>图书编号</td> <td>图书名称</td> <td>图书做者</td> </tr> <!-- th:each遍历数据,th:text显示数据的具体信息--> <tr th:each="book:${books}"> <td th:text="${book.id}"></td> <td th:text="${book.name}"></td> <td th:text="${book.author}"></td> </tr> </table> </body> </html>
5)测试
在浏览器输入http://localhost:8080/getBooks便可看到模板生成的页面。
3.SpringBoot整合web开发
3.1返回json数据
1)使用默认的json处理器
当springboot中依赖了web以后,就有一个默认的json处理器jackjson-databind,能够直接返回json数据。
添加web依赖代码以下:


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
建立实体类Book:


public class Book { //忽略id字段,那么在返回数据中就不包含这个字段 @JsonIgnore private int id; private String name; private String author; //格式化字段 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date time; //getter和setter方法略 }
建立controller层:


package com.example.demo; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.Date; import java.util.List; @RestController public class BookController { @GetMapping("/books") public List<Book> books(){ List<Book> books=new ArrayList<>(); Book b1=new Book(); b1.setId(1001); b1.setName("西游记"); b1.setAuthor("张三"); b1.setTime(new Date()); Book b2=new Book(); b2.setId(1002); b2.setName("水浒传"); b2.setAuthor("李四"); books.add(b1); books.add(b2); b2.setTime(new Date()); return books; } }
启动项目,在浏览器输入http://localhost:8080/books便可看到json格式的数据。这是springboot自带的,包含忽略字段和格式化等功能。不过也能够按照下面的两个进行自定义转换器。
2)使用fastjson处理器
fastjson是阿里巴巴开发的目前json解析速度最快的开源框架。使用的时候必须先除去jackjson-databind,而后依赖fastjson。依赖以下:


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency>
实体类需按照上面的写法,只不过须要去掉里面的两个注解。而后自定义json的配置类MyFastJsonConfig:


package com.example.demo; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import java.nio.charset.Charset; @Configuration public class MyFastJsonConfig { @Bean//使用@Bean注入fastJsonHttpMessageConvert public HttpMessageConverters fastJsonHttpMessageConverters(){ //须要定义一个Convert转换消息的对象 FastJsonHttpMessageConverter fastConverter=new FastJsonHttpMessageConverter(); //添加fastjson的配置信息,好比是否要格式化返回的json数据 FastJsonConfig fastJsonConfig=new FastJsonConfig(); //设置日期的格式 fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); //设置数据编码 fastJsonConfig.setCharset(Charset.forName("UTF-8")); fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); //在convert中添加配置信息 fastConverter.setFastJsonConfig(fastJsonConfig); HttpMessageConverter<?> converter=fastConverter; return new HttpMessageConverters(converter); } }
除了上面那种方式以外,还有另一种方式,当添加web依赖后,它又依赖了spring-boot-autoconfigure,咱们只需实现WebMvcConfigurer接口便可:


package com.example.demo; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.nio.charset.Charset; import java.util.List; @Configuration public class MyFastJsonConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters){ //须要定义一个Convert转换消息的对象 FastJsonHttpMessageConverter fastConverter=new FastJsonHttpMessageConverter(); //添加fastjson的配置信息,好比是否要格式化返回的json数据 FastJsonConfig fastJsonConfig=new FastJsonConfig(); //设置日期的格式 fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); //设置数据编码 fastJsonConfig.setCharset(Charset.forName("UTF-8")); fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); //在convert中添加配置信息 fastConverter.setFastJsonConfig(fastJsonConfig); HttpMessageConverter<?> converter=fastConverter; converters.add(converter); } }
上面配置完后,设置响应编码,不然中文会乱码。在application.properties中加一行配置:
spring.http.encoding.force-response=true
controller类同上,启动后测试,内容显示正常。
3)使用Gson处理器
Gson是Google的开源json解析框架,也要先去除jackjson-databind,依赖以下:


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency>
而后启动项目就能够正常的显示数据了,只是时间是默认格式的时间。可是有时候须要对数据进行格式化等操做,因此能够自定义一个gsonHttpMessageConverter,代码以下:


package com.example.demo; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.GsonHttpMessageConverter; import java.lang.reflect.Modifier; @Configuration public class GsonConfig { @Bean GsonHttpMessageConverter gsonHttpMessageConverter(){ //初始化实例 GsonHttpMessageConverter converter=new GsonHttpMessageConverter(); GsonBuilder builder=new GsonBuilder(); //设置日期的解析格式 builder.setDateFormat("yyyy-MM-dd HH:mm:ss"); //设置被过滤的字段的修饰符,这里设置protected的被过滤 builder.excludeFieldsWithModifiers(Modifier.PROTECTED); Gson gson=builder.create(); //将对象放入实例中 converter.setGson(gson); return converter; } }
而后把Book对象的id字段修饰符改成protected,而后controller同上,启动项目,测试发现时间是格式化以后的格式,id并无显示,是由于这里被过滤掉了。
3.2静态资源访问
3.2.1 springboot配置了静态的资源过滤,而静态资源一共有5个位置:
1)classpath:/META-INF/resourses/
2)classpath:/resourses/
3)classpath:/static/
4)classpath:/public/
5)/
第五种可不考虑,他们的优先级依次从高到低。即找资源位置的前后顺序。
3.2.2 自定义过滤策略
先在resources目录下新建一个static1的目录,复制一张图片进去,名字为1.png。
1)在配置文件中定义
配置资源目录的位置:
spring.resources.static-locations=classpath:/static1/
2)使用java编码定义
只须要建立一个配置的类实现WebMvcConfigurer接口便可:


package com.example.demo; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class MyWebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/static1/"); } }
无论是哪一种方式定义,启动项目,在浏览器输入http://localhost:8080/1.png均可以看到这张图片。
3.3文件上传
3.3.1单文件上传
首先建立一个springboot的项目,依赖spring-boot-starter-web,(后面的默认都使用快速建立的方式建立SpringBoot项目,并勾选了web项)而后步骤以下:
1)在资源目录下建立一个文件上传的页面upload.html


<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>文件上传</title> </head> <body> <form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="file" value="选择文件"> <input type="submit" value="上传"> </form> </body> </html>
2)建立文件上传的接口upload


package com.example.uploaddemo.controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.UUID; @RestController public class UploadController { @PostMapping("/upload") public String upload(MultipartFile file, HttpServletRequest request){ //设置日期的格式 SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd"); //设置文件的保存路径是项目运行目录下的uploadFile目录下 String realPath=new File("D:/upload/").getAbsolutePath(); //经过日期 对文件归类,如2019/11/30,2019/11/29 String format=File.separator+sdf.format(new Date()); //根据规则建立目录 File folder=new File(realPath+format); if(!folder.isDirectory()){ folder.mkdirs(); } //获取文件的原始名 String oldName=file.getOriginalFilename(); //获取文件的后缀名 String suffix=oldName.substring(oldName.lastIndexOf("."),oldName.length()); //使用uuid设置新的文件名,防止文件名重复 String newName= UUID.randomUUID().toString()+suffix; try { //文件保存 file.transferTo(new File(folder,newName)); //生成文件的保存路径 String accessPath=realPath+format+File.separator+newName; return accessPath; } catch (IOException e) { e.printStackTrace(); } return "上传失败"; } }
注意:按照上面的写法,页面中type是file的name值必须和后台得参数值相同,不然会出现空指针异常。
3)测试
启动项目,在浏览器输入upload.html,选择文件上传,上传成功时会返回文件保存的位置,此时在指定的目录下会生成多级的目录,最后一级是文件。
3.3.2多文件上传
1)页面upload2.html:


<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>多文件上传</title> </head> <body> <form action="/uploads" method="post" enctype="multipart/form-data"> <input type="file" name="file" value="选择文件" multiple> <input type="submit" value="上传"> </form> </body> </html>
2)后台接口uploads:


@PostMapping("/uploads") public String uploads(MultipartFile[] file, HttpServletRequest request) { //设置日期的格式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); //设置文件的保存路径是项目运行目录下的uploadFile目录下 String realPath = new File("D:/upload/").getAbsolutePath(); //经过日期 对文件归类,如2019/11/30,2019/11/29 String format = "/" + sdf.format(new Date()); //根据规则建立目录 File folder = new File(realPath + format); if (!folder.isDirectory()) { folder.mkdirs(); } try { for (MultipartFile multipartFile : file) { //获取文件的原始名 String oldName = multipartFile.getOriginalFilename(); //获取文件的后缀名 String suffix = oldName.substring(oldName.lastIndexOf("."), oldName.length()); //使用uuid设置新的文件名,防止文件名重复 String newName = UUID.randomUUID().toString() + suffix; //文件保存 multipartFile.transferTo(new File(folder, newName)); //生成文件的保存路径 String accessPath = realPath + format + newName; } return "上传成功"; } catch (IOException e) { e.printStackTrace(); } return "上传失败"; }
而后启动项目,进行测试。在选择文件的时候,使用ctrl来选择多个文件,而后点击打开,接着上传就好了。
3.4@ControllerAdvice
它是@Controller的加强版,功能比较多。
1)全局异常处理
当上面的文件大小超出限制时,就会抛出异常,可是咱们是须要处理的,就可使用@ControllerAdvice。新建一个异常处理的类CustomExceptionHandler,代码以下,那么当文件大小超出限制时就会在页面显示我设置输出的内容:


package com.example.demo; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.multipart.MaxUploadSizeExceededException; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @ControllerAdvice public class CustomExceptionHandler { @ExceptionHandler(MaxUploadSizeExceededException.class) public void uploadException(MaxUploadSizeExceededException e, HttpServletResponse resp) throws IOException { resp.setContentType("text/html;charset=utf-8"); PrintWriter out=resp.getWriter(); out.write("文件大小超出限制"); out.flush(); out.close(); } }
2)全局数据配置
只要配置了全局数据,那么就能够在任何的Controller中经过方法参数中的Model获取对应的内容。
新建一个去全局数据配置的类:


package com.example.demo; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ModelAttribute; import java.util.HashMap; import java.util.Map; @ControllerAdvice public class GlobalConfig { //配置全局数据 @ModelAttribute("user") public Map<String,String> user(){ Map<String,String> map=new HashMap<>(); map.put("username","张三"); map.put("sex","女"); return map; } }
新建一个Controller来获取这个全局数据:


package com.example.demo; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Iterator; import java.util.Map; import java.util.Set; @RestController public class HelloController { @GetMapping("/hello") public void hello(Model model){ Map<String,Object> map=model.asMap(); Set<String> keySet=map.keySet(); Iterator<String> iterator = keySet.iterator(); while (iterator.hasNext()){ String key=iterator.next(); Object value=map.get(key); System.out.println(key+",,"+value); } } }
在浏览器访问localhost:8080/htllo,就会在控制台打印结果,以下
3)请求参数预处理
将表单的数据绑定到实体类上时进行额外的处理。
先建立两个实体类Book和Author:


public class Book { private String name; private String author; //setter/getter方法略 }


public class Author { private String name; private int age; //setter/getter方法略 }
在传递参数时,两个name是同样的,会混淆,那就建立Controller的类,进行配置,关键代码以下:


@GetMapping("book") public String book(@ModelAttribute("b") Book book,@ModelAttribute("a") Author author){ return book.toString()+"--"+author.toString(); }
对参数预处理,代码以下:


package com.example.demo; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.InitBinder; @ControllerAdvice public class GlobalConfig { //给每一个方法的字段加上前缀 @InitBinder("a") public void inita(WebDataBinder binder){ binder.setFieldDefaultPrefix("a."); } @InitBinder("b") public void initb(WebDataBinder binder){ binder.setFieldDefaultPrefix("b."); } }
3.5拦截器
建立springboot项目,添加web依赖,而后建立拦截器,代码以下:


package com.example.demo; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle..."); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion..."); } }
配置拦截器:


package com.example.demo; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // addPathPatterns配置拦截路径 // excludePathPatterns排除拦截的路径 registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/hello"); } }
拦截器的方法按照preHandle、Controller、postHandle、afterCompletion的顺序执行,只有当preHandle方法返回true时后面的方法才会执行。当拦截器链存在多个拦截器时,postHandler在拦截器内的全部拦截器返回成功时才会调用,而afterCompletion只要preHandle返回true时才会调用。
4.Spring Boot整合持久层
整合持久层就是和数据库打交道,这里以mysql为例。
4.1准备工做
首先建立一个数据库和表,代码以下:


create database test1 default character set utf8; use test1; CREATE TABLE `book` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL, `author` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; insert into book values(null,'三国演义','罗贯中'); insert into book values(null,'水浒传','施耐庵');
而后建立springboot的项目。
4.2整合JdbcTemplate
第一步:导入依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> </dependency>
其中lombok能够导入也能够不导入,这里是为了使用get和set方法方便。
第二步:配置数据源,这里使用的配置文件是application.yml


#数据源配置 spring: datasource: #使用阿里巴巴的druid type: com.alibaba.druid.pool.DruidDataSource #配置数据库的路径和用户名密码 url: jdbc:mysql://localhost:3306/test1?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC username: root password: root
第三步:建立类Book


package com.example.demo.entity; import lombok.Getter; import lombok.Setter; import lombok.ToString; @Getter @Setter @ToString public class Book { private Integer id; private String name; private String author; }
第四步:建立类BookController


package com.example.demo.controller; import com.example.demo.entity.Book; import com.example.demo.service.Bookservice; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/book") public class BookController { @Autowired private BookService bookservice; @RequestMapping("/addBook") public String addBook(){ Book book=new Book(); book.setName("西游记"); book.setAuthor("张三"); bookservice.addBook(book); return "添加成功"; } @RequestMapping("/updateBook") public String updateBook(){ Book book=new Book(); book.setId(3); book.setName("西游记2"); book.setAuthor("张三2"); bookservice.updateBook(book); return "修改为功"; } @RequestMapping("/deleteBook") public String deleteBook(){ bookservice.deleteBook(3); return "添删成功"; } @RequestMapping("/getAllBook") public List<Book> getAllBook(){ return bookservice.getAllBook(); } }
第五步:建立类BookService


package com.example.demo.service; import com.example.demo.dao.BookDao; import com.example.demo.entity.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class BookService { @Autowired private BookDao bookDao; public int addBook(Book book){ return bookDao.addBook(book); } public int updateBook(Book book){ return bookDao.updateBook(book); } public int deleteBook(Integer id){ return bookDao.deleteBook(id); } public List<Book> getAllBook(){ return bookDao.getAllBook(); } }
第六步:建立类BookDao


package com.example.demo.dao; import com.example.demo.entity.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.util.List; @Repository public class BookDao { @Autowired private JdbcTemplate template; //添加图书 public int addBook(Book book){ String sql="insert into book values(null,?,?)"; return template.update(sql,book.getName(),book.getAuthor()); } //修改图书 public int updateBook(Book book){ String sql="update book set author=?,name=? where id=?"; return template.update(sql,book.getAuthor(),book.getName(),book.getId()); } //删除图书 public int deleteBook(Integer id){ String sql="delete from book where id=?"; return template.update(sql,id); } //查询图书 public List<Book> getAllBook(){ String sql="select * from book"; return template.query(sql,new BeanPropertyRowMapper<>(Book.class)); } }
第七步:测试。启动项目,在浏览器输入localhost:8080/book/abbBook便可向数据库添加设定的数据,同理其余的几个接口也可使用。到这里SpringBoo整合JdbcTemolate已经完成了,至于从前端向controller的接口传递数据,这里暂时不讲。
4.3整合MyBatis
第一步:导入依赖


<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency>
第二步:类Book,类BookController同上,数据源application.yml同上,而BookService中只需把@Autowired中的BookDao改为BookMapper便可,其余同上。
第三步:建立接口BookMapper


package com.example.demo.dao; import com.example.demo.entity.Book; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface BookMapper { int addBook(Book book); int updateBook(Book book); int deleteBook(Integer id); List<Book> getAllBook(); }
第四步:建立BookMapper.xml文件
在资源目录下先一次建立和BookMapper相同的包,这里是com.example.demo.dao。包建立完成后再包下建立一个名为BookMapper.xml的文件


<?xml version="1.0" encoding="uTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.dao.BookMapper"> <insert id="addBook" parameterType="com.example.demo.entity.Book"> insert into book values(null,#{name},#{author}) </insert> <update id="updateBook" parameterType="com.example.demo.entity.Book"> update book set name=#{name},author=#{author} where id=#{id} </update> <delete id="deleteBook" parameterType="java.lang.Integer"> delete from book where id=#{id} </delete> <select id="getAllBook" resultType="com.example.demo.entity.Book"> select * from book </select> </mapper>
第五步:启动项目,进行测试。
4.4整合Spring Data JPA
JPA是一种ORM规范,Hibernate是一个ORM框架,所以JPA至关于Hibernate的一个子集。这里只须要有数据库便可,就是要test1数据库,不须要手动建立表,。
第一步:导入依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency>
第二步:数据库配置


spring: #数据源配置 datasource: #使用阿里巴巴的druid type: com.alibaba.druid.pool.DruidDataSource #配置数据库的路径和用户名密码 url: jdbc:mysql://localhost:3306/test1?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC username: root password: root #JPA相关配置 jpa: #指定数据库 database: mysql #在控制台打印JPA执行过程当中生成的sql show-sql: true #项目启动时根据实体类更新数据库的表 hibernate: ddl-auto: update
第三步:建立实体类Book


package com.example.demo.entity; import lombok.Getter; import lombok.Setter; import lombok.ToString; import javax.persistence.*; @Getter @Setter @ToString //表示该类是一个实体类,name是表名,不写则默认是类名 @Entity(name="t_book") public class Book { //id表示是主键,而后GeneratedValue是自动生成,配置生成的策略 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; //Column设置字段的名字,不写则是属性名 @Column(name = "book_name",nullable = false) private String name; private String author; private Float price; //Transient设置忽略的字段,建立表时不生成此字段 @Transient private String description; }
第四步:建立类BookController


package com.example.demo.controller; import com.example.demo.entity.Book; import com.example.demo.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/book") public class BookController { @Autowired private BookService bookservice; @GetMapping("/add") public Book insert( Book book){ return bookservice.save(book); } @GetMapping("/findAll") public List<Book> findAll(){ return bookservice.findAll(); } }
第五步:建立类BookService


package com.example.demo.service; import com.example.demo.dao.BookDao; import com.example.demo.entity.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class BookService { @Autowired private BookDao bookDao; public Book save(Book book) { return bookDao.save(book); } public List<Book> findAll() { return bookDao.findAll(); } }
第六步:建立接口BookDao


package com.example.demo.dao; import com.example.demo.entity.Book; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; @Component public interface BookDao extends JpaRepository<Book,Integer>{ }
BookDao中没有写方法,缘由是jpa中有一些经常使用的方法。
4.5配置多数据源
多数据源就是javaEE项目中采用了不一样数据库实例中的 多个库,或者同一个数据库实例中多个不一样的库。
1)JdbcTemplate多数据源
第一步:建立两个数据库test1,test2,而后在两个数据库中分别建立一个book,并插入一条不一样的内容,建立test1的脚本以下,test2同


create database test2 default character set utf8 use test2 CREATE TABLE `book` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL, `author` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
第二步:添加依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency>
第三步:配置数据库


#多数据源配置 #数据源1 spring: datasource: one: type: com.alibaba.druid.pool.DruidDataSource #配置数据库的路径和用户名密码 url: jdbc:mysql://localhost:3306/test1?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC username: root password: root #数据源2 two: type: com.alibaba.druid.pool.DruidDataSource #配置数据库的路径和用户名密码 url: jdbc:mysql://localhost:3306/test2?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC username: root password: root
第四步:配置多数据源


package com.example.demo.config; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration public class DataSourceConfig { //根据不一样前缀的配置文件来建立不一样的DataSource实例 @Bean @ConfigurationProperties("spring.datasource.one") DataSource dsOne(){ return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.two") DataSource dsTwo(){ return DruidDataSourceBuilder.create().build(); } }
第五步:配置JdbcTemplate


package com.example.demo.config; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; @Configuration public class JdbcTemplateConfig { //根据不一样的DataSource实例来建立jdbcTemplate的实例 @Bean JdbcTemplate jdbcTemplateOne(@Qualifier("dsOne")DataSource dataSource){ return new JdbcTemplate(dataSource); } @Bean JdbcTemplate jdbcTemplateTwo(@Qualifier("dsTwo")DataSource dataSource){ return new JdbcTemplate(dataSource); } }
第六步:建立类BookController
为了简单化,这里就直接使用controller来注入JdbcTemplate,在实际开发中需规范化。


package com.example.demo.controller; import com.example.demo.entity.Book; import com.example.demo.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.quartz.QuartzProperties; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/book") public class BookController { //如下使用两种不一样的方法注入JdbcTemplate @Resource(name = "jdbcTemplateOne") private JdbcTemplate jdbcTemplateOne; @Autowired @Qualifier("jdbcTemplateTwo") private JdbcTemplate jdbcTemplateTwo; @GetMapping("/find") public Map find(){ Map<String,Object> map=new HashMap<>(); String sql="select * from book"; List<Book> query1 = jdbcTemplateOne.query(sql, new BeanPropertyRowMapper<>(Book.class)); List<Book> query2 = jdbcTemplateTwo.query(sql, new BeanPropertyRowMapper<>(Book.class)); map.put("datasouce1",query1); map.put("datasouce2",query2); return map; } }
第七步:测试
启动项目,在浏览器输入localhost:8080/book/get便可看到查询的两个数据库的全部结果。
2)Mybatis多数据源
第一步:上面已经详细的介绍了一些配置信息,这里就再也不赘述。两个数据库同上,数据库配置同上,多数据源配置同上。依赖也只是把Spring-boot-starter-jdbc替换成mybatis的依赖便可,mybatis的依赖以下:


<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency>
第二步:配置Mybatis
第一个配置类


package com.example.demo.config; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; import javax.sql.DataSource; @Configuration //指定接口所在的位置,它下面的接口将使用SqlSessionFactory实例 @MapperScan(value = "com.example.demo.dao",sqlSessionFactoryRef = "sqlSessionFactoryBean1") public class MybatisConfigOne { @Resource(name="dsOne") private DataSource dsOne; @Bean SqlSessionFactoryBean sqlSessionFactoryBean1() throws Exception { SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean(); factoryBean.setDataSource(dsOne); return (SqlSessionFactoryBean) factoryBean.getObject(); } @Bean SqlSessionTemplate sqlSessionTemplate1() throws Exception { return new SqlSessionTemplate((SqlSessionFactory) sqlSessionFactoryBean1()); } }
第二个配置类


package com.example.demo.config; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; import javax.sql.DataSource; @Configuration //指定接口所在的位置,它下面的接口将使用SqlSessionFactory实例 @MapperScan(value = "com.example.demo.dao",sqlSessionFactoryRef = "sqlSessionFactoryBean2") public class MybatisConfigTwo { @Resource(name="dsTwo") private DataSource dsTwo; @Bean SqlSessionFactoryBean sqlSessionFactoryBean2() throws Exception { SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean(); factoryBean.setDataSource(dsTwo); return (SqlSessionFactoryBean) factoryBean.getObject(); } @Bean SqlSessionTemplate sqlSessionTemplate1() throws Exception { return new SqlSessionTemplate((SqlSessionFactory) sqlSessionFactoryBean2()); } }
第三步:在包com.example.demo.dao和com.example.demo.dao2分别建立接口BookMapper和BookMapper2


package com.example.demo.dao; import com.example.demo.entity.Book; import java.util.List; public interface BookMapper { List<Book> getAllBook(); }


package com.example.demo.dao2; import com.example.demo.entity.Book; import java.util.List; public interface BookMapper2 { List<Book> getAllBook(); }
第四步:在上面两个包中分别建立BookMapper.xml,BookMapper2.xml


<?xml version="1.0" encoding="uTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.dao.BookMapper"> <select id="getAllBook" resultType="com.example.demo.entity.Book"> select * from book </select> </mapper>


<?xml version="1.0" encoding="uTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.dao2.BookMapper2"> <select id="getAllBook" resultType="com.example.demo.entity.Book"> select * from book </select> </mapper>
第五步:建立BookController


package com.example.demo.controller; import com.example.demo.dao.BookMapper; import com.example.demo.dao2.BookMapper2; import com.example.demo.entity.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/book") public class BookController { @Autowired private BookMapper mapper; @Autowired private BookMapper2 mapper2; @GetMapping("/find") public Map find(){ Map<String,Object> map=new HashMap<>(); List<Book> query1 = mapper.getAllBook(); List<Book> query2 = mapper2.getAllBook(); map.put("datasouce1",query1); map.put("datasouce2",query2); return map; } }
第六步:启动项目,进行测试。
4.6.SpringBoot整合Mybatis出现属性为null不能插入的状况处理(无完整代码)
当前端传入的数据给后台,一个对象中有的属性为null时mybatis是不能进行插入操做的,可是需求是这些为null的值得转换为空字符串存入到数据库,其中的一个解决办法以下:
第一步:建立一个类,用于转换类型是字符串,值为null的属性


package com.kanq.framework.util; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.TypeHandler; import org.springframework.context.annotation.Configuration; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * 当值为null时设置为"",用于mybatis的数据插入 */ @Configuration public class NullValueHandler implements TypeHandler<String> { @Override public void setParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException { if(s==null&&jdbcType==JdbcType.VARCHAR){//判断传入的参数值是否为null preparedStatement.setString(i,"");//设置当前参数的值为空字符串 }else{ preparedStatement.setString(i,s);//若是不为null,则直接设置参数的值为value } } @Override public String getResult(ResultSet resultSet, String s) throws SQLException { return resultSet.getString(s); } @Override public String getResult(ResultSet resultSet, int i) throws SQLException { return resultSet.getString(i); } @Override public String getResult(CallableStatement callableStatement, int i) throws SQLException { return callableStatement.getString(i); } }
第二步:在mybatis的BookMapper.xml中修改以下


insert into xcjhb values(null, #{xczrr,jdbcType=VARCHAR,typeHandler=com.kanq.framework.util.NullValueHandler},.....)
typeHandler的值是NullValueHandler所在的路径,这样当前台传入的值为null时后台就能够正常插入了。
5.SpringBoot整合NoSQL
5.1整合Redis
在整合以前,默认redis已经在虚拟机上安装完成,而且容许外网访问。
第一步:导入依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <!-- 排除lettuce,使用jredis--> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
第二步:配置数据库链接


#配置redis链接信息 spring: redis: #配置redis的编号,redis有16个database,0~15 database: 0 #配置主机地址 host: 192.168.6.128 #配置redis端口号,默认是6379 port: 6379 #配置redis登陆密码 password: 1234 #配置链接池信息 jedis: pool: #配置最大链接数 max-active: 8 #配置最大空闲链接数 max-idle: 8 #配置最大阻塞等待时间 max-wait: -1ms #配置最小空闲链接数 min-idle: 0
第三步:建立实体类Book


package com.example.demo.entity; import lombok.Getter; import lombok.Setter; import lombok.ToString; import java.io.Serializable; @Getter @Setter @ToString public class Book implements Serializable { private Integer id; private String name; private String author; }
第四步:建立BookController


package com.example.demo.controller; import com.example.demo.entity.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/book") public class BookController { //RedisTemplate使用时,对象必定要实现Serializable接口 @Autowired private RedisTemplate redisTemplate; //StringRedisTemplate是RedisTemplate的子类,它的key和value都是字符串 @Autowired private StringRedisTemplate stringRedisTemplate; @GetMapping("/test1") public String test1(){ //获取操做对象 ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); //存储记录 ops.set("name","三国演义"); //取出记录 String name=ops.get("name"); return name; } @GetMapping("/test2") public Book test2(){ ValueOperations ops1 = redisTemplate.opsForValue(); Book book=new Book(); book.setId(12); book.setAuthor("曹雪芹"); book.setName("红楼梦"); ops1.set("book",book); Book book1 = (Book) ops1.get("book"); return book1; } }
第五步:测试。启动项目,在浏览器输入localhost:8080/book/test1便可看到test1方法存的数据,同理能够访问test2。
5.2session共享
session共享简单的来讲就是把用户登陆后的session根据用户的惟一主键放到redis里面,用户在访问分布的其余系统时先去redis中查看是否有这个用户的session信息,有的话就不用再次登陆,没有就须要登陆。
第一步:在导入redis依赖的基础上,去除应用自己的session,添加须要的session依赖


<!--替换应用的session容器--> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>
第二步:在controller中的存储和获取代码以下


package com.example.demo.controller; import com.example.demo.entity.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpSession; @RestController @RequestMapping("/book") public class BookController { @Value("${server.port}") String port; @GetMapping("/save") public String save(String name, HttpSession session){ //向session中存储数据 session.setAttribute("name",name); return port; } @GetMapping("/get") public String get(HttpSession session){ //从session中h获取数据 return port+":"+session.getAttribute("name").toString(); } }
第三步:把这个项目打包,而后部署到虚拟机上,部署两个,端口号不同便可,这里就用8080和8081。
第四步:配置nginx负载均衡,对上面的两个项目配置负载均衡便可,默认监听80端口。
第五步:测试。在本机的浏览器输入http://虚拟机ip/book/save?name=123就能够把session存到redis中,同理get能够取出session。
6.SpringBoot缓存
6.1Ehcache缓存
这里使用mybatis进行数据的查询,mybatis的部分配置这里略。
第一步:导入依赖,除了mybatis依赖其余依赖以下


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>
第二步:添加缓存的配置文件ehcache.xml,这个文件能够直接放在资源目录下


<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <!--配置默认的缓存--> <defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="120" timeToLiveSeconds="120" memoryStoreEvictionPolicy="LRU"/> <cache name="book_cache" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="180" timeToLiveSeconds="180" memoryStoreEvictionPolicy="LRU"/> </ehcache>
若是须要将这个配置文件放在其余目录下,能够指定位置,好比放在资源目录下的config目录中


#配置缓存的配置文件位置 spring: cache: ehcache: config: classpath:config/ehcache.xml
第三步:添加mybatis的配置,根据须要,能够设置mybatis的日志级别是debug,那么能够在控制台打印执行的sql语句


logging: level: #下面先指定包名,再指定等级 com: example: demo: dao: debug
第四步:开启缓存
修改启动类,添加代码以下
@SpringBootApplication @EnableCaching//开启缓存 public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); System.out.println("-----------启动成功------------"); } }
第五步:建立实体类Book,这里必须实现Serializable接口


package com.example.demo.entity; import lombok.Getter; import lombok.Setter; import lombok.ToString; import java.io.Serializable; @Getter @Setter @ToString public class Book implements Serializable { private Integer id; private String name; private String author; }
第六步:建立BookController


package com.example.demo.controller; import com.example.demo.dao.BookMapper; import com.example.demo.entity.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/book") public class BookController { @Autowired private BookMapper mapper; @RequestMapping("/query") public Book findById(int id){ return mapper.findById(id); } @RequestMapping("/update") public int update(Book book){ return mapper.update(book); } @RequestMapping("/delete") public int delete(int id){ return mapper.delete(id); } }
第七步:建立BookMapepr


package com.example.demo.dao; import com.example.demo.entity.Book; import org.apache.ibatis.annotations.Mapper; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; @Mapper @CacheConfig(cacheNames = "book_cache") public interface BookMapper { //对这个方法缓存,默认缓存的key是参数,value是方法的返回值 @Cacheable public Book findById(int id); @CacheEvict(key = "#book.id") public int update(Book book); @CacheEvict(key= "#id") public int delete(int id); }
BookMapepr.xml在这里略。在浏览器输入对应的路径便可测试。
注:当执行同一个条件的查询屡次时,只会去查询一次,而后把参数做为key,返回值做为value存到缓存中。当修改或删除后,就把对应的数据从缓存中删除。
@Cacheable : Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,若是存在就再也不执行该方法,而是直接从缓存中获取结果进行返回,不然才会执行并将返回结果存入指定的缓存中。 @CacheEvict : 清除缓存。 @CachePut标注的方法在执行前不会去检查缓存中是否存在以前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。 这三个方法中都有两个主要的属性:value 指的是 ehcache.xml 中的缓存策略空间;key 指的是缓存的标识,同时能够用 # 来引用参数。
6.2redis缓存
第一步:添加依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
redis数据库链接配置详见上面第5节内容。剩余步骤同上6.1的第三至第六步。
7.SpringBoot整合SpringSecurity
7.1基本原理
spring security的核心是用户认证(Authentication)和用户受权(Authorization)。
用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户可否访问该系统。通常要求用户提供用户名和密码。
用户受权指的是验证某个用户是否有权限执行某个操做。在一个系统中,不一样用户所具备的权限是不一样的。
认证原理图
7.2基本配置
这里使用Mybatis和SpringSecurity共同开发,除了Mybatis的配置,其余配置以下
第一步:导入依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
第二步:建立使用的页面
在资源目录的static目录下建立几个页面


***********add.html************ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>添加订单!!!!!!!</h2> </body> </html> ***********look.html************ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>查看订单!!!!!!!</h2> </body> </html> ***********delete.html************ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>删除订单!!!!!!!</h2> </body> </html> ***********update.html************ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>修改订单!!!!!!!</h2> </body> </html> ***********index.html************ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <p><a href="/look">查看订单</a></p> <p><a href="/add">添加订单</a></p> <p><a href="/delete">删除订单</a></p> <p><a href="/update">修改订单</a></p> </body> </html>
配置application.properties,其余配置在这里略,参考所讲配置


spring.mvc.view.prefix=/ spring.mvc.view.suffix=.html spring.resources.static-locations=classpath:/static
第三步:建立UserController类


package com.example.springsecurity.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Controller public class UserController { @RequestMapping("/") public String index(){ return "index"; } @RequestMapping("/add") public String add(){ return "add"; } @RequestMapping("/look") public String look(){ return "look"; } @RequestMapping("/delete") public String delete(){ return "delete"; } @RequestMapping("/update") public String update(){ return "update"; } }
7.3 httpBaisc的方式
1)在config包下建立SecurityConfig的配置类:


package com.example.springsecurity.config; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @Component @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { //配置加密的方式 @Bean PasswordEncoder passwordEncoder(){ //设置不加密 return NoOpPasswordEncoder.getInstance(); } //配置认证用户信息和受权 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //基于内存的认证 auth.inMemoryAuthentication().withUser("user").password("1234").authorities("addUser"); } //配置拦截请求资源 @Override protected void configure(HttpSecurity http) throws Exception { http //开启HttpSecurity配置 .authorizeRequests() //指定路径 .antMatchers("/**") //拦截全部 .fullyAuthenticated() //配置认证模式 .and().httpBasic(); } }
启动项目在浏览器输入localhost:8080,就会出现以下界面,须要进行登陆。
这里没有配置加密,登陆成功后才能进行访问其余的资源。
7.4 使用FormLogin的方式
1)只须要在7.3的基础上把configure(HttpSecurity http)的.httpBasic()换成formLogin()便可,就会出现登陆页面。
2)示例:admin用户能够访问全部的资源,user用户只能添加和查询订单的资源,SecurityConfig配置以下:


package com.example.springsecurity.config; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @Component @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { //配置加密的方式 @Bean PasswordEncoder passwordEncoder(){ //设置不加密 return NoOpPasswordEncoder.getInstance(); } //配置认证用户信息和受权 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //基于内存的认证 //配置user拥有权限 auth.inMemoryAuthentication(). withUser("user").password("1234").authorities("add","look"); //配置admin拥有全部的权限 auth.inMemoryAuthentication(). withUser("admin").password("1234").authorities("add","look","delete","update"); } //配置拦截请求资源 @Override protected void configure(HttpSecurity http) throws Exception { http //开启HttpSecurity配置 .authorizeRequests() //配置权限的受权 .antMatchers("/add").hasAuthority("add") .antMatchers("/look").hasAuthority("look") .antMatchers("/delete").hasAuthority("delete") .antMatchers("/update").hasAuthority("update") .antMatchers("/**").fullyAuthenticated() //配置认证模式 .and().formLogin(); } }
启动项目,使用admin登陆可访问全部的资源,而user登陆后访问look和add之外的资源时会出现403,这就是权限分配。
3)更改403权限不足页面
在static目录下新建error/403.html,内容以下


<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 权限不足!没法访问 </body> </html>
新建一个配置类WebServerAutoConfiguration


package com.example.springsecurity.config; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; /** * 配置发生错误的请求路径 */ @Configuration public class WebServerAutoConfiguration { @Bean public ConfigurableServletWebServerFactory webServerFactory(){ TomcatServletWebServerFactory factory=new TomcatServletWebServerFactory(); ErrorPage errorPage400=new ErrorPage(HttpStatus.BAD_REQUEST,"error/400"); ErrorPage errorPage401=new ErrorPage(HttpStatus.UNAUTHORIZED,"error/401"); ErrorPage errorPage403=new ErrorPage(HttpStatus.FORBIDDEN,"error/403"); ErrorPage errorPage404=new ErrorPage(HttpStatus.NOT_FOUND,"error/404"); ErrorPage errorPage415=new ErrorPage(HttpStatus.UNSUPPORTED_MEDIA_TYPE,"error/415"); ErrorPage errorPage500=new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR,"error/500"); factory.addErrorPages(errorPage400,errorPage401,errorPage403,errorPage404,errorPage415,errorPage500); return factory; } }
新建错误的controller处理类ErrorController


package com.example.springsecurity.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * 错误的controller */ @Controller public class ErrorController { @RequestMapping("/error/403") public String error(){ return "error/403"; } }
这里是以403错误为例,自定义其余的错误页面雷同。启动项目,当使用user用户登陆访问look和add之外的资源时会显示自定义的403页面的内容。
4)更换自定义登陆页面
在资源目录下新建login.html


<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登陆</title> </head> <body> <form method="post" action="/login"> <p> <input type="text" id="username" name="username" placeholder="用户名"> </p> <p> <input type="password" id="password" name="password" placeholder="密码"> </p> <button type="submit">登陆</button> </form> </body> </html>
修改security的配置类,指定自定义的登陆页面以及登陆成功或失败的处理


package com.example.springsecurity.config; import com.example.springsecurity.handler.MyAuthenticationFailureHandler; import com.example.springsecurity.handler.MyAuthenticationSuccessHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 登陆成功的处理 */ @Autowired private MyAuthenticationSuccessHandler successHandler; /** * 登陆失败的处理 */ @Autowired private MyAuthenticationFailureHandler failureHandler; //配置加密的方式 @Bean PasswordEncoder passwordEncoder() { //设置不加密 return NoOpPasswordEncoder.getInstance(); } //配置认证用户信息和受权 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //配置user拥有权限 auth.inMemoryAuthentication(). withUser("user").password("1234").authorities("add","look"); //配置admin拥有全部的权限 auth.inMemoryAuthentication(). withUser("admin").password("1234").authorities("add","look","delete","update"); } //配置拦截请求资源 @Override protected void configure(HttpSecurity http) throws Exception { http //开启HttpSecurity配置 .authorizeRequests() //指定路径 //配置权限的受权 .antMatchers("/add").hasAuthority("add") .antMatchers("/look").hasAuthority("look") .antMatchers("/delete").hasAuthority("delete") .antMatchers("/update").hasAuthority("update") .antMatchers("/login.html").permitAll() .antMatchers("/**").fullyAuthenticated() //配置认证模式 .and().formLogin() .loginPage("/login.html") .loginProcessingUrl("/login") //登陆成功的操做 .successHandler(successHandler) //登陆失败的操做 .failureHandler(failureHandler) .and() //关闭cors .csrf() .disable(); } }
建立登陆成功的处理类MyAuthenticationSuccessHandler


package com.example.springsecurity.handler; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @Author: yushizhong * @Date: 2020/1/7 10:11 * @Title: 登陆成功处理 */ @Component public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException { System.out.println("登陆成功"); resp.sendRedirect("/"); } }
建立登陆失败的处理类MyAuthenticationFailureHandler


package com.example.springsecurity.handler; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.authentication.*; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @Author: yushizhong * @Date: 2020/1/7 10:10 * @Title: 验证失败处理 */ @Component public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException { System.out.println("登陆失败"); resp.sendRedirect("/login.html"); } }
启动项目,访问localhost:8080,当登陆成功时控制台会打印登陆成功,并跳转到首页;登陆失败时会打印登陆失败,回到登陆页面。
7.5 使用数据库的方式验证
第一步:建立实体类


package com.example.springsecurity.domain; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; import java.util.List; @Data public class User implements UserDetails { private Integer id; private String username; private String name; private String password; private boolean enabled; private boolean locked; private String role; private List<SimpleGrantedAuthority> authorities = new ArrayList<>(); //获取用户的角色信息 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } //获取用户的密码 @Override public String getPassword() { return password; } //获取用户的用户名 @Override public String getUsername() { return username; } //当前帐户是否未过时 @Override public boolean isAccountNonExpired() { return true; } //当前帐户是否锁定 @Override public boolean isAccountNonLocked() { return !locked; } //当前帐户密码是否未过时 @Override public boolean isCredentialsNonExpired() { return true; } //当前帐户是否可用 @Override public boolean isEnabled() { return enabled; } }


package com.example.springsecurity.domain; import lombok.Data; @Data public class Role { private Integer id; private String role; private String explain; }


package com.example.springsecurity.domain; import lombok.Data; @Data public class Auth { private Integer id; private String auth; private String url; private String permission; }
第二步:建立接口


package com.example.springsecurity.mapper; import com.example.springsecurity.domain.Auth; import com.example.springsecurity.domain.User; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface UserMapper { User loadUserByUsername(String username); List<Auth> findAuthByUsername(String username); }


package com.example.springsecurity.mapper; import org.apache.ibatis.annotations.Mapper; @Mapper public interface RoleMapper { }


package com.example.springsecurity.mapper; import com.example.springsecurity.domain.Auth; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface AuthMapper { List<Auth> findAll(); }
第三步:建立xml


<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.springsecurity.mapper.UserMapper"> <!--查询用户--> <select id="loadUserByUsername" resultType="com.example.springsecurity.domain.User"> select * from user where username=#{username} </select> <!--查询用户的权限--> <select id="findAuthByUsername" resultType="com.example.springsecurity.domain.Auth"> select auth.* from user u inner join user_role on user_role.user_id=u.id inner join role on role.id=user_role.user_id inner join role_auth on role_auth.role_id=role.id inner join auth on auth.id=role_auth.auth_id where u.username=#{username} </select> </mapper>


<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.springsecurity.mapper.RoleMapper"> </mapper>


<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.springsecurity.mapper.AuthMapper"> <!--查询全部的权限--> <select id="findAll" resultType="com.example.springsecurity.domain.Auth"> select * from auth </select> </mapper>
第三步:修改配置类SecurityConfig,md5加密的工具类在此略(可在工具类的博客中查看)


package com.example.springsecurity.config; import com.example.springsecurity.domain.Auth; import com.example.springsecurity.handler.MyAuthenticationFailureHandler; import com.example.springsecurity.handler.MyAuthenticationSuccessHandler; import com.example.springsecurity.mapper.AuthMapper; import com.example.springsecurity.service.UserService; import com.example.springsecurity.util.Md5Utils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; @Component public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 登陆成功的处理 */ @Autowired private MyAuthenticationSuccessHandler successHandler; /** * 登陆失败的处理 */ @Autowired private MyAuthenticationFailureHandler failureHandler; /** * 数据库验证用户信息 */ @Autowired private UserService userService; /** * 查询权限 */ @Autowired private AuthMapper authMapper; //配置加密的方式 @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } //配置认证用户信息和受权 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() { //对输入的密码加密,这里暂时不用 @Override public String encode(CharSequence charSequence) { return null; } //加密密码与传入的密码对比 @Override public boolean matches(CharSequence charSequence, String encodePassword) { //encodePassword是数据库的密码,charSequence是输入的密码 return Md5Utils.md5((String)charSequence).equals(encodePassword); } }); } //配置拦截请求资源 @Override protected void configure(HttpSecurity http) throws Exception { ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http //开启HttpSecurity配置 .authorizeRequests(); //指定路径 //动态配置权限的受权 List<Auth> authList = authMapper.findAll(); for (Auth auth : authList) { authorizeRequests.antMatchers(auth.getUrl()).hasAuthority(auth.getAuth()); } authorizeRequests.antMatchers("/login.html").permitAll() .antMatchers("/**").fullyAuthenticated() //配置认证模式 .and().formLogin() .loginPage("/login.html") .loginProcessingUrl("/login") //登陆成功的操做 .successHandler(successHandler) //登陆失败的操做 .failureHandler(failureHandler) .and() .logout() .logoutUrl("/logout") //清除身份认证信息 .clearAuthentication(true) //设置session失效 .invalidateHttpSession(true) .addLogoutHandler(new LogoutHandler() { @Override public void logout(HttpServletRequest req, HttpServletResponse resp, Authentication auth) {} }) .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth) throws IOException, ServletException { //退出成功后跳转到登陆 resp.sendRedirect("/login.html"); } }) //配置和登陆相关的接口不须要认证 .permitAll() .and() //关闭cors .csrf() .disable(); } }
也可使用默认的加密方式,与md5的配置对比,关键代码以下


//配置加密的方式 @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } //配置认证用户信息和受权 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService); }
第四步:建立UserService类


package com.example.springsecurity.service; import com.example.springsecurity.domain.Auth; import com.example.springsecurity.domain.User; import com.example.springsecurity.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class UserService implements UserDetailsService { @Autowired private UserMapper mapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //根据用户名查询用户的信息 User user=mapper.loadUserByUsername(username); if(user==null){ throw new UsernameNotFoundException("用户不存在"); } List<Auth> authList = mapper.findAuthByUsername(username); //赋予用户权限 if(authList!=null&&authList.size()>0){ List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (Auth auth : authList) { authorities.add(new SimpleGrantedAuthority(auth.getAuth())); } user.setAuthorities(authorities); } //底层会根据数据库来查询用户信息,判断密码是否正确 return user; } }
第五步:这里使用了数据库验证,就能够对用户的登陆信息进行细化,好比登陆失败的缘由。登陆成功的处理和失败的处理配置修改以下:


package com.example.springsecurity.handler; import com.example.springsecurity.domain.User; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * @Author: yushizhong * @Date: 2020/1/7 10:11 * @Title: 登陆成功处理 */ @Component public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth) throws IOException, ServletException { //这里能够进行页面的跳转或返回json数据给客户端浏览器 User principal = (User) auth.getPrincipal();//获取登陆用户的信息 principal.setPassword(null); resp.setContentType("application/json;charset=utf-8"); PrintWriter out=resp.getWriter(); resp.setStatus(200); Map<String,Object> map=new HashMap<>(); map.put("status",200); map.put("msg",principal); ObjectMapper objectMapper = new ObjectMapper(); out.write(objectMapper.writeValueAsString(map)); out.flush();; out.close(); // resp.sendRedirect("/"); } }


package com.example.springsecurity.handler; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.authentication.*; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * @Author: yushizhong * @Date: 2020/1/7 10:10 * @Title: 验证失败处理 */ @Component public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException { resp.setContentType("application/json;charset=utf-8"); PrintWriter out=resp.getWriter(); resp.setStatus(401); Map<String,Object> map=new HashMap<>(); map.put("status",401); if(e instanceof LockedException){ map.put("msg","帐户被锁定,没法登陆"); }else if(e instanceof BadCredentialsException){ map.put("msg","用户名或密码错误"); }else if(e instanceof DisabledException){ map.put("msg","帐户被禁用,没法登陆"); }else if(e instanceof AccountExpiredException){ map.put("msg","帐户已过时,没法登陆"); }else if(e instanceof CredentialsExpiredException){ map.put("msg","密码已过时,没法登陆"); }else{ map.put("msg","登陆异常,请联系管理员"); } ObjectMapper objectMapper = new ObjectMapper(); out.write(objectMapper.writeValueAsString(map)); out.flush();; out.close(); // resp.sendRedirect("/login.html"); } }
有了这些,不只能够返回给用户具体的信息,也能够把这些信息记录到日志中。
第六步:因为这里对密码进行了加密,全部数据库中的密码也须要加密。启动项目进行测试,动态的配置和以前静态的配置的效果同样。
表数据以下,表结构可参考实体类:
*****************user表********************
*****************role表********************
*****************auth表********************
*****************user-role表********************
*****************role-auth表********************
7.6 获取表单额外的参数
首先,在7.5的基础上,往login.html中添加一个输入框,name是identity


<p> <input type="text" name="identify" placeholder="身份"> </p>
接着,建立类MyAuthenticationDetails来获取额外的参数


package com.example.springsecurity.filter; import org.springframework.security.web.authentication.WebAuthenticationDetails; import javax.servlet.http.HttpServletRequest; /** * @Author: yushizhong * @Date: 2020/1/7 16:05 * @Title: 在登陆什获取表单的其余参数,存到session中,方便后面使用 */ public class MyAuthenticationDetails extends WebAuthenticationDetails { private String identify; public MyAuthenticationDetails(HttpServletRequest request) { super(request); identify = request.getParameter("identify"); request.getSession().setAttribute("identify", identify); System.out.println("identify:" + identify); } }
而后,在SecurityConfig类的登陆失败的操做后面添加一行
.authenticationDetailsSource(authenticationDetailsSource)
最后,启动项目,进行测试,输入的额外信息在控制台打印了,对于这个信息能够存入redis,在登陆验证的时候使用。
7.7 自定义图片验证码验证
图片验证码的在页面显示须要调用生成图片验证码的工具类,验证码生成后会先存入redis,在此略,这里只介绍如何进行验证。
首先定义一个图片验证码验证的过滤器ImgCodeFilter


package com.example.springsecurity.filter; import com.example.springsecurity.exception.ImgException; import com.example.springsecurity.handler.MyAuthenticationFailureHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @Author: yushizhong * @Date: 2020/1/9 10:24 * @Title: 数字验证过滤器,可用在图片验证码验证 */ @Component public class ImgCodeFilter extends OncePerRequestFilter { @Autowired MyAuthenticationFailureHandler authenticationFailureHandler; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //从请求中获取请求地址和方式进行判断是不是登陆请求验证图片验证码 if("/login".equals(request.getRequestURI())&&"post".equalsIgnoreCase(request.getMethod())){ try{ verityCode(request); }catch (ImgException e){ authenticationFailureHandler.onAuthenticationFailure(request,response,e); } } doFilter(request,response,filterChain); } //验证图片验证码 public void verityCode(HttpServletRequest request) throws ImgException { //图片验证码的在页面显示须要调用生成图片验证码的工具类,验证码生成后会先存入redis,在此略 //这里的1234是自定义的,在实际开发中是从redis获取 if(!"1234".equals(request.getParameter("code"))){ throw new ImgException("验证码错误"); } } }
定义一个图片验证的异常类


package com.example.springsecurity.exception; import org.springframework.security.core.AuthenticationException; /** * @Author: yushizhong * @Date: 2020/1/9 10:59 * @Title: 验证码异常类 */ public class ImgException extends AuthenticationException { public ImgException(String explanation) { super(explanation); } }
在SecurityConfig配置类中注入过滤器,并把过滤器加入security


*********注入图片验证的过滤器 @Autowired private ImgCodeFilter imgCodeFilter; **********在configure(HttpSecurity http)方法中把过滤器加到security //验证用户名密码以前进行过滤验证 http.addFilterBefore(imgCodeFilter, UsernamePasswordAuthenticationFilter.class);
修改登陆失败处理类,添加一个异常的判断,异常判断的代码以下


if(e instanceof LockedException){ map.put("msg","帐户被锁定,没法登陆"); }else if(e instanceof BadCredentialsException){ map.put("msg","用户名或密码错误"); }else if(e instanceof DisabledException){ map.put("msg","帐户被禁用,没法登陆"); }else if(e instanceof AccountExpiredException){ map.put("msg","帐户已过时,没法登陆"); }else if(e instanceof CredentialsExpiredException){ map.put("msg","密码已过时,没法登陆"); }else if(e instanceof ImgException){ map.put("msg",e.getMessage()); }else{ map.put("msg","登陆异常,请联系管理员"); }
修改登陆页面,表单中加入输入框,name是code


<p> <input type="text" name="code" placeholder="验证码"> </p>
启动项目进行测试,当输入验证码不是1234时会显示验证码错误,验证码就验证成功了。在这里的过滤器中从请求中获取了请求的部分参数,对参数进行处理,这个方法能够借鉴,同上7.6。
7.7 短信登陆开发
1)表单登陆与短信登陆的认证流程图比对
2)建立类SmsCodeAuthenticationToken,对应的是UsernamePasswordAuthenticationToken


package com.example.springsecurity.handler; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; /** * @Author: yushizhong * @Date: 2020/1/9 14:38 * @Title: 短信验证码token,对应UsernamePasswordAuthenticationToken */ public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = 500L; private final Object principal; public SmsCodeAuthenticationToken(Object mobile) { super((Collection)null); this.principal = mobile; this.setAuthenticated(false); } public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; super.setAuthenticated(true); } public Object getCredentials() { return null; } public Object getPrincipal() { return this.principal; } public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { if (isAuthenticated) { throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); } else { super.setAuthenticated(false); } } public void eraseCredentials() { super.eraseCredentials(); } }
3)建立过滤器SmsCodeAuthenticationFilter,对应的是UsernamePasswordAuthenticationFilter


package com.example.springsecurity.filter; import com.example.springsecurity.handler.SmsCodeAuthenticationToken; import org.springframework.lang.Nullable; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Author: yushizhong * @Date: 2020/1/9 14:40 * @Title: 短信验证码认证过滤器,对应UsernamePasswordAuthenticationFilter */ public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile"; private String mobileParameter = "mobile"; private boolean postOnly = true; public SmsCodeAuthenticationFilter() { super(new AntPathRequestMatcher("/mobile", "POST")); } public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { String mobile = this.obtainMobile(request); if (mobile == null) { mobile = ""; } mobile = mobile.trim(); SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile); this.setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } } @Nullable protected String obtainMobile(HttpServletRequest request) { return request.getParameter(this.mobileParameter); } protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) { authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request)); } public void setMobileParameter(String mobileParameter) { Assert.hasText(mobileParameter, "Username parameter must not be empty or null"); this.mobileParameter = mobileParameter; } public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } public final String getMobileParameter() { return this.mobileParameter; } }
4)建立认证器SmsCodeAuthenticationProvider,对应的是DaoAuthenticationProvider


package com.example.springsecurity.handler; import com.example.springsecurity.service.UserService; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; /** * @Author: yushizhong * @Date: 2020/1/9 14:43 * @Title: 短信验证码认证校验器,对应DaoAuthenticationProvider */ public class SmsCodeAuthenticationProvider implements AuthenticationProvider { private UserService userService; public UserService getUserService() { return userService; } public void setUserService(UserService userService) { this.userService = userService; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { SmsCodeAuthenticationToken smsCodeAuthenticationToken = (SmsCodeAuthenticationToken)authentication; UserDetails user = userService.loadUserByUsername((String)smsCodeAuthenticationToken.getPrincipal()); if (user == null) { throw new InternalAuthenticationServiceException("没法获取用户信息"); } //构造认证结果 SmsCodeAuthenticationToken result = new SmsCodeAuthenticationToken(user, user.getAuthorities()); result.setDetails(smsCodeAuthenticationToken.getDetails()); return result; } @Override public boolean supports(Class<?> authentication) { return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication); } }
5)建立短信验证码过滤器SmsCodeFilter,用于验证短信验证码是否正确


package com.example.springsecurity.filter; import com.example.springsecurity.exception.SmsCodeException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashSet; import java.util.Set; /** * @Author: yushizhong * @Date: 2020/1/9 15:00 * @Title: 用于验证短信验证码是否正确 */ @Component public class SmsCodeFilter extends OncePerRequestFilter implements InitializingBean { @Autowired private AuthenticationFailureHandler authenticationFailureHandler; private Set<String> urls = new HashSet<>(); private AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); // 这里配置须要拦截的地址 urls.add("/mobile"); } @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { boolean action = false; //判断请求地址 for (String url : urls) { if (antPathMatcher.match(url, httpServletRequest.getRequestURI())) { action = true; break; } } if (action) { try { validate(httpServletRequest); } catch (SmsCodeException e) { authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e); return; } } filterChain.doFilter(httpServletRequest, httpServletResponse); } private void validate(HttpServletRequest request) { String code= (String) request.getSession().getAttribute("code"); String smsCodeRequest = request.getParameter("smsCode"); if (code == null) { throw new SmsCodeException("短信验证码不存在"); } if(!smsCodeRequest.equalsIgnoreCase(code)) { throw new SmsCodeException("短信验证码错误"); } //清除session // request.getSession().removeAttribute("code"); } }
6)建立配置类SmsCodeAuthenticationSecurityConfig,将短信验证码认证的各个组件组合起来


package com.example.springsecurity.config; import com.example.springsecurity.filter.SmsCodeAuthenticationFilter; import com.example.springsecurity.handler.MyAuthenticationFailureHandler; import com.example.springsecurity.handler.MyAuthenticationSuccessHandler; import com.example.springsecurity.handler.SmsCodeAuthenticationProvider; import com.example.springsecurity.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.stereotype.Component; /** * @Author: yushizhong * @Date: 2020/1/9 14:57 * @Title: 短信验证码认证安全设置,重写configure方法 */ @Component public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Autowired private MyAuthenticationFailureHandler myAuthenticationFailHandler; @Autowired private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler; @Autowired private UserService userService; @Override public void configure(HttpSecurity http) throws Exception { SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter(); smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler); smsCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailHandler); SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider(); smsCodeAuthenticationProvider.setUserService(userService); http.authenticationProvider(smsCodeAuthenticationProvider) .addFilterBefore(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } }
7)建立配置类 SecurityConfig


package com.example.springsecurity.config; import com.example.springsecurity.domain.Auth; import com.example.springsecurity.filter.ImgCodeFilter; import com.example.springsecurity.filter.SmsCodeFilter; import com.example.springsecurity.handler.MyAuthenticationFailureHandler; import com.example.springsecurity.handler.MyAuthenticationSuccessHandler; import com.example.springsecurity.mapper.AuthMapper; import com.example.springsecurity.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 登陆成功的处理 */ @Autowired private MyAuthenticationSuccessHandler successHandler; /** * 登陆失败的处理 */ @Autowired private MyAuthenticationFailureHandler failureHandler; /** * 数据库验证用户信息 */ @Autowired private UserService userService; @Autowired private SmsCodeFilter smsCodeFilter; @Autowired private SmsCodeAuthenticationSecurityConfig codeAuthenticationSecurityConfig; //配置加密的方式 @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } //配置认证用户信息和受权 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService); } //配置拦截请求资源 @Override protected void configure(HttpSecurity http) throws Exception { http //开启HttpSecurity配置 .authorizeRequests() //指定路径 .antMatchers("/login.html").permitAll() .antMatchers("/sendCode").permitAll() .antMatchers("/mobile").permitAll() .antMatchers("/**").fullyAuthenticated() //配置认证模式 .and().formLogin() .loginPage("/login.html") .loginProcessingUrl("/mobile") //登陆成功的操做 .successHandler(successHandler) //登陆失败的操做 .failureHandler(failureHandler) .and() .logout() .logoutUrl("/logout") //清除身份认证信息 .clearAuthentication(true) //设置session失效 .invalidateHttpSession(true) .addLogoutHandler(new LogoutHandler() { @Override public void logout(HttpServletRequest req, HttpServletResponse resp, Authentication auth) { } }) .logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth) throws IOException, ServletException { //退出成功后跳转到登陆 resp.sendRedirect("/login.html"); } }) //配置和登陆相关的接口不须要认证 .permitAll() .and() //关闭cors .csrf() .disable(); //加载本身的配置 http.apply(codeAuthenticationSecurityConfig); http.addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class); } }
8)建立登陆成功和失败的处理类、异常类


package com.example.springsecurity.handler; import com.example.springsecurity.domain.User; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * @Author: yushizhong * @Date: 2020/1/7 10:11 * @Title: 登陆成功处理 */ @Component public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth) throws IOException, ServletException { //这里能够进行页面的跳转或返回json数据给客户端浏览器 User principal = (User) auth.getPrincipal();//获取登陆用户的信息 principal.setPassword(null); resp.setContentType("application/json;charset=utf-8"); PrintWriter out=resp.getWriter(); resp.setStatus(200); Map<String,Object> map=new HashMap<>(); map.put("status",200); map.put("msg",principal); ObjectMapper objectMapper = new ObjectMapper(); out.write(objectMapper.writeValueAsString(map)); out.flush();; out.close(); // resp.sendRedirect("/"); } }


package com.example.springsecurity.handler; import com.example.springsecurity.exception.ImgException; import com.example.springsecurity.exception.SmsCodeException; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.authentication.*; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * @Author: yushizhong * @Date: 2020/1/7 10:10 * @Title: 验证失败处理 */ @Component public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException { resp.setContentType("application/json;charset=utf-8"); PrintWriter out=resp.getWriter(); resp.setStatus(401); Map<String,Object> map=new HashMap<>(); map.put("status",401); if(e instanceof LockedException){ map.put("msg","帐户被锁定,没法登陆"); }else if(e instanceof BadCredentialsException){ map.put("msg","用户名或密码错误"); }else if(e instanceof DisabledException){ map.put("msg","帐户被禁用,没法登陆"); }else if(e instanceof AccountExpiredException){ map.put("msg","帐户已过时,没法登陆"); }else if(e instanceof CredentialsExpiredException){ map.put("msg","密码已过时,没法登陆"); }else if(e instanceof SmsCodeException){ map.put("msg",e.getMessage()); }else{ map.put("msg","登陆异常,请联系管理员"); } ObjectMapper objectMapper = new ObjectMapper(); out.write(objectMapper.writeValueAsString(map)); out.flush();; out.close(); // resp.sendRedirect("/login.html"); } }


package com.example.springsecurity.exception; import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; /** * @Author: yushizhong * @Date: 2020/1/9 15:03 * @Title: 短信验证码异常类 */ public class SmsCodeException extends AuthenticationException { public SmsCodeException(String msg) { super(msg); } }
9)建立登陆页面login.html


<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登陆</title> </head> <body> <h3>手机号登陆</h3> <form method="post" action="/mobile"> <p> <input type="text" name="mobile" placeholder="手机号" value="15623637827"> </p> <a href="/sendCode?mobile=15623637827">发送</a> <p> <input type="text" name="smsCode" placeholder="短信验证码" > </p> <button type="submit">登陆</button> </form> </body> </html>
10)建立短信发送的接口,这里的短信发送是模拟是,实际开发中改成短信发送便可。


package com.example.springsecurity.controller; import com.example.springsecurity.util.CodeUtil; import com.example.springsecurity.util.SendSms; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; @Controller public class UserController { @RequestMapping("/sendCode") public void sendCode(HttpServletRequest request,String mobile){ String code = CodeUtil.getCode(6); System.out.println("验证码:"+code); // SendSms.sendMsg(mobile,code); request.getSession().setAttribute("code",code); } }
11)建立User类


package com.example.springsecurity.domain; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; import java.util.List; @Data public class User implements UserDetails { private Integer id; private String username; private String name; private String password; private boolean enabled; private boolean locked; private String role; private String mobile; private List<SimpleGrantedAuthority> authorities = new ArrayList<>(); //获取用户的角色信息 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } //获取用户的密码 @Override public String getPassword() { return password; } //获取用户的用户名 @Override public String getUsername() { return username; } //当前帐户是否未过时 @Override public boolean isAccountNonExpired() { return true; } //当前帐户是否锁定 @Override public boolean isAccountNonLocked() { return !locked; } //当前帐户密码是否未过时 @Override public boolean isCredentialsNonExpired() { return true; } //当前帐户是否可用 @Override public boolean isEnabled() { return enabled; } }
12)建立UserService类,重写验证的方法loadUserByUsername。Usermapper和UserMapper.xml的代码也附加在下面。


package com.example.springsecurity.service; import com.example.springsecurity.domain.Auth; import com.example.springsecurity.domain.User; import com.example.springsecurity.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class UserService implements UserDetailsService { @Autowired private UserMapper mapper; @Override public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException { User user=mapper.loadUserByMobile(mobile); if(user==null){ throw new UsernameNotFoundException("用户不存在"); } return user; } }


package com.example.springsecurity.mapper; import com.example.springsecurity.domain.Auth; import com.example.springsecurity.domain.User; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface UserMapper { User loadUserByMobile(String mobile); }


<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.springsecurity.mapper.UserMapper"> <select id="loadUserByMobile" resultType="com.example.springsecurity.domain.User"> select * from user where mobile=#{mobile} </select> </mapper>
数据库中的表和数据参考User类填写便可。启动项目,点击发送按钮,在控制台复制验证码,进行登陆测试。
8.SpringBoot整合WebSocket
8.1 WebSocket介绍和原理
WebSocket协议是一种全双工协议,服务端能够主动向客户端推送消息,能够是文本也能够是二进制数据,并且没有同源策略的限制,不存在跨域问题。这里主要介绍服务端向客户端推送消息。
第一步:导入依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>sockjs-client</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>stomp-websocket</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.3.1</version> </dependency>
第二步:建立WebSocket进行消息的推送


package com.example.websocket.controller; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.web.socket.server.standard.ServerEndpointExporter; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; /** * @Author: yushizhong * @Date: 2020/1/10 15:42 * @Title: 描述 */ @Component @ServerEndpoint(value = "/ws/myWebSocket") public class WebSocket { //每一个客户端都会有相应的session,服务端能够发送相关消息 private Session session; //J.U.C包下线程安全的类,主要用来存放每一个客户端对应的webSocket链接,为何说他线程安全。在文末作简单介绍 private static CopyOnWriteArraySet<WebSocket> copyOnWriteArraySet = new CopyOnWriteArraySet<WebSocket>(); @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } /** * 打开链接。进入页面后会自动发请求到此进行链接 * @param session */ @OnOpen public void onOpen(Session session) { this.session = session; copyOnWriteArraySet.add(this); System.out.println("websocket有新的链接, 总数:"+ copyOnWriteArraySet.size()); } /** * 用户关闭页面,即关闭链接 */ @OnClose public void onClose() { copyOnWriteArraySet.remove(this); System.out.println("websocket链接断开, 总数:"+ copyOnWriteArraySet.size()); } /** * 测试客户端发送消息,测试是否联通 * @param message */ @OnMessage public void onMessage(String message) { System.out.println("websocket收到客户端发来的消息:"+message); sendMessage(message); } /** * 出现错误 * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { System.out.println("发生错误:" + error.getMessage()+session.getId()); error.printStackTrace(); } /** * 用于发送给客户端消息(群发) * @param message */ public void sendMessage(String message) { //遍历客户端 for (WebSocket webSocket : copyOnWriteArraySet) { System.out.println("websocket广播消息:" + message); try { //服务器主动推送 webSocket.session.getBasicRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } /** * 用于发送给指定客户端消息, * * @param message */ public void sendMessage(String sessionId, String message) throws IOException { Session session = null; WebSocket tempWebSocket = null; for (WebSocket webSocket : copyOnWriteArraySet) { if (webSocket.session.getId().equals(sessionId)) { tempWebSocket = webSocket; session = webSocket.session; break; } } if (session != null) { tempWebSocket.session.getBasicRemote().sendText(message); } else { System.out.println("没有找到你指定ID的会话:{}"+sessionId); } } }
第三步:建立页面chat.html,导入相关的js文件


<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>websocket</title> <script src="stomp.min.js"></script> <script src="sockjs.min.js"></script> </head> <body> <h2>测试链接websocket</h2> <p> 链接url: <input type="text" value="ws://localhost:8080/ws/myWebSocket" id="url"> <button onclick="openWeb()">打开链接</button> </p> <p> 发送信息内容: <input type="text" id="message"><button onclick="send()">发送信息</button> </p> <hr> <p>消息区域</p> <p id="show"></p> </body> <script type="text/javascript"> var url="";//socket所须要的地址 var socket;//socket对象 function openWeb(){ createWebSocket(document.getElementById("url").value) } //建立WebSocket链接 function createWebSocket(url){ if ('WebSocket' in window) { socket = new WebSocket(url); } else { socket = new SockJS(url); } //链接打开事件 socket.onopen = function() { console.log("Socket已链接到"+url); }; //收到服务器消息后响应 socket.onmessage = function(e) { console.log("收到服务端消息:"+e.data) document.getElementById("show").innerHTML+="<br>"+e.data }; //链接关闭事件 socket.onclose = function() { console.log("Socket已关闭链接"); }; //发生了错误事件 socket.onerror = function() { console.log("Socket发生了错误"); } //窗口关闭时,关闭链接 window.unload=function() { socket.close(); }; } function send(){ socket.send(document.getElementById("message").value) } </script> </html>
第四步:测试
启动项目,在浏览器输入localhost:8080/chat.html,而后点击链接wensocket进行链接,接着输入要发送的信息,点击发送就能够在下面看到发送的信息。
9.邮件发送
这里使用QQ邮箱进行发送邮件,因此要先开启POP3/SMTP服务或IMAP/SMTP服务,登录网页版QQ邮箱,在设置中找到帐户,在下面开启服务便可。
邮件发送使用SpringBoot的环境,因此下面在SpringBoot的环境中演示。
9.1准备工做
第一步:导入依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
第二步:配置邮件的基本信息


spring: mail: #邮件服务器的地址 host: smtp.qq.com #邮件服务器的端口号,465或587 port: 465 #用户帐号 username: 1916008067@qq.com #用户密码,这个密码是开启服务后系统显示的密码 password: zysferigqzobxqkfcej default-encoding: UTF-8 properties: mail: #配置SSL链接 smtp: socketFactory.class: javax.net.ssl.SSLSocketFactory #开启dubug debug: true
9.2简单邮件
第一步:建立一个MailService类


package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.io.File; @Component public class MailService { @Autowired JavaMailSender javaMailSender; //简单邮件 public void sendSimpleMail(String sender,String recipient,String cc,String title,String content){ SimpleMailMessage message=new SimpleMailMessage(); message.setFrom(sender);//发送者 message.setTo(recipient);//收件人 message.setCc(cc);//抄送人 message.setSubject(title);//邮件主题 message.setText(content);//邮件内容 javaMailSender.send(message); } }
第二步:在测试目录建立一个测试类Demo2ApplicationTests


package com.example.demo; import freemarker.template.Configuration; import freemarker.template.Template; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import java.io.File; import java.io.StringWriter; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest class Demo2ApplicationTests { @Autowired private MailService mailService; @Test public void sendSimpleMail(){ mailService.sendSimpleMail( "1916008067@qq.com","1953090026@qq.com", "1953090026@qq.com","通知","今天加班"); } }
第三步:运行测试方法,便可在收件人的邮箱看到邮件的信息。
9.3带附件的邮件
第一步:在MailService中添加一个方法


//带附件的邮件 public void sendAttachFileMail(String sender, String recipient, String cc, String title, String content, File file) { try { MimeMessage message = javaMailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(sender);//发送者 helper.setTo(recipient);//收件人 helper.setCc(cc);//抄送人 helper.setSubject(title);//邮件主题 helper.setText(content);//邮件内容 helper.addAttachment(file.getName(),file);//文件 javaMailSender.send(message); } catch (MessagingException e) { e.printStackTrace(); } }
第二步:在测试类中添加方法


@Test public void sendAttachFileMail(){ mailService.sendAttachFileMail( "1916008067@qq.com","1953090026@qq.com", "1953090026@qq.com","通知","今天加班", new File("D:\\layui-v2.5.5\\layui\\layui.js")); }
第三步:运行测试方法,便可在收件人的邮箱看到邮件的信息。
9.4带图片的附件
第一步:在MailService中添加一个方法


//带图片的邮件 public void sendMailWithImage(String sender, String recipient, String cc,String title, String content, String[] srcPath,String[] resId){ if(srcPath.length!=resId.length){ System.out.println("发送失败"); return; } try{ MimeMessage message = javaMailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(sender);//发送者 helper.setTo(recipient);//收件人 helper.setCc(cc);//抄送人 helper.setSubject(title);//邮件主题 helper.setText(content,true);//邮件内容,设置为true说明正文是html格式 for (int i = 0; i <srcPath.length ; i++) { FileSystemResource res=new FileSystemResource(new File(srcPath[i])); helper.addInline(resId[i],res); } javaMailSender.send(message); }catch (MessagingException e){ e.printStackTrace(); System.out.println("发送失败"); } }
第二步:在测试类中添加方法


@Test public void sendMailWithImage(){ mailService.sendMailWithImage( "1916008067@qq.com","1953090026@qq.com", "1953090026@qq.com","带图片的邮件", "<div><img src='cid:p01'/><img src='cid:p02'/></div>", new String[]{"D:\\1.png","D:\\2.png"},new String[]{"p01","p02"}); }
第三步:运行测试方法,便可在收件人的邮箱看到邮件的信息。
9.5使用Freemarker模板的邮件
第一步:导入依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
第二步:在MailService中添加一个方法


//模板邮件 public void sendHtmlMail(String sender, String recipient, String cc, String title, String content) { try { MimeMessage message = javaMailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(sender);//发送者 helper.setTo(recipient);//收件人 helper.setCc(cc);//抄送人 helper.setSubject(title);//邮件主题 helper.setText(content,true);//邮件内容 javaMailSender.send(message); } catch (MessagingException e) { e.printStackTrace(); } }
第三步:建立邮件模板
在资源目录下的static目录下新建一个名字为mailtemplate.ftl的模板文件


<div>邮箱激活</div> <div>你的注册信息 <table> <tr> <td>用户名</td> <td>${username}</td> </tr> <tr> <td>用户性别</td> <td>${sex}</td> </tr> </table> <div><a href="http://www.baidu.com">点击激活邮箱</a></div> </div>
第四步:建立实体类User


package com.example.demo; public class User { private String username; private String sex; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
第五步:在测试类中添加方法


@Test public void sendHtmlMail() { try { Configuration configuration = new Configuration(Configuration.VERSION_2_3_0); ClassLoader loader = Demo2Application.class.getClassLoader(); configuration.setClassLoaderForTemplateLoading(loader,"static"); Template template = configuration.getTemplate("mailtemplate.ftl"); StringWriter writer = new StringWriter(); User user=new User(); user.setSex("男"); user.setUsername("张三哈哈哈"); template.process(user,writer); mailService.sendHtmlMail( "1916008067@qq.com","1953090026@qq.com", "1953090026@qq.com","模板文件",writer.toString() ); } catch (Exception e) { e.printStackTrace(); } }
第六步:运行测试方法,便可在收件人的邮箱看到邮件的信息。
9.6使用Thymeleaf模板的邮件
第一步:导入依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
第二步:建立模板文件
在资源目录的templates目录下新建一个名为mailtemplate.html的模板文件


<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.com"> <head> <meta charset="UTF-8"> <title>邮件</title> </head> <body> <div>邮箱激活</div> <div>你的注册信息 <table> <tr> <td>用户名</td> <td th:text="${username}"></td> </tr> <tr> <td>用户性别</td> <td th:text="${sex}"></td> </tr> </table> <div><a href="http://www.baidu.com">点击激活邮箱</a></div> </div> </body> </html>
第三步:在测试类中添加方法
这里没有在MailService类中添加方法,而是直接使用了9.5里面新建的方法。


@Autowired private TemplateEngine templateEngine; @Test public void sendHtmlMail2(){ Context context = new Context(); context.setVariable("username","赵柳"); context.setVariable("sex","女"); String mail=templateEngine.process("mailtemplate.html",context); mailService.sendHtmlMail( "1916008067@qq.com","1953090026@qq.com", "1953090026@qq.com","模板文件2",mail ); }
第四步:运行测试方法,便可在收件人的邮箱看到邮件的信息。
10.定时任务
10.1使用@Schedule
SpringBoot中自带有定时任务,因此用法很简单。
第一步:在启动类上面开启定时任务的注解
@SpringBootApplication @EnableScheduling//开启定时任务 public class Demo2Application { public static void main(String[] args) { SpringApplication.run(Demo2Application.class, args); } }
第二步:配置定时任务。
1)fixedDelay
表示这个方法上一次执行完成后再过多少秒后再次执行,或者此次执行完毕后,距离下次执行须要多少秒。
建立两个定时任务,以下


package com.example.demo; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; @Component public class MyScheduleTask1 { SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // fixedDelay:上一次执行完毕以后再过6秒再执行,或者此次执行完毕后,距离下次执行须要6s @Scheduled(fixedDelay = 6000) public void fixedDelay(){ System.out.println("我是定时任务1,我6s后被再次执行,如今时间"+sdf.format(new Date())); } // fixedDelay:此次执行完毕后,距离下次执行须要10s @Scheduled(fixedDelay = 10000) public void fixedDelay2(){ System.out.println("我是定时任务2,我10s后被再次执行,如今时间"+sdf.format(new Date())); } }
运行结果以下
结果分析:建立了两个定时任务,首先fixedDelay的任务在首次启动时都会先执行一次,而后定时任务1执行完成后再过6秒再次执行,定时任务2执行完成后再过10秒再次执行,就这样一直循环执行。
2)fixedRate
上一次开始执行后每隔多少秒执行一次,可能再次执行时这个方法的上一次执行尚未完毕,代码以下


// fixedDelay:上一次执行完毕以后再过6秒再执行,或者此次执行完毕后,距离下次执行须要6s @Scheduled(fixedDelay = 6000) public void fixedDelay(){ System.out.println("我是定时任务1,我6s后被再次执行,如今时间"+sdf.format(new Date())); } // fixedRate:上一次开始执行后每隔8秒执行一次,可能再次执行时这个方法的上一次执行尚未完毕。 @Scheduled(fixedRate = 8000) public void fixedRate(){ System.out.println("我是定时任务2,我8s后被再次执行,如今时间"+sdf.format(new Date())); }
运行结果以下
结果分析:建立了两个定时任务,首先fixedDelay和fixedRate的任务在首次启动时都会先执行一次,而后定时任务1执行完成后再过6秒再次执行,定时任务2首次执行后每隔8秒再次执行,此时这个方法可能尚未执行完毕,就这样一直循环执行。
3)initialDelay
在首次启动后延迟多少秒去执行,后面根据fixedDelay区执行,代码以下


// fixedDelay:上一次执行完毕以后再过6秒再执行,或者此次执行完毕后,距离下次执行须要6s @Scheduled(fixedDelay = 6000) public void fixedDelay(){ System.out.println("我是定时任务1,我6s后被再次执行,如今时间"+sdf.format(new Date())); } // initialDelay:在首次启动后延迟5s去执行,后面根据fixedDelay区执行 int i=1; @Scheduled(initialDelay = 5000,fixedDelay = 10000) public void initialDelay(){ if(i==1){ System.out.println("我是定时任务2,我首次启动延迟5秒执行,如今时间"+sdf.format(new Date())); i++; }else{ System.out.println("我是定时任务2,我不是首次启动,我执行完成后再过10秒执行,如今时间"+sdf.format(new Date())); } }
运行结果以下
结果分析:建立了两个定时任务,首先fixedDelay的任务在首次启动时会先执行一次,而后定时任务1执行完成后再过6秒再次执行,定时任务2首次启动延迟5秒执行,而后执行完成后过10秒再次执行,就这样一直循环执行。
4)cron
隔某一段时间或在某个时间段触发,这个能够设置较长的时间,如一天触发一次、每月15号触发等。
具体参数以下
完整字段:秒 分 小 时 日 月 周 年。注意中间用空格隔开。
字段 | 容许值 | 容许的字符 | 解释 |
秒 | 0-59 | , - * / | *表示全部值,在分钟里表示每一分钟触发。在小时,日期,月份等里面表示每一小时,每一日,每一月。 |
分 | 0-59 | , - * / | ?表示不指定值。表示不关心当前位置设置的值。 好比不关心是周几,则周的位置填写?。 主要是因为日期跟周是有重复的因此二者必须有一者设置为? |
小时 | 0-23 | , - * / | - 表示区间。小时设置为10-12表示10,11,12点均会触发。 |
日 | 1-31 | , - * ? / L W C | ,表示多个值。 小时设置成10,12表示10点和12点会触发。 |
月 | 1-7或SUN-SAT | , - * / | / 表示递增触发。 5/15表示从第5秒开始,每隔15秒触发。 |
周 | 1-7或SUN-SAT | , - * ? / L C # | L 表示最后的意思。 日上表示最后一天。星期上表示星期六或7。 L前加数据,表示该数据的最后一个。 星期上设置6L表示最后一个星期五。 6表示星期五 |
年 | 不写或1970-2099 | , - * / | W表示离指定日期最近的工做日触发。15W离该月15号最近的工做日触发。 #表示每个月的第几个周几。 6#3表示该月的第三个周五。 |
示例:


"0 0 12 * * ?" 天天中午12点触发 "0 15 10 ? * *" 天天上午10:15触发 "0 15 10 * * ?" 天天上午10:15触发 "0 15 10 * * ? *" 天天上午10:15触发 "0 15 10 * * ? 2005" 2005年的天天上午10:15触发 "0 * 14 * * ?" 在天天下午2点到下午2:59期间的每1分钟触发 "0 0/5 14 * * ?" 在天天下午2点到下午2:55期间的每5分钟触发 "0 0/5 14,18 * * ?" 在天天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 "0 0-5 14 * * ?" 在天天下午2点到下午2:05期间的每1分钟触发 "0 10,44 14 ? 3 WED" 每一年三月的星期三的下午2:10和2:44触发 "0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发 "0 15 10 15 * ?" 每个月15日上午10:15触发 "0 15 10 L * ?" 每个月最后一日的上午10:15触发 "0 15 10 ? * 6L" 每个月的最后一个星期五上午10:15触发 "0 15 10 ? * 6L 2002-2005" 2002年至2005年的每个月的最后一个星期五上午10:15触发 "0 15 10 ? * 6#3" 每个月的第三个星期五上午10:15触发 天天早上6点 0 6 * * * 每两个小时 0 */2 * * * 晚上11点到早上8点之间每两个小时,早上八点 0 23-7/2,8 * * * 每月的4号和每一个礼拜的礼拜一到礼拜三的早上11点 0 11 4 * 1-3 1月1日早上4点 0 4 1 1 *
下面的定时任务是每一分钟触发一次


//cron:在某一段时间后触发,这个能够设置较长的时间,如一天、一年等 @Scheduled(cron = "0 * * * * ?") public void cron(){ System.out.println("我是定时任务,我每一分钟都触发,如今时间"+sdf.format(new Date())); }
运行结果以下
10.2使用Quartz
Quartz能够建立简单或复杂的执行计划,支持数据库、集群、邮件等。使用以下:
第一步:导入依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
第二步:建立两个任务


package com.example.demo; import org.springframework.stereotype.Component; import java.text.SimpleDateFormat; import java.util.Date; @Component public class MyFirstJob { //第一个任务 SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public void sayHello(){ System.out.println("MyfirstJob:sayHello now "+sdf.format(new Date())); } }


package com.example.demo; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.scheduling.quartz.QuartzJobBean; import java.text.SimpleDateFormat; import java.util.Date; public class MySecondJob extends QuartzJobBean { //第二个任务 SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private String name; public void setName(String name) { this.name = name; } @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("hello:"+name+",如今的时间是"+sdf.format(new Date())); } }
第三步:建立配置类QuratzConfig


package com.example.demo; import org.quartz.CronTrigger; import org.quartz.JobDataMap; import org.quartz.SimpleTrigger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.*; @Configuration public class QuartzConfig { //jobDetail配置的第一种方式 @Bean public MethodInvokingJobDetailFactoryBean jobDetail(){ MethodInvokingJobDetailFactoryBean bean=new MethodInvokingJobDetailFactoryBean(); bean.setTargetBeanName("myFirstJob");//指定job的实例名 bean.setTargetMethod("sayHello");//指定要调用的方法 return bean; } //jobDetail配置的第二种方式 @Bean public JobDetailFactoryBean jobDetail2(){ JobDetailFactoryBean bean = new JobDetailFactoryBean(); bean.setJobClass(MySecondJob.class); JobDataMap map=new JobDataMap(); map.put("name","嘻嘻嘻");//传递参数 bean.setJobDataMap(map); return bean; } //trigger的第一种实现方式 @Bean public SimpleTriggerFactoryBean simpleTrigger(){ SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean(); bean.setJobDetail(jobDetail().getObject()); bean.setRepeatCount(5);//任务循环次数 bean.setStartDelay(6000);//任务启动延迟时间 bean.setRepeatInterval(3000);//任务时间间隔 return bean; } //trigger的第二种实现方式 @Bean public CronTriggerFactoryBean cronTrigger(){ CronTriggerFactoryBean bean=new CronTriggerFactoryBean(); bean.setJobDetail(jobDetail2().getObject()); bean.setCronExpression("* * * * * ?");//配置cron的表达式 return bean; } @Bean public SchedulerFactoryBean schedulerFactory(){ SchedulerFactoryBean bean=new SchedulerFactoryBean(); SimpleTrigger simpleTrigger = simpleTrigger().getObject(); CronTrigger cronTrigger= cronTrigger().getObject(); bean.setTriggers(simpleTrigger,cronTrigger); return bean; } }
第四步:测试运行。
运行结果是,MyFirstJob在启动后延迟6秒执行,而后每隔3秒再重复执行5次就不执行了,而MySecondJob在启动的时候就开始循环执行,每一秒执行一次。
11.批处理
Spring Batch是一个开源的、全面的、轻量级的批处理框架。
Spring Boot整合Spring Batch的步骤以下
第一步:导入相关的依赖,这里使用jdbc来插入数据


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
第二步:配置数据库基本信息


#数据源配置 spring.datasource.type: com.alibaba.druid.pool.DruidDataSource spring.datasource.username: root spring.datasource.password: root spring.datasource.url: jdbc:mysql://localhost:3306/batch?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC #项目启动时建立数据库的sql脚本 spring.datasource.schema=classpath:/org/springframework/batch/core/schema-mysql.sql #项目启动时执行建表sql spring.batch.initialize-schema=always #禁止Spring Batch自动执行 spring.batch.job.enabled=false
第三步:在启动类加注解开启Spring Batch支持
@SpringBootApplication @EnableBatchProcessing//开启Spring Batch支持 public class Demo2Application { public static void main(String[] args) { SpringApplication.run(Demo2Application.class, args); } }
第四步:建立实体类User


package com.example.demo; public class User { private Integer id; private String username; private String address; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }
第五步:在资源目录下建立data.csv文件,内容以下


id username address 1 张三 安徽 2 李四 武汉 3 赵武 十堰 4 李刘 河南 5 鹏程 黄石
第六步:建立数据库和表
建立一个数据库batch,而后在里面建立user表。
第七步:建立批处理配置类


package com.example.demo; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider; import org.springframework.batch.item.database.JdbcBatchItemWriter; import org.springframework.batch.item.file.FlatFileItemReader; import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper; import org.springframework.batch.item.file.mapping.DefaultLineMapper; import org.springframework.batch.item.file.mapping.FieldSetMapper; import org.springframework.batch.item.file.transform.DelimitedLineTokenizer; import org.springframework.batch.item.file.transform.LineTokenizer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.PathResource; import javax.sql.DataSource; @Configuration public class CsvBatchJobConfig { @Autowired private JobBuilderFactory jobBuilderFactory; @Autowired private StepBuilderFactory stepBuilderFactory; @Autowired private DataSource dataSource; @Bean @StepScope public FlatFileItemReader<User> itemReader(){ FlatFileItemReader<User> reader=new FlatFileItemReader<>();//加载文件 reader.setLinesToSkip(1);//跳过data.csv的第一行 reader.setResource(new ClassPathResource("data.csv"));//配置data.csv的位置 reader.setLineMapper(new DefaultLineMapper<User>(){{ setLineTokenizer(new DelimitedLineTokenizer(){{ setNames("id","username","address"); setDelimiter("\t");//列之间的分隔符 }}); setFieldSetMapper(new BeanWrapperFieldSetMapper<User>(){{ setTargetType(User.class); }}); }}); return reader; } @Bean public JdbcBatchItemWriter jdbcBatchItemWriter(){ JdbcBatchItemWriter writer=new JdbcBatchItemWriter();//将数据写到数据库中 writer.setDataSource(dataSource); writer.setSql("insert into user(id,username,address) values(:id,:username,:address)");//占位符格式 :属性名 writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider());//属性和占位符映射 return writer; } @Bean public Step csvStep(){ //2表示每读取两行就执行一次write操做 return stepBuilderFactory.get("csvStep").<User,User>chunk(2) .reader(itemReader()).writer(jdbcBatchItemWriter()).build(); } @Bean public Job csvJob(){ return jobBuilderFactory.get("csvJob").start(csvStep()).build(); } }
第八步:建立HelloController类


package com.example.demo; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.core.JobParametersInvalidException; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRestartException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @Autowired private JobLauncher jobLauncher; @Autowired private Job job; @GetMapping("/hello") public void hello(){ try { jobLauncher.run(job,new JobParametersBuilder().toJobParameters()); } catch (Exception e) { e.printStackTrace(); } } }
第九步:测试。
启动项目,在浏览器输入localgost:8080/hello,会发现数据库中多了好几张表,这些是用来记录批处理的执行状态的表。同时data.csv的数据也插入到了user表中。
12.Swagger2
Swagger是测试文档Api接口,方便团队之间的交流。Swagger2整合Spring Boot步骤以下:
第一步:导入依赖


<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
第二步:建立配置类


package com.example.demo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2//开启Swagger2 public class SwaggerConfig { @Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2).select() //配置apis要扫描的controlelr的位置 .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller")) //配置路径 .paths(PathSelectors.any()) //构建文件的基本信息 .build().apiInfo( new ApiInfoBuilder().description("微微一笑接口测试文档") .contact(new Contact("哈哈哈","https://github.com.lenve","1916008067@qq.com")) .version("v1.1.0") .title("API测试文档") .license("Apache2.0") .licenseUrl("http://www.apache.org.licenses/LICENSE-2.0") .build()); } }
第三步:建立开发接口


package com.example.demo.controller; import com.example.demo.User; import io.swagger.annotations.*; import org.springframework.web.bind.annotation.*; import springfox.documentation.annotations.ApiIgnore; @RestController @Api(tags = "用户接口")//描述UserController的信息 public class UserController { @ApiOperation(value = "查询用户",notes = "根据id查询用户") @ApiImplicitParam(paramType = "path",name="id",value = "用户id",required = true) @GetMapping("/user/query/{id}") public String getUserById(@PathVariable Integer id) { return "/user/"+id; } @ApiResponses({ @ApiResponse(code=200,message="删除成功"), @ApiResponse(code=500,message="删除失败")}) @ApiOperation(value = "删除用户",notes = "根据id删除用户") @DeleteMapping("/user/delete/{id}") public Integer deleteUserById(@PathVariable Integer id) { return id; } @ApiOperation(value = "添加用户",notes = "添加一个用户,传入用户名和性别") @ApiImplicitParams({ @ApiImplicitParam(paramType = "query",name="username",value = "用户名",required = true,defaultValue = "张三"), @ApiImplicitParam(paramType = "query",name="sex",value = "性别",required = true,defaultValue = "女") }) @PostMapping("/user") public String addUser(@RequestParam String username,@RequestParam String sex){ return username+","+sex; } @ApiOperation(value="修改用户",notes = "根据传入的用户信息修改用户") @PutMapping("/user") public String updateUser(@RequestBody User user){ return user.toString(); } @GetMapping("/ignore") @ApiIgnore public void ignoreMethod(){} }
第四步:测试。
启动项目,在浏览器输入http://localhost:8080/swagger-ui.html就能够看到接口的信息,展开接口,就能看到全部的接口详细信息。
说明:
@ApiOperation 描述方法的基本信息,value对方法简短描述,note对方法作备注
@ApiImplicitParam 描述方法的参数,paramType是参数类型,用path的获取方式是@PathVariable,query的获取方式是@RequestParam,header的获取方式是@RequestHeader、body、form;name表示参数名称,value是参数的描述,required是否必填,defaultValue是默认值;若是是多个参数,可使用@ApiImplicitParams,里面放多个@ApiImplicitParam
@ApiResponses是响应结果,code是响应码,message是描述信息,@ApiResponses同上
@ApiIgnore 忽略某个接口文档
13.数据效验
除了前端要进行数据校验,后端也可能须要校验,与springboot整合步骤下面分别介绍。
1)普通校验
第一步:导入依赖


<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
第二步:在资源根目录下建立配置文件ValidationMessages.properties


user.name.size=用户名长度在5到10之间 user.address.notnull=用户地址不能为空 user.age.size=年龄输入不正确 user.email.notnull=邮箱不能为空 user.emali.pattern=邮箱格式不正确
第三步:建立实体类User


package com.example.demo; import javax.validation.constraints.*; public class User { @Size(min=5,max=10,message = "{user.name.size}") private String name; @NotNull(message = "{user.address.notnull}") private String address; @DecimalMin(value = "1",message = "{user.age.size}") @DecimalMax(value = "200",message = "{user.age.size}") private int age; @Email(message = "{user.email.pattern}") @NotNull(message = "{user.email.notnull}") private String email; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
第四步:建立类UserController


package com.example.demo.controller; import com.example.demo.User; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController public class UserController { @RequestMapping("/user") public List<String> addUser(@Validated User user, BindingResult result){ //对user作数据校验,result保存出错信息 List<String> errors=new ArrayList<>(); if(result.hasErrors()){ List<ObjectError> allErrors = result.getAllErrors(); for (ObjectError error : allErrors) { errors.add(error.getDefaultMessage()); } } return errors; } }
第五步:测试。
启动项目,在浏览器输入localhost:8080/user,后面能够本身拼接参数进行测试,如?name=1.
2)分组校验
第一步:在上面前两步的基础上,建立两个接口ValidationGroup1和2


package com.example.demo; public interface ValidationGroup1 { } package com.example.demo; public interface ValidationGroup2 { }
第二步:修改User类


//不一样的属性指定不一样的分组来进行不一样的验证 @Size(min=5,max=10,message = "{user.name.size}",groups = ValidationGroup1.class) private String name; @NotNull(message = "{user.address.notnull}",groups = ValidationGroup2.class) private String address; @DecimalMin(value = "1",message = "{user.age.size}") @DecimalMax(value = "200",message = "{user.age.size}") private int age; @Email(message = "{user.email.pattern}") @NotNull(message = "{user.email.notnull}",groups ={ValidationGroup1.class,ValidationGroup2.class,} )
第三步:修改UserController类


//设置使用ValidationGroup1分组的校验规则进行验证 public List<String> addUser(@Validated(ValidationGroup1.class) User user, BindingResult result)
第四步:测试。测试同上,自由测试。
14.SpringBoot项目打包与部署
springboot项目想要在tomcat服务器上运行,必须添加配置:
第一步:让启动类继承SpringBootServletInitializer,并重写configure方法,关键代码以下
@SpringBootApplication public class UploadDemoApplication extends SpringBootServletInitializer { public static void main(String[] args) { SpringApplication.run(UploadDemoApplication.class, args); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(UploadDemoApplication.class); } }
第二步:在pom文件中添加打包方式和去除自带的tomcat
<groupId>com.example</groupId> <artifactId>upload-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>upload-demo</name> <!--配置打包方式war--> <packaging>war</packaging> <description>Demo project for Spring Boot</description>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!--移除内嵌的tomcat--> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <!--servlet的 api--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency>
要注意,tomcat进行部署的时候,访问须要带项目名称,因此springboot在配置时默认已经配置了项目名称,这个在application.properties中查看
server.servlet.context-path=/upload-demo
第三步:清理项目,进行打包
第四步:把这个war包放到tomcat的webapps中,而后启动,访问便可。(需带项目名称)