撸了今年阿里、头条和美团的面试,我有一个重要发现.......>>>

第一节

现在开始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(二)相关推荐

  1. 企业级 SpringBoot 教程 (二)Spring Boot配置文件详解

    springboot采纳了建立生产就绪Spring应用程序的观点. Spring Boot优先于配置的惯例,旨在让您尽快启动和运行.在一般情况下,我们不需要做太多的配置就能够让spring boot正 ...

  2. 企业级 SpringBoot 教程 (二十三)异步方法

    创建工程 在pom文件引入相关依赖: 1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency> <groupId>org.springframewo ...

  3. SpringBoot从入门到精通教程(二十七)- @Valid注解用法详解+全局处理器Exception优雅处理参数验证用法

    问题痛点 用 Spring 框架写代码时,写接口类,相信大家对该类的写法非常熟悉.在写接口时要写效验请求参数逻辑,这时候我们会常用做法是写大量的 if 与 if else 类似这样的代码来做判断,如下 ...

  4. SpringBoot教程(二):Maven

    系列文章目录 SpringBoot教程(一):初识SpringBoot SpringBoot教程(二):Maven 目录 系列文章目录 前言 一.什么是Maven? 二.为什么使用Maven? 三.如 ...

  5. 通俗易懂的SpringBoot教程---day1---Springboot入门教程介绍

    通俗易懂的SpringBoot教程-day1-教程介绍 教程介绍: 初级教程: 一. Spring Boot入门 二. Spring Boot配置 三. Spring Boot与日志 四. Sprin ...

  6. 详细SpringBoot教程之入门(一)

    写在前面 鉴于有人留言说想要学习SpringBoot相关的知识,我这里打算写一个SpringBoot系列的相关博文,目标呢是想让看了这一系列博文的同学呢,能够对SpringBoot略窥门径,这一系列的 ...

  7. 详细SpringBoot教程之数据访问

    写在前面 鉴于有人留言说想要学习SpringBoot相关的知识,我这里打算写一个SpringBoot系列的相关博文,目标呢是想让看了这一系列博文的同学呢,能够对SpringBoot略窥门径,这一系列的 ...

  8. Spring Security和Angular教程(二)登录页面

    Spring Security和Angular教程(二) 登录页面 在本节中,我们将继续讨论如何在"单页面应用程序"中使用带有Angular的Spring Security.在这里 ...

  9. 详细SpringBoot教程之配置文件(一)

    写在前面 鉴于有人留言说想要学习SpringBoot相关的知识,我这里打算写一个SpringBoot系列的相关博文,目标呢是想让看了这一系列博文的同学呢,能够对SpringBoot略窥门径,这一系列的 ...

最新文章

  1. 2022-2028年中国丁晴橡胶行业市场深度分析及投资规划分析报告
  2. Linux五种IO模型性能分析
  3. Linux 内核开发 - Eclipse内核开发环境搭建
  4. 主线程如何等待多线程完成 返回数据_多线程基础体系知识清单
  5. ArrayList刷题总结
  6. 后端解决跨域问题---SpringBoot
  7. 查看Linux服务器raid信息笔记整理!
  8. C++如何禁止函数的传值调用
  9. 枚举学习文摘 — 框架设计(第2版) CLR Via C#
  10. Android通过包名启动应用程序
  11. 基于MATLAB绘制双纵坐标轴图
  12. 用Python给喜欢的女孩写一个办公小工具,她说棒极了
  13. Creator Kit: Beginner Code Unity学习记录 (1)
  14. bat for循环_bat教程[285] FOR/F options选项中usebackq的用法
  15. 清华大学出版社计算机绘谱,清华大学出版社-图书详情-《土木与建筑类CAD技能一级(二维计算机绘图)AutoCAD培训教程》...
  16. 华为高姐寄来的新年礼物
  17. 仿京东手机端地址选择四级地址选择
  18. kubernetes中的PV、PVC
  19. Pyghon学习记录
  20. 史上最最靠谱,又双叒叒(ruò,zhuó)简单的基于MSXML的XML解析指南-C++

热门文章

  1. sql server 2005 时间转化获取年,有一个时间如2009-07-15,现在要分别查出年、月、日,...
  2. 放弃广告 反思站长收益来源
  3. 百度不收录你网站的原因
  4. Java——遍历List过程中添加和删除元素的问题(亲测第二种)
  5. 神经网络算法的相关知识
  6. WSGI接口(廖雪峰重点)
  7. 自己动手用C扩展PHP(三)
  8. 体验XHProf(linux版本)
  9. PHP OPCode缓存:APC详细介绍
  10. PHP的chunk_split() 函数把字符串分割为一连串更小的部分