日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

springboot教程-web(二)

發布時間:2024/9/16 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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
?

spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp

在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=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);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=false

spring.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 log

service啟用了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:

HTTP方法路徑描述
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 null

springboot測試步驟,

? ? 直接在測試類上面添加下面的注解:

????????@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(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。