springboot教程-web(二)
擼了今年阿里、頭條和美團的面試,我有一個重要發現.......>>>
第一節
現在開始springboot-web開發教程。
引入依賴,pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ac.iie</groupId><artifactId>spring-course</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies> </project>spring-boot-starter-web已經包含了spring-boot-starter依賴,因此只需引入這個依賴就可以了。
新建UserController.java
package com.edu.spring.springboot;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;@Controller public class UserController {@RequestMapping(value = "/user/home")@ResponseBodypublic String home() {return "user home";}}新建App.java
package com.edu.spring.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}}運行App.java,則服務器正常運行,默認端口號是8080,通過瀏覽器訪問http://localhost:8080/user/home正常。
這樣最簡單的web開發就完成了。
如果要修改端口,可以再application.properties中修改:
server.port=8081這樣端口號就修改成功了。
默認的請求方式是:GET,POST,PUT方式都支持。我們可以限制他的請求方式:
方法一:
@RequestMapping(value = "/user/home", method = RequestMethod.GET)@ResponseBodypublic String home() {return "user home";}方法二:
使用GetMapping
@GetMapping("/user/show")@ResponseBodypublic String show() {return "user home";}@PostMapping("/user/create")@ResponseBodypublic String create() {return "user home";}GetMapping PostMapping等是spring4.3的新特性
如何傳遞參數
方法一:
修改UserController.java
@PostMapping("/user/create")@ResponseBodypublic String create(@RequestParam("username") String username, @RequestParam("password") String password) {return "user create, username: " + username + ", password: " + password;}@RequestParam注解默認是參數必須提供,如果可以不提供可以使用required=false
可以提供一個默認值defaultValue=""
方法二:
@GetMapping("/user/{id}")@ResponseBodypublic String show(@PathVariable("id") String id) {return "user home id: " + id;}方法三:
注入Servlet的api
@GetMapping("/user/edit")@ResponseBodypublic String edit(HttpServletRequest httpServletRequest){return "user edit: " + httpServletRequest.getRemoteHost();}我們發現每個方法都必須使用@ResponseBody來注釋。因此可以使用RestController來簡化
新建RoleController.java
package com.edu.spring.springboot;import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;@RestController public class RoleController {@GetMapping("/role/show")public String show(){return "role show ";} }@RestController 表明了當前controller的方法的返回值可以直接用body輸出。
如何在springboot中使用jsp
新建LoginController.java
package com.edu.spring.springboot;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam;@Controller public class LoginController {@PostMapping("/login")public String login(@RequestParam("username") String username, @RequestParam(value = "password") String password) {if (username.equals(password)) {return "ok";}return "fail";}}在main文件夾下面新建webapp,與java和resources文件夾并列。
修改application.properties
?
在webapp目錄下新建文件夾/WEB-INF/jsp,然后新建ok.jsp和fail.jsp
springboot默認是不支持使用jsp的
?在springboot中使用jsp,需要引入依賴:
<dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-jasper</artifactId></dependency>這樣就可以成功訪問jsp了。
如何向jsp傳參數?
@GetMapping("/loginIndex")public String loginIndex(Model model) {model.addAttribute("username", "root");model.addAttribute("password", "123456");return "login";}新建login.jsp
<html> <h1>username; ${username}</h1> <h2>password: ${password}</h2> </html>在springboot中使用jsp時,不能使用@RestController, 而要使用@Controller
如何在Jsp中使用模板?
添加pom.xml依賴:并且刪除jsp的依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency>在application.properties中刪除jsp的配置。
在springboot中使用freemarker的步驟:
1. 在pom中加入依賴,? ? ? ?
????????<dependency>
? ? ? ? ? ? <groupId>org.apache.tomcat.embed</groupId>
? ? ? ? ? ? <artifactId>tomcat-embed-jasper</artifactId>
? ? ? ? </dependency>
2. 默認的freemaker的模板文件在classpath:/template/, 默認的文件擴展名為:ftl
新建AccountController.java
package com.edu.spring.springboot;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping;@Controller public class AccountController {@GetMapping("/reg")public String reg(){return "reg";}}在resources/template下新建reg.ftl
<h1>ftl reg </h1>可以通過訪問?http://192.168.170.132:8081/reg來獲取這個模板頁面了
如何修改模板文件的文件路徑
在application.properties中修改:
spring.freemarker.template-loader-path=classpath:/ftl/ 多個用逗號隔開在resources下新建ftl文件夾,然后將reg.ftl文件移動到這個路徑下,就可以訪問了。
如何在模板文件中傳參數
在AccountController.java
@GetMapping("/logout")public String logout(Model model){model.addAttribute("username", "admin");model.addAttribute("logout", "true");return "logout";}在ftl目錄下新建logout.ftl文件:
<h1>logout</h1> <h2>username: ${username}</h2> <h2>logout is ${logout}</h2>這樣就傳遞參數到模板中了。
最好在項目中要么選擇模板,要么選擇jsp,不要二者都選。
Springboot默認容器是Tomcat,如果想換成Jetty,如何做
首先需要把tomcat排除掉。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency>導入jetty依賴。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jetty</artifactId></dependency>其余都不需要改變,直接運行,輸出:
2019-05-15 21:00:56.619 INFO 14692 --- [ main] o.e.jetty.server.handler.ContextHandler : Started o.s.b.w.e.j.JettyEmbeddedWebAppContext@37d3d232{application,/,[org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory$LoaderHidingResource@30c0ccff],AVAILABLE} 2019-05-15 21:00:56.619 INFO 14692 --- [ main] org.eclipse.jetty.server.Server : Started @2652ms 2019-05-15 21:00:56.776 INFO 14692 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2019-05-15 21:00:57.069 INFO 14692 --- [ main] o.e.j.s.h.ContextHandler.application : Initializing Spring DispatcherServlet 'dispatcherServlet' 2019-05-15 21:00:57.070 INFO 14692 --- [ main] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2019-05-15 21:00:57.075 INFO 14692 --- [ main] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms 2019-05-15 21:00:57.194 INFO 14692 --- [ main] o.e.jetty.server.AbstractConnector : Started ServerConnector@614aeccc{HTTP/1.1,[http/1.1]}{0.0.0.0:8081} 2019-05-15 21:00:57.196 INFO 14692 --- [ main] o.s.b.web.embedded.jetty.JettyWebServer : Jetty started on port(s) 8081 (http/1.1) with context path '/' 2019-05-15 21:00:57.198 INFO 14692 --- [ main] com.edu.spring.springboot.App : Started App in 2.8 seconds (JVM running for 3.23)說明容器已經變成jetty了。
添加項目名稱
默認是不需要有項目名稱的,在application.properties文件中修改:
server.servlet.context-path=/mall在地址欄中,需要指定/mall才能訪問。例如:http://192.168.170.132:8081/mall/logout
第二節
如何在springboot中訪問靜態資源
1. src/main/webapp 下可以直接訪問
2. 默認的靜態資源路徑是:classpath:[/META-INF/resources/, * /resources/, /static/, /public/] 源碼在org.springframework.boot.autoconfigure.web包中
3. 可以通過spring.resources.static-locations配置項修改默認靜態資源路徑
方法一:
在src/main/webapp下新建user.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body><h1>this is user page </h1> </body> </html>可以直接在瀏覽器訪問http://localhost:8080/user.html,說明直接將html頁面放到webapp下面就可以直接訪問了。
在webapp下面新建目錄img,在img目錄中拷貝一張圖片進去my.jpg,在user.html中添加圖片,<img src="img/my.jpg" />。這樣可以直接在user.html中訪問圖片了。
方法二:
在resources下新建文件夾public
在resources/public 下新建login.html,
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>login</title> </head> <body> this is login html page. 在public下 </body> </html>訪問http://localhost:8080/login.html?可以訪問成功。
在public下新建css文件夾,新建main.css
body {color: red; }在login.html中引入這個main.css文件
<link href="css/main.css" rel="stylesheet" />訪問login.html頁面可以成功訪問,字體顏色生效。
方法三:
在application.properties中添加:
spring.resources.static-locations=classpath:/html/在resources中新建文件夾html
然后在resources/html/中新建index.html頁面,
重啟以后可以直接訪問http://localhost:8080/index.html
如何在springboot中使用Servlet
新建UserServlet.java,并且繼承HTTPServlet
使用Servlet3.0注解
package com.edu.spring.springboot;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;@WebServlet("/user.do") public class UserServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().println("user servlet");} }修改App.java ,將Servlet添加到spring容器中。
package com.edu.spring.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan;@ServletComponentScan @SpringBootApplication public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}}運行,可以通過瀏覽器訪問http://localhost:8080/user.do
如何在springboot容器中使用Servlet filter
新建LogFilter.java
package com.edu.spring.springboot;import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException;@WebFilter("/user.do") public class LogFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("income log filter " + servletRequest.getRemoteHost());filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {} }這個Filter可以攔截user.do請求。運行訪問http://localhost:8080/user.do時,控制臺輸出結果:
income log filter 0:0:0:0:0:0:0:1如何在springboot中使用Listener
新建MyContextListener.java
package com.edu.spring.springboot;import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import java.time.LocalDateTime;@WebListener public class MyContextListener implements ServletContextListener {@Overridepublic void contextInitialized(ServletContextEvent sce) {System.out.println("app start up at: " + LocalDateTime.now().toString());}@Overridepublic void contextDestroyed(ServletContextEvent sce) {} }這個監聽器將監聽應用程序啟動。啟動程序時,控制臺將會輸出:
app start up at: 2019-05-16T15:09:23.084如何不使用上述方法,實現Servlet的API
新建包com.edu.spring.springboot.servlet,在這個包下面新建BookServlet.java
package com.edu.spring.springboot.servlet;import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;public class BookServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().println("book servlet output");} }在這個包下新建ServletConfiguration.java
package com.edu.spring.springboot.servlet;import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean;@SpringBootConfiguration public class ServletConfiguration {@Beanpublic ServletRegistrationBean createBookServlet() {ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new BookServlet(), "/book.do");return servletRegistrationBean;}}修改App.java
package com.edu.spring.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}}瀏覽器輸入http://localhost:8080/book.do返回結果正常
使用這個方法,不用在Servlet上使用注釋,也不用使用@ServletComponentScan注釋。
同理,可以使用這個方法使用Filter
在Servlet這個包下新建EchoFilter.java
package com.edu.spring.springboot.servlet;import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException;public class EchoFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;System.out.println("spring boot web filter " + httpServletRequest.getRequestURI());filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {} }在ServletConfiguration.java中添加bean
@Beanpublic FilterRegistrationBean createFilterRegistraionBean() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setFilter(new EchoFilter());filterRegistrationBean.setUrlPatterns(Arrays.asList("/book.do"));return filterRegistrationBean;}瀏覽器上輸入http://localhost:8080/book.do,控制臺輸出:
spring boot web filter /book.do同理,新建StartUpListener.java
package com.edu.spring.springboot.servlet;import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener;public class StartUpListener implements ServletContextListener {@Overridepublic void contextInitialized(ServletContextEvent sce) {System.out.println("===========");System.out.println("application is started");}@Overridepublic void contextDestroyed(ServletContextEvent sce) {} }在ServletConfiguration.java中添加bean
@Beanpublic ServletListenerRegistrationBean<StartUpListener> createServletListenerRegistrationBean() {ServletListenerRegistrationBean<StartUpListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean(new StartUpListener() );return servletListenerRegistrationBean;}運行App.java,在應用程序運行開始,控制臺輸出:
=========== application is started總結
? ? springboot 中使用Servlet的API
方法一:? ??
? ? 1. 編寫Servlet,然后加上相應的注解
? ? 2. 需要啟用@ServletComponentScan注解?
? ? servlet2.5以上版本 可以使用這種方法使用
? ? 這種方法更方便一些。
方法二:
? ? 1. 編寫Servlet,
? ? 2.? 裝配相應的bean到spring容器中
????? ? Servlet ->?ServletRegistrationBean
????? ? Filter ->?FilterRegistrationBean
????? ? Listener -> ServletListenerRegistrationBean?
? ? Servlet2.5及以下版本可以使用這種方法
第三節
如何在springboot中使用攔截器
新建UserController.java
package com.edu.spring.springboot;import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;@RestController public class UserController {@GetMapping("/user/home")public String home() {System.out.println("----user---home");return "user home";}}新建LogHandlerInterceptor.java
package com.edu.spring.springboot;import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;public class LogHandlerInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("=preHandle=====" + handler.getClass());return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("=postHandle=====");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("=afterCompletion=====");} }新建WebConfiguration.java
package com.edu.spring.springboot;import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;@Configuration public class WebConfiguration extends WebMvcConfigurerAdapter {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LogHandlerInterceptor());} }或者:
package cn.ac.iie.authorization.config;import cn.ac.iie.authorization.interceptor.AuthorizationInterceptor; 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 MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LogHandlerInterceptor());} }這里的@Configuration注釋可以替換為@SpringBootConfiguration
運行App.java,然后在瀏覽器中輸入http://127.0.0.1:8080/user/home,正常顯示user home
控制臺輸出:
=preHandle=====class org.springframework.web.method.HandlerMethod ----user---home =postHandle===== =afterCompletion=====總結:攔截器的使用步驟
? ? 1. 寫一個攔截器,實現HandlerInterceptor接口
? ? 2. 寫一個類,繼承WebvcConfigurereAdapter抽象類,然后重寫addInterceptors方法,并調用registry.addInterceptor把上一步的攔截器加進去
HanderInterceptor
? ? 1. preHanle: controller執行之前調用
? ? 2. postHandle: controller執行之后,且頁面渲染之前調用
? ? 3. afterCompletion: 頁面渲染之后調用,一半用于資源清理操作
springboot開發中的異常處理?
將攔截器關閉,注釋WebConfiguration.java中的@Configuration
在UserController.java中添加方法:
@GetMapping("/user/help")public String help() {throw new IllegalArgumentException("args is empty");}當頁面請求/user/help的時候拋出異常,運行App.java
瀏覽器輸入http://127.0.0.1:8080/user/help,瀏覽器顯示如下:
Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback.Sun May 19 22:03:18 CST 2019 There was an unexpected error (type=Internal Server Error, status=500). args is empty同時控制臺輸出:
java.lang.IllegalArgumentException: args is emptyat com.edu.spring.springboot.UserController.help(UserController.java:17) ~[classes/:na]at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_144]at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_144]at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_144]at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_144]at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189) ~[spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]如何使用我們自己的異常頁面?
方法一
默認的異常頁面在ErrorMvcAutoConfiguration.java中定義,我們需要將這個類排除掉。
package com.edu.spring.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;@SpringBootApplication(exclude = ErrorMvcAutoConfiguration.class) public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}}這時,我們如果在瀏覽器中輸入一個不存在的網址時例如http://127.0.0.1:8080/user/help000,出現404的錯誤。
如果在瀏覽器中輸入http://127.0.0.1:8080/user/help,出現500錯誤頁面。
如何去掉springboot 默認的異常處理邏輯?
@SpringBootApplication(exclude = ErrorMvcAutoConfiguration.class)?如何使用自己的異常邏輯頁面?
在resoures下新建文件夾public,這時默認的web頁面訪問路徑,在public文件夾下面新建404.html和500.html
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body> <h1>404 not found</h1> </body> </html> <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title> </head> <body> <h1>500 error</h1> </body> </html>新建CommonErrorPageRegistry.java
package com.edu.spring.springboot;import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.ErrorPageRegistrar; import org.springframework.boot.web.server.ErrorPageRegistry; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component;@Component public class CommonErrorPageRegistry implements ErrorPageRegistrar {@Overridepublic void registerErrorPages(ErrorPageRegistry registry) {ErrorPage e404 = new ErrorPage(HttpStatus.NOT_FOUND, "/404.html");ErrorPage e500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html");registry.addErrorPages(e404, e500);} }瀏覽器中輸入http://127.0.0.1:8080/user/help000和http://127.0.0.1:8080/user/help分別跳轉到我們自定義的頁面
總結:
? ? 使用ErrorPageRegistrar方法
? ? 寫一個類,實現ErrorPageRegistrar接口,然后實現registerErrorPage方法,在該方法里面,添加具體的錯誤處理邏輯(類似web.xml有里面配置錯誤處理方法)
?如果我們想單獨給IllegalArgumentException異常渲染一個頁面,如何做?
修改CommonErrorPageRegistry.java
package com.edu.spring.springboot;import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.ErrorPageRegistrar; import org.springframework.boot.web.server.ErrorPageRegistry; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component;@Component public class CommonErrorPageRegistry implements ErrorPageRegistrar {@Overridepublic void registerErrorPages(ErrorPageRegistry registry) {ErrorPage e404 = new ErrorPage(HttpStatus.NOT_FOUND, "/404.html");ErrorPage e500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html");ErrorPage args = new ErrorPage(IllegalArgumentException.class, "/args.html");registry.addErrorPages(e404, e500, args);} }這樣IllegalArgumentException異常可以單獨頁面渲染了。
方法二:
首先將上一種方式屏蔽,將CommonErrorPageRegistry.java中的@Component注釋掉
新建BookController.java
package com.edu.spring.springboot;import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;import java.io.FileNotFoundException;@RestController public class BookController {@ExceptionHandler(value = FileNotFoundException.class)public String error(Exception e) {return "file not found exception" + e.getMessage();}@GetMapping("/book/error1")public String error1() throws FileNotFoundException {throw new FileNotFoundException("book.txt not found");}@GetMapping("/book/error2")public String error2() throws ClassNotFoundException {throw new ClassNotFoundException("book.class not found");}}在BookController.java中定義當前Controller中的異常,這個error方法將捕獲到FileNotFoundException并返回file not found exception,捕獲不到FileNotFound異常。并且這個只對當前Controller生效。對UserController中的異常并不處理。
如果要對當前Controller中的所有異常都捕獲,則@ExceptionHandler(value = Exception.class)
如何對所有Controller生效?
新建GlobalExceptionHandler.java
package com.edu.spring.springboot;import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice public class GlobalExceptionHandler {@ExceptionHandler(value = Exception.class)@ResponseBodypublic String errorHandler(Exception e) {return "global error " + e.getClass().getName();}}這樣就可以捕獲所有的Controller中的異常。
全局異常處理
? ? 1. 寫一個類,需要加上@ControllerAdvice注解
? ? 2. 寫一個異常處理方法,方法上面需要加上@ExceptionHandler(value=Exception.class)這個注解,然后在該方法里面處理異常
第四節
springboot如何定制和優化內嵌的Tomcat
springboot默認集成了2種web容器分別是tomcat和jetty
新建UserController.java
package com.edu.spring.springboot;import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;@RestController public class UserController {@GetMapping("/user/home")public String home(){return "user home";}}在application.properties中修改端口號
server.port=8081運行應用程序,在瀏覽器中輸入網址:http://127.0.0.1:8081/user/home和http://192.168.170.132:8081/user/home都可以訪問成功
在application.properties中添加:
server.port=8081 server.address=192.168.170.132運行應用程序,在瀏覽器中輸入http://127.0.0.1:8081/user/home就無法訪問了,說明ip綁定成功。
可以啟用tomcat日志:
server.port=8081 server.address=192.168.170.132 server.tomcat.accesslog.enabled=true server.tomcat.accesslog.directory=F:/test如何通過代碼的方式配置tomcat
注釋application.properties中的內容
新建MyEmbeddedServletContainerFactory.java
package com.edu.spring.springboot;import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class MyEmbeddedServletContainerFactory {@Beanpublic TomcatServletWebServerFactory servletContainer() {TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();tomcat.setPort(8081);return tomcat;} }同樣端口號修改為8081
設置tomcat連接數和線程數:
package com.edu.spring.springboot;import org.apache.catalina.connector.Connector; import org.apache.catalina.valves.AccessLogValve; import org.apache.coyote.http11.Http11NioProtocol; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class MyEmbeddedServletContainerFactory {@Beanpublic TomcatServletWebServerFactory servletContainer() {TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();tomcat.setPort(8081);tomcat.addConnectorCustomizers(new MyTomcatConnectorCustomizer());return tomcat;}class MyTomcatConnectorCustomizer implements TomcatConnectorCustomizer {@Overridepublic void customize(Connector connector) {Http11NioProtocol protocol=(Http11NioProtocol) connector.getProtocolHandler();//設置最大連接數protocol.setMaxConnections(2000);//設置最大線程數protocol.setMaxThreads(500);}} }添加tomcat日志,和404錯誤重定向頁面
package com.edu.spring.springboot;import org.apache.catalina.connector.Connector; import org.apache.catalina.valves.AccessLogValve; import org.apache.coyote.http11.Http11NioProtocol; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.server.ErrorPage; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus;@Configuration public class MyEmbeddedServletContainerFactory {@Beanpublic TomcatServletWebServerFactory servletContainer() {TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();tomcat.setPort(8081);tomcat.addContextValves(getLogAccessLogValve());tomcat.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,"/404.html"));tomcat.addInitializers(servletContext -> System.out.println("servlet start up =========="));tomcat.addConnectorCustomizers(new MyTomcatConnectorCustomizer());return tomcat;}private AccessLogValve getLogAccessLogValve(){AccessLogValve log = new AccessLogValve();log.setDirectory("F:/test");log.setEnabled(true);log.setPattern("common");log.setPrefix("springboot--");log.setSuffix(".txt");return log;}class MyTomcatConnectorCustomizer implements TomcatConnectorCustomizer {@Overridepublic void customize(Connector connector) {Http11NioProtocol protocol=(Http11NioProtocol) connector.getProtocolHandler();//設置最大連接數protocol.setMaxConnections(2000);//設置最大線程數protocol.setMaxThreads(500);}} }總結
定制和優化Tomcat,以編碼的方式設置Tomcat的各個屬性值,以及Tomcat的日志配置
TomcatServletWebServerFactory納入spring容器中管理
當我們的springboot中沒有自定義的web容器,那么springboot使用自己的tomcat,如果我們自定義了容器,則使用我們自定義的tomcat。?原因如下:在org.springframework.boot.autoconfigure.web.embedded包下
@Configuration @ConditionalOnWebApplication @EnableConfigurationProperties(ServerProperties.class) public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {/*** Nested configuration if Tomcat is being used.*/@Configuration@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })public static class TomcatWebServerFactoryCustomizerConfiguration {@Beanpublic TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {return new TomcatWebServerFactoryCustomizer(environment, serverProperties);}}第五節
spring JDBC配置
引入pom.xml
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://192.168.152.45:3306/renyuanku spring.datasource.username=root spring.datasource.password=123456在App.java中使用數據源
package com.edu.spring.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext;import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);DataSource dataSource = configurableApplicationContext.getBean(DataSource.class);try {Connection connection = dataSource.getConnection();System.out.println(connection.getCatalog());connection.close();} catch (SQLException e) {e.printStackTrace();}}}運行結果輸出數據庫名。
總結:
? ? 裝配DataSource的步驟
? ? 1.? 加入數據庫驅動
? ? 2.? 配置數據源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://192.168.152.45:3306/renyuanku spring.datasource.username=root spring.datasource.password=123456以上操作,springboot會自動裝配好DataSource,JDBCTemplate,可以直接使用
?數據庫使用JDBCTemplate操作數據庫
新建ProductDao.java
package com.edu.spring.springboot;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository;@Repository public class ProductDao {@Autowiredprivate JdbcTemplate jdbcTemplate;public void addProduct(String id){String sql = "insert into test (id) values ("+ id + ")";jdbcTemplate.execute(sql);}}修改App.java
package com.edu.spring.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext;import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);DataSource dataSource = configurableApplicationContext.getBean(DataSource.class);try {Connection connection = dataSource.getConnection();System.out.println(connection.getCatalog());connection.close();ProductDao bean = configurableApplicationContext.getBean(ProductDao.class);bean.addProduct("123");} catch (SQLException e) {e.printStackTrace();}}}執行App.java,查詢數據庫,可以看到執行成功
查看Springboot用的什么數據源
package com.edu.spring.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext;import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);DataSource dataSource = configurableApplicationContext.getBean(DataSource.class);System.out.println(dataSource.getClass());}}輸出結果:
class com.zaxxer.hikari.HikariDataSource可以看到使用的是HikariDataSource數據源
如何使用其他數據源
在application.properties中配置
spring.datasource.type=可以指定具體使用哪種數據源,springboot默認支持一下數據源,在類中org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,DataSourceJmxConfiguration.class })Hikari,tomcat,dbcp2,generic,放到classpath下
如何自己配置數據源
添加druid數據源依賴
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.6</version></dependency>新建DBConfiguration.java
package com.edu.spring.springboot;import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment;import javax.sql.DataSource;@SpringBootConfiguration public class DBConfiguration {@Autowiredprivate Environment environment;@Beanpublic DataSource createDataSource() {DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setUrl(environment.getProperty("spring.datasource.url"));druidDataSource.setUsername(environment.getProperty("spring.datasource.username"));druidDataSource.setPassword(environment.getProperty("spring.datasource.password"));druidDataSource.setDriverClassName(environment.getProperty("spring.datasource.driver-class-name"));return druidDataSource;}}application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://192.168.152.45:3306/renyuanku spring.datasource.username=root spring.datasource.password=123456App.java
package com.edu.spring.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext;import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);DataSource dataSource = configurableApplicationContext.getBean(DataSource.class);System.out.println(dataSource.getClass());}}運行輸出:
class com.alibaba.druid.pool.DruidDataSource說明數據源已經變為Druid了。
springboot的特點是優先使用自己的配置,然后使用spring默認配置。
同樣可以使用JDBCTemplate
修改App.java
public class App {public static void main(String[] args) {ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);DataSource dataSource = configurableApplicationContext.getBean(DataSource.class);try {Connection connection = dataSource.getConnection();System.out.println(connection.getCatalog());connection.close();ProductDao bean = configurableApplicationContext.getBean(ProductDao.class);bean.addProduct("124");} catch (SQLException e) {e.printStackTrace();}System.out.println(dataSource.getClass());}}成功插入數據124
Springboot對事務也做了很好的集成
修改ProductDao.java
package com.edu.spring.springboot;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional;@Repository public class ProductDao {@Autowiredprivate JdbcTemplate jdbcTemplate;public void addProduct(String id){String sql = "insert into test (id) values ("+ id + ")";jdbcTemplate.execute(sql);}@Transactionalpublic void addProductBatch(String ...ids) throws FileNotFoundException {for(String id: ids){String sql = "insert into test (id) values ("+ id + ")";jdbcTemplate.execute(sql);if("".equals("")) {throw new FileNotFoundException();}}}}使用事務需要在方法上添加注釋@Transactional
然后在App.java啟用事務,添加注釋@EnableTransactionManagement
@SpringBootApplication @EnableTransactionManagement public class App {public static void main(String[] args) {ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);DataSource dataSource = configurableApplicationContext.getBean(DataSource.class);try {Connection connection = dataSource.getConnection();System.out.println(connection.getCatalog());connection.close();ProductDao bean = configurableApplicationContext.getBean(ProductDao.class);try {bean.addProductBatch("111", "222", "333", "444", "555");} catch (FileNotFoundException e) {e.printStackTrace();}} catch (SQLException e) {e.printStackTrace();}System.out.println(dataSource.getClass());}}然后執行,報異常,查詢數據庫發現存入了一條數據 111,說明事務沒有生效。
原因是spring默認會對運行時的異常進行事務的操作,而fileNotFound不是運行時的異常,我們需要修改為RunTimeException。修改:
@Transactionalpublic void addProductBatch(String ...ids) throws FileNotFoundException {for(String id: ids){String sql = "insert into test (id) values ("+ id + ")";jdbcTemplate.execute(sql);if("".equals("")) {throw new NullPointerException();}}}然后運行App.java,報出異常,查詢數據庫,沒有插入數據,說明事務生效。
事務
? ? 首先要使用@EnableTransactionManagement啟用對事務的支持
? ? 然后在需要使用事務的方法上面加上@Transactional
? ? 注意,默認只會對運行時異常進行事務回滾,非運行時異常不會回滾事務
?如何回滾非運行時異常
使用@Transactional(rollbackFor = Exception.class)可以回滾所有異常
@Transactional(rollbackFor = Exception.class)public void addProductBatch(String ...ids) throws FileNotFoundException {for(String id: ids){String sql = "insert into test (id) values ("+ id + ")";jdbcTemplate.execute(sql);if("".equals("")) {throw new FileNotFoundException();}}}如何不回滾某些異常
使用@Transactional(noRollbackFor = NullPointerException.class)
@Transactional(rollbackFor = Exception.class, noRollbackFor = NullPointerException.class)public void addProductBatch(String ...ids) throws Exception {for(String id: ids){String sql = "insert into test (id) values ("+ id + ")";jdbcTemplate.execute(sql);if("".equals("")) {throw new NullPointerException();}}}注意:@Transactional必須要標注在納入到spring容器管理bean的公有方法,例如:
@Transactional()public void addTest(String ...ids){add(ids);}@Transactional()private void add(String ...ids){for(String id: ids){String sql = "insert into test (id) values ("+ id + ")";jdbcTemplate.execute(sql);if("".equals("")) {throw new NullPointerException();}}}運行App.java可以成功插入數據庫,事務沒有生效。
注意:直接調用的方法必須要使用@Transactional注釋,否則不能回滾
第六節
SpringAOP
日志記錄、權限處理、監控、異常處理
添加依賴pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ac.iie</groupId><artifactId>spring-course</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency></dependencies> </project>新建包dao,在dao下面新建UserDao.java
package com.edu.spring.springboot.dao;import org.springframework.stereotype.Component;@Component public class UserDao {public void add (String username, String password){System.out.println("add: username:" + username + ",password:" + password);} }新建LogAspect.java
package com.edu.spring.springboot;import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component;@Aspect @Component public class LogAspect {@Before("execution(* com.edu.spring.springboot.dao..*.*(..))")public void log() {System.out.println("method log done" );}}execution(* com.edu.spring.springboot.dao..*.*(..)) 表示織入到com.edu.spring.springboot.dao及其子包下面的所有的類的所有的方法。
執行的時機就是,前置執行。
App.java
package com.edu.spring.springboot;import com.edu.spring.springboot.dao.UserDao; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);configurableApplicationContext.getBean(UserDao.class).add("admin", "123456");configurableApplicationContext.close();}}運行結果:
method log done add: username:admin,password:123456這是一個最簡單的AOP。
AOP開發流程
? ? 1.?spring-boot-starter-aop加入依賴,默認開啟了AOP的支持
? ? 2. 寫一個Aspect,封裝橫切關注點(日志,監控等等),需要配置通知(前置通知,后置通知等等)和切入點(哪些包的哪些類的哪些方法等等);
? ? 3. 這個Aspect需要納入到spring容器管理,并且需要加入@Aspect注解
在application.properties中配置:
spring.aop.auto=false表示不啟用aop,默認是為true啟用,運行App.java,結果如下:
add: username:admin,password:123456在application.properties中配置:
spring.aop.auto=true spring.aop.proxy-target-class=falsespring.aop.proxy-target-class默認是true,false表示使用的是JDK的動態代理,true表示使用CGLIB的動態代理
JDK的動態代理需要一個接口
新建IUserDao.java
package com.edu.spring.springboot.dao;public interface IUserDao {public void add (String username, String password); }然后讓UserDao實現這個接口,修改App.java
package com.edu.spring.springboot;import com.edu.spring.springboot.dao.UserDao; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);System.out.println(configurableApplicationContext.getBean(UserDao.class).getClass());configurableApplicationContext.getBean(UserDao.class).add("admin", "123456");configurableApplicationContext.close();}}運行報錯:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.edu.spring.springboot.dao.UserDao' availableat org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:335)at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1123)at com.edu.spring.springboot.App.main(App.java:12)原因是基于JDK的動態代理之后,就不能根據class來獲取對象,需要根據接口來獲取對象。
@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);System.out.println(configurableApplicationContext.getBean(IUserDao.class).getClass());configurableApplicationContext.getBean(IUserDao.class).add("admin", "123456");configurableApplicationContext.close();}}運行輸出結果如下:
class com.sun.proxy.$Proxy55 method log done add: username:admin,password:123456這是典型的動態代理。
將spring.aop.proxy-target-class改為true,運行結果如下:
class com.edu.spring.springboot.dao.UserDao$$EnhancerBySpringCGLIB$$62d64f2d method log done add: username:admin,password:123456總結:
? ? aop默認是使用基于JDK的動態代理來實現AOP,默認啟用
????spring.aop.proxy-target-class=true或者不配置,表示使用cglib的動態代理,
? ? =false表示jdk動態代理
? ? 如果配置了false,而類沒有借口,則依然使用cglib
將application.properties中的配置注釋掉。
如何得到aop相關參數
修改LogAspect.java
package com.edu.spring.springboot;import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component;import java.util.Arrays;@Aspect @Component public class LogAspect {@Before("execution(* com.edu.spring.springboot.dao..*.*(..))")public void log() {System.out.println("before method log done" );}@After("execution(* com.edu.spring.springboot.dao..*.*(..))")public void logAfter(JoinPoint point) {System.out.println("before method log done" + point.getTarget().getClass() + ", args="+ Arrays.asList(point.getArgs()) + ", method=" + point.getSignature().getName());}}輸出結果如下:
class com.edu.spring.springboot.dao.UserDao$$EnhancerBySpringCGLIB$$abbff7d9 before method log done add: username:admin,password:123456 before method log doneclass com.edu.spring.springboot.dao.UserDao, args=[admin, 123456]雖然springboot默認支持了AOP,但是springboot依然提供了enable的注解,@EnableAspectJAutoProxy
第七節 Springboot starter
新建RedisProperties.java
package com.edu.spring.springboot;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(prefix = "redis") public class RedisProperties {private String host;private Integer port;public String getHost() {return host;}public void setHost(String host) {this.host = host;}public Integer getPort() {return port;}public void setPort(Integer port) {this.port = port;} }新建RedisConfiguration.java
package com.edu.spring.springboot;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import redis.clients.jedis.Jedis;@Configuration @ConditionalOnClass(Jedis.class) @EnableConfigurationProperties(RedisProperties.class) public class RedisAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic Jedis jedis(RedisProperties redisProperties){Jedis jedis = new Jedis(redisProperties.getHost(), redisProperties.getPort());System.out.println("springbourse bean" + jedis);return jedis;} }這樣的話spring容器在裝配Jedis這個bean的時候會先從容器中獲取RedisProperties這個bean,然后傳到這個方法中去。
@ConditionalOnClass(Jedis.class)表示裝配這個bean的時候Jedis.class這個類一定要存在。
@ConditionalOnMissingBean表示沒有這個Jedis這個類的時候,我們才裝配。
新建項目spring-course-redis,將上面的項目加到這個項目中去:pom.xml如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ac.iie</groupId><artifactId>spring-course-redis</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>cn.ac.iie</groupId><artifactId>spring-course</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies></project>加入好依賴以后,在spring-course-redis項目中我們可以直接從容器中獲取jedis了,App.java如下:
package com.edu.spring.springboot;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import redis.clients.jedis.Jedis;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);Jedis jedis = configurableApplicationContext.getBean(Jedis.class);System.out.println("springbourseredis bean" + jedis);jedis.set("id", "vincent");System.out.println(jedis.get("id"));}}新建application.properties,內容如下:
redis.host=192.168.152.45 redis.port=6379運行App.java輸出如下:
springbourse beanredis.clients.jedis.Jedis@60bdf15d springbourseredis beanredis.clients.jedis.Jedis@60bdf15d vincent說明已經成功注入進去了。
但是在springboot1.X版本中是無法直接這樣使用的。
解決方法有兩種,
方法一:
? ? 在springbootcourse項目中,新建EnableRedis.java,需要使用@Import注解
package com.edu.spring.springboot;import org.springframework.context.annotation.Import;import java.lang.annotation.*;@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(RedisAutoConfiguration.class) public @interface EnableRedis { }? ? 在springbootcourseredis項目中,添加@EnableRedis注解
@EnableRedis @SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(App.class, args);Jedis jedis = configurableApplicationContext.getBean(Jedis.class);System.out.println("springbourseredis bean" + jedis);jedis.set("id", "vincent");System.out.println(jedis.get("id"));}}方法二:
使用spring.factories
在springbootcourse項目中在resources目錄下,新建/META-INF文件夾,然后在這個文件夾下新建spring.factories文件,內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.edu.spring.springboot.RedisAutoConfiguration總結
? ? springboot2.x 可以直接使用
? ? springboot1.x 需要進行配置。
自己開發一個spring boot? starter的步驟
1. 新建一個項目
2. 需要一個配置類,配置類里面需要裝配好需要提供出去的類
3. 使用
? ? (1)@Enable ,使用@Import導入需要裝配的類
? ? (2)/META-INF/spring.factories, 在org.springframework.boot.autoconfigure.EnableAutoConfiguration配置需要裝配的類
第八節 springboot日志
默認的日志輸出結果如下:
2019-05-26 14:29:27.650 INFO 641 --- [ main] com.edu.spring.springboot.App : Starting App on duandingyangdeMacBook-Pro.local with PID 641 (/Users/duandingyang/git-project/springcourse/target/classes started by duandingyang in /Users/duandingyang/git-project/springcourse) 2019-05-26 14:29:27.654 INFO 641 --- [ main] com.edu.spring.springboot.App : No active profile set, falling back to default profiles: default 2019-05-26 14:29:28.804 INFO 641 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2019-05-26 14:29:28.834 INFO 641 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2019-05-26 14:29:28.835 INFO 641 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.17] 2019-05-26 14:29:28.931 INFO 641 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2019-05-26 14:29:28.931 INFO 641 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1230 ms 2019-05-26 14:29:29.203 INFO 641 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2019-05-26 14:29:29.409 INFO 641 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2019-05-26 14:29:29.416 INFO 641 --- [ main] com.edu.spring.springboot.App : Started App in 2.638 seconds (JVM running for 3.64)日志級別為Info ,進程ID(PID)641 , 線程名字main,所在類,日志內容。
新建dao包,然后在這個dao包下新建UserDao.java
package com.edu.spring.springboot.dao;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component;@Component public class UserDao {private Logger logger = LoggerFactory.getLogger(UserDao.class);public void log() {logger.debug("user dao debug log");logger.info("user dao info log");logger.warn("user dao warn log");logger.error("user dao error log");} }App.java內容如下:
package com.edu.spring.springboot;import com.edu.spring.springboot.dao.UserDao; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext;@SpringBootApplication public class App {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(App.class, args);run.getBean(UserDao.class).log();run.close();}}運行輸出結果如下:
2019-05-26 14:35:24.170 INFO 787 --- [ main] com.edu.spring.springboot.dao.UserDao : user dao info log 2019-05-26 14:35:24.170 WARN 787 --- [ main] com.edu.spring.springboot.dao.UserDao : user dao warn log 2019-05-26 14:35:24.170 ERROR 787 --- [ main] com.edu.spring.springboot.dao.UserDao : user dao error log說明日志的默認級別是info。
如何調整日志級別?
方法一:
修改application.properties
logging.level.*=DEBUG可以通過logging.level.*=debug 來設置,* 可以是包,也可以是某個類。
方法二
在program arguments中設置--debug,也可以啟用DEBUG,但是這種方式無法輸出我們自己的DEBUG信息,只可以輸出Spring的debug
新建service包,然后在這個包下新建UserService.java
package com.edu.spring.springboot.service;import org.slf4j.Logger; import org.slf4j.LoggerFactory;public class UserService {private Logger logger = LoggerFactory.getLogger(UserService.class);public void log() {logger.debug("user service debug log");logger.info("user service info log");logger.warn("user service warn log");logger.error("user service error log");} }如果我們只想在service包下面使用debug,則需要修改application.properties內容:
logging.level.com.edu.spring.springboot.service=DEBUG在App.java中添加
run.getBean(UserService.class).log();輸出結果如下:
2019-05-26 14:54:20.149 INFO 843 --- [ main] com.edu.spring.springboot.dao.UserDao : user dao info log 2019-05-26 14:54:20.149 WARN 843 --- [ main] com.edu.spring.springboot.dao.UserDao : user dao warn log 2019-05-26 14:54:20.149 ERROR 843 --- [ main] com.edu.spring.springboot.dao.UserDao : user dao error log 2019-05-26 14:54:20.149 DEBUG 843 --- [ main] c.e.s.springboot.service.UserService : user service debug log 2019-05-26 14:54:20.149 INFO 843 --- [ main] c.e.s.springboot.service.UserService : user service info log 2019-05-26 14:54:20.149 WARN 843 --- [ main] c.e.s.springboot.service.UserService : user service warn log 2019-05-26 14:54:20.149 ERROR 843 --- [ main] c.e.s.springboot.service.UserService : user service error logservice啟用了debug,dao默認的info
日志級別有:trace,debug,info,warn,error,fatal,off
日至級別off表示關閉日志
如何配置日志輸出文件?
application.properties
logging.file=/Users/vincent/my.log指定日志文件路徑與名字。
logging.path 也可以指定日志的路徑,此時名字為spring.log
日志文件輸出,文件的大小10M之后,就會分割了
如何指輸出日志格式
logging.pattern.console=%-20(%d{yyy-MM-dd} [%thread]) %-5level %logger{80} - %msg%n logging.file.console=%-20(%d{yyy-MM-dd HH:mm:ss.SSS} [%thread]) %-5level %logger{80} - %msg%n分別為控制臺的日志輸出格式和文件日志輸出格式
使用logback
在resources下新建logback.xml
<?xml version="1.0" encoding="utf-8" ?> <configuration><appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender"><layout class="ch.qos.logback.classic.PatternLayout"><pattern>%-20(%d{yyy-MM-dd HH:mm:ss.SSS} [%thread]) %-5level %logger{80} - %msg%n</pattern></layout></appender><root level="debug"><appender-ref ref="consoleLog" /></root> </configuration>springboot 默認支持logback,也就是說,只需要在classpath下放一個logback.xml或者logback-spring.xml的文件,即可定制日志的輸出。
如何使用log4j2
現將默認的日志排除,并且加入log4j依賴,pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ac.iie</groupId><artifactId>spring-course</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency></dependencies> </project>在resources目錄下新建log4j2.xml
<?xml version="1.0" encoding="utf-8" ?> <configuration><appenders><Console name="console" target="SYSTEM_OUT" follow="true"><PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n" /></Console></appenders><loggers><root level="DEBUG"><appender-ref ref="console" /></root></loggers> </configuration>運行App.java,輸出結果為:
2019-05-27 23:28:51.808 [main] DEBUG com.edu.spring.springboot.dao.UserDao - user dao debug log 2019-05-27 23:28:51.808 [main] INFO com.edu.spring.springboot.dao.UserDao - user dao info log 2019-05-27 23:28:51.808 [main] WARN com.edu.spring.springboot.dao.UserDao - user dao warn log 2019-05-27 23:28:51.808 [main] ERROR com.edu.spring.springboot.dao.UserDao - user dao error log 2019-05-27 23:28:51.808 [main] DEBUG com.edu.spring.springboot.service.UserService - user service debug log 2019-05-27 23:28:51.808 [main] INFO com.edu.spring.springboot.service.UserService - user service info log 2019-05-27 23:28:51.808 [main] WARN com.edu.spring.springboot.service.UserService - user service warn log 2019-05-27 23:28:51.808 [main] ERROR com.edu.spring.springboot.service.UserService - user service error log說明log4j2配置成功。當然了,log4j2.xml 文件名也可以改為log4j2-spring.xml
使用其他的日志組件的步驟1:排除掉默認的日志組件spring-boot-starter-logging2:加入新的日志組件依賴3:把相應的日志文件加到classpath下?springboot 的相關日志源碼在org.springframework.boot.logging包下面,其中LogLevel.java定義了日志級別,LoggingSystemProperties定義了日志配置項。
第九節 springboot監控和度量
添加依賴pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ac.iie</groupId><artifactId>spring-course</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-actuator</artifactId></dependency></dependencies> </project>? ? spring boot2.x中,默認只開放了info、health兩個端點,其余的需要自己通過配置management.endpoints.web.exposure.include屬性來加載(有include自然就有exclude)。如果想單獨操作某個端點可以使用management.endpoint.端點.enabled屬性進行啟用或者禁用。
Endpoints
????Actuator endpoints 允許你去監控和操作你的應用。SpringBoot包含了許多內置的端點,當然你也可以添加自己的端點。比如 health 端點就提供了基本的應用健康信息。
Metrics
????Spring Boot Actuator 提供 dimensional metrics 通過集成 Micrometer.
Audit
????Spring Boot Actuator 有一套靈活的審計框架會發布事件到 AuditEventRepository。
springboot 2.x 默認只啟動了 health 和 info 端點,可以通過 application.properties 配置修改:
management.endpoints.web.exposure.include=health,info,env,metrics項目啟動時可以看到暴露出來的接口信息:
2019-05-30 18:47:35.162 INFO 9888 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 4 endpoint(s) beneath base path '/actuator'瀏覽器中輸入http://localhost:8080/actuator/,結果如下:
{"_links": {"self": {"href": "http://localhost:8080/actuator","templated": false},"health-component": {"href": "http://localhost:8080/actuator/health/{component}","templated": true},"health-component-instance": {"href": "http://localhost:8080/actuator/health/{component}/{instance}","templated": true},"health": {"href": "http://localhost:8080/actuator/health","templated": false},"env": {"href": "http://localhost:8080/actuator/env","templated": false},"env-toMatch": {"href": "http://localhost:8080/actuator/env/{toMatch}","templated": true},"info": {"href": "http://localhost:8080/actuator/info","templated": false},"metrics": {"href": "http://localhost:8080/actuator/metrics","templated": false},"metrics-requiredMetricName": {"href": "http://localhost:8080/actuator/metrics/{requiredMetricName}","templated": true}} }瀏覽器中輸入:http://localhost:8080/actuator/metrics/system.cpu.usage,結果如下:
{"name": "system.cpu.usage","description": "The \"recent cpu usage\" for the whole system","baseUnit": null,"measurements": [{"statistic": "VALUE","value": 0.24420139608387495}],"availableTags": [] }常用的endpoint:
| GET | /autoconfig | 查看自動配置的使用情況 |
| GET | /configprops | 查看配置屬性,包括默認配置 |
| GET | /beans | 查看bean及其關系列表 |
| GET | /dump | 打印線程棧 |
| GET | /env | 查看所有環境變量 |
| GET | /env/{name} | 查看具體變量值 |
| GET | /health | 查看應用健康指標 |
| GET | /info | 查看應用信息 |
| GET | /mappings | 查看所有url映射 |
| GET | /metrics | 查看應用基本指標 |
| GET | /metrics/{name} | 查看具體指標 |
| POST | /shutdown | 關閉應用 |
| GET | /trace | 查看基本追蹤信息 |
想要查看服務器的健康狀態詳細信息,需要配置application.properties
management.endpoint.health.show-details=always這樣就可以查看健康狀態的詳細信息了,例如磁盤利用情況,數據庫情況。
如何自定義健康狀態檢查?
新建MyHealthIndicator.java
package com.edu.spring.springboot;import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component;@Component public class MyHealthIndicator implements HealthIndicator {@Overridepublic Health health() {return Health.up().withDetail("error","springboot error").build();} }在瀏覽器中輸入http://localhost:8080/actuator/health可以看到自定義的健康狀態監控。
總結:
? ? 自定義健康狀態監測,實現HealthIndicator接口,并納入spring容器的管理之中。
使用info,
在application.properties中使用info開頭的信息都可以顯示出來,例如:
info.name=myinfo info.datasource.driver-class-name=com.mysql.cj.jdbc.Driver在瀏覽器中輸入,http://localhost:8080/actuator/info?可以查看到這些配置信息。
可以對git信息進行監控。
Prometheus Grafana實現應用可視化監控
第十節 打包springboot
pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ac.iie</groupId><artifactId>spring-course</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.1.4.RELEASE</version><configuration><mainClass>com.edu.spring.springboot.App</mainClass></configuration><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>一定要指定mainClass才可以
springboot 測試
添加pom.xml依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.ac.iie</groupId><artifactId>spring-course</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.1.4.RELEASE</version><scope>import</scope><type>pom</type></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.1.4.RELEASE</version><configuration><mainClass>com.edu.spring.springboot.App</mainClass></configuration><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>新建UserDao.java
package com.edu.spring.springboot.dao;import org.springframework.stereotype.Repository;@Repository public class UserDao {public Integer addUser(String username) {System.out.println("user dao adduser " + username);if(username == null) {return 0;}return 1;}}在Intellij下Ctrl + Shift + T 新建測試類UserDaoTest.java
package com.edu.spring.springboot.dao;import org.junit.Assert; import org.junit.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.SpringRunner;import static org.junit.Assert.*;@RunWith(SpringRunner.class) @SpringBootTest public class UserDaoTest {@Autowiredprivate UserDao userDao;@Testpublic void addUser() {Assert.assertEquals(Integer.valueOf(1), userDao.addUser("root"));Assert.assertEquals(Integer.valueOf(0), userDao.addUser(null));} }輸出結果如下:
user dao adduser root user dao adduser nullspringboot測試步驟,
? ? 直接在測試類上面添加下面的注解:
????????@RunWith(SpringRunner.class)
????????@SpringBootTest
Test下的包名和java下的包名應該一致。
如何測試bean?
新建User.java并且納入到spring容器管理中去。
package com.edu.spring.springboot.bean;import org.springframework.stereotype.Component;@Component public class User {}新建測試類ApplicationContextTest.java
package com.edu.spring.springboot.dao;import com.edu.spring.springboot.bean.User; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class) @SpringBootTest public class ApplicationContextTest {@Autowiredprivate ApplicationContext applicationContext;@Testpublic void testNull() {Assert.assertNotNull(applicationContext.getBean(User.class));}}輸出結果可以顯示,輸出正常。
如何在測試類中自定義一個bean?
在src/test/java/com/edu/springboot/dao下新建TestBeanConfiguration.java
package com.edu.spring.springboot.dao;import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean;@TestConfiguration public class TestBeanConfiguration {@Beanpublic Runnable createRunnable() {return () -> {};}}然后在ApplicationContextTest.java 指定classes
package com.edu.spring.springboot.dao;import com.edu.spring.springboot.bean.User; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class) @SpringBootTest(classes = TestBeanConfiguration.class) public class ApplicationContextTest {@Autowiredprivate ApplicationContext applicationContext;@Testpublic void testNull() {Assert.assertNotNull(applicationContext.getBean(User.class));Assert.assertNotNull(applicationContext.getBean(Runnable.class));}}指定classes=TestBeanConfiguration.class就可以使用這個bean了。
使用@TestConfiguration可以在測試環境下指定bean。如果在App.java 中使用這個bean,那么會報錯,找不到這個bean。
只有在測試環境下有效。
? ? 測試環境下,只能用@TestConfiguration,不能用@Configuration
如何環境測試?
在test/java/com/edu/spring/springboot/dao下 新建EnvTest.java?
package com.edu.spring.springboot.dao;import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.env.Environment; import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class) @SpringBootTest public class EnvTest {@Autowiredprivate Environment environment;@Testpublic void testValue() {Assert.assertEquals("myapplication", environment.getProperty("spring.application.name"));}}在main/resources/application.properties內容如下:
spring.application.name=myapplication測試運行正常。
如果我們的application.properties在test/resources/下怎么辦?
在test下新建文件夾resources,然后在test/resources/目錄下新建application.properties,內容如下:
spring.application.name=myapplication-test然后運行EnvTest.java文件,報錯:
Expected :myapplication Actual :myapplication-test說明這里面的配置文件,優先去取test/resources中的application.properties,如果沒有這個配置文件,則去main/resoures中去取。
在測試環境中,springboot會優先加載測試環境下的配置文件(application.properties)
測試環境下沒有,才會加載正式環境下的配置文件。
測試環境中自定義指定配置項?
(properties = {"app.version=1.0.0"}):
package com.edu.spring.springboot.dao;import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.env.Environment; import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class) @SpringBootTest(properties = {"app.version=1.0.0"}) public class EnvTest {@Autowiredprivate Environment environment;@Testpublic void testValue() {Assert.assertEquals("myapplication-test", environment.getProperty("spring.application.name"));Assert.assertEquals("1.0.0", environment.getProperty("app.version"));}}運行成功。
Mock如何測試接口?
新建mapper包,在包下新建UserMapper.java
package com.edu.spring.springboot.mapper;public interface UserMapper {Integer createUser(String username); }在Test下,新建UserDaoTest.java
package com.edu.spring.springboot.mapper;import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.BDDMockito; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) public class UserMapperTest {@MockBeanprivate UserMapper userMapper;@Test(expected = NullPointerException.class)public void createUser() {BDDMockito.given(userMapper.createUser("admin")).willReturn(Integer.valueOf(1));BDDMockito.given(userMapper.createUser("")).willReturn(Integer.valueOf(0));BDDMockito.given(userMapper.createUser(null)).willThrow(NullPointerException.class);Assert.assertEquals(Integer.valueOf(1), userMapper.createUser("admin"));Assert.assertEquals(Integer.valueOf(0), userMapper.createUser(""));Assert.assertEquals(Integer.valueOf(0), userMapper.createUser(null));} }因為接口并沒有實現類,因此需要做提前預測,BDDMockito.given就是這個功能。當user.createUser()的輸入時admin時,返回整型1,當user.createUser()的輸入是“”時,返回整型0,當user.createUser()的輸入時null時,返回異常。
mock方法可以卸載init方法中,如下所示:
package com.edu.spring.springboot.mapper;import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.BDDMockito; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) public class UserMapperTest {@MockBeanprivate UserMapper userMapper;@Beforepublic void init() {BDDMockito.given(userMapper.createUser("admin")).willReturn(Integer.valueOf(1));BDDMockito.given(userMapper.createUser("")).willReturn(Integer.valueOf(0));BDDMockito.given(userMapper.createUser(null)).willThrow(NullPointerException.class);}@Test(expected = NullPointerException.class)public void createUser() {Assert.assertEquals(Integer.valueOf(1), userMapper.createUser("admin"));Assert.assertEquals(Integer.valueOf(0), userMapper.createUser(""));Assert.assertEquals(Integer.valueOf(0), userMapper.createUser(null));} }對Controller進行測試
方法一:
新建BookController.java
package com.edu.spring.springboot.controller;import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;@RestController public class BookController {@GetMapping("/book/home")public String home() {System.out.println("/book/home url is invoke");return "book home";}}新建測試類BookControllerTest.java
package com.edu.spring.springboot.controller;import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.test.context.junit4.SpringRunner;import static org.junit.Assert.*;@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class BookControllerTest {@Autowiredprivate TestRestTemplate testRestTemplate;@Testpublic void home() {String forObject = testRestTemplate.getForObject("/book/home", String.class);Assert.assertEquals("book home", forObject);} }TestRestTemplate 需要在web環境中,因此需要SpringBootTest.WebEnvironment.RANDOM_PORT
?在BookController.java中添加方法,測試有參數的Controller方法:
@GetMapping("/book/show")public String show(@RequestParam("id") String id) {System.out.println("/book/show url is invoke");return "book" + id;}在測試方法中:
@Testpublic void show() {String forObject = testRestTemplate.getForObject("/book/show?id=100", String.class);Assert.assertEquals("book100", forObject);}方法二:
新建測試類BookControllerTest2.java
package com.edu.spring.springboot.controller;import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvcBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers;@RunWith(SpringRunner.class) @WebMvcTest(controllers = BookController.class) public class BookControllerTest2 {@Autowiredprivate MockMvc mockMvc;@Testpublic void home() throws Exception {mockMvc.perform(MockMvcRequestBuilders.get("/book/home")).andExpect(MockMvcResultMatchers.status().isOk());mockMvc.perform(MockMvcRequestBuilders.get("/book/home")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().string("book home"));}}@WebMvcTest 不需要運行在web環境下,但是,需要指定controllers,表示需要測試哪些controller
?修改BookController.java,使用UserDao
package com.edu.spring.springboot.controller;import com.edu.spring.springboot.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;@RestController public class BookController {@Autowiredprivate UserDao userDao;@GetMapping("/book/home")public String home() {System.out.println("/book/home url is invoke");return "book home";}@GetMapping("/book/show")public String show(@RequestParam("id") String id) {System.out.println("/book/show url is invoke");userDao.addUser("aaa");return "book" + id;}}報錯信息如下:
java.lang.IllegalStateException: Failed to load ApplicationContextat org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125) ~[spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108) ~[spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118) ~[spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83) ~[spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:44) ~[spring-boot-test-autoconfigure-2.1.4.RELEASE.jar:2.1.4.RELEASE]at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246) ~[spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) [spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) [spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) [spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) [spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) [spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) [spring-test-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.junit.runner.JUnitCore.run(JUnitCore.java:137) [junit-4.12.jar:4.12]at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) [junit-rt.jar:na]at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) [junit-rt.jar:na]at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) [junit-rt.jar:na]at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) [junit-rt.jar:na] Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bookController': Unsatisfied dependency expressed through field 'userDao'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.edu.spring.springboot.dao.UserDao' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1411) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE]說沒有UserDao對象。因為使用@WebMvcTest方式測試,不會加載整個spring容器,只能測試controller。Controller里面的一些依賴,需要自己去mock。
但是使用@SpringBootTest方式是可以把整個spring容器加載進來的。但不能和@WebMvcTest同時使用
如果要使用@SpringBootTest,則無法使用MockMVC,如果要是用MockMvc需要添加@AutoConfigureMockMvc,如下所示
package com.edu.spring.springboot.controller;import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers;@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class BookControllerTest3 {@Autowiredprivate MockMvc mockMvc;@Testpublic void home() throws Exception {mockMvc.perform(MockMvcRequestBuilders.get("/book/home")).andExpect(MockMvcResultMatchers.status().isOk());mockMvc.perform(MockMvcRequestBuilders.get("/book/home")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().string("book home"));}@Testpublic void show() throws Exception {mockMvc.perform(MockMvcRequestBuilders.get("/book/show").param("id", "400")).andExpect(MockMvcResultMatchers.status().isOk());mockMvc.perform(MockMvcRequestBuilders.get("/book/show").param("id", "400")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().string("book400"));}}?
總結
以上是生活随笔為你收集整理的springboot教程-web(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot教程(一)
- 下一篇: intellij2019.1 破jie不