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中,而後啓動,訪問便可。(需帶項目名稱)