既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
DefaultErrorAttributes
先来处理异常,它主要功能把异常信息保存到request域,并且返回null。
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
...
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
this.storeErrorAttributes(request, ex);
return null;
}
private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
request.setAttribute(ERROR_ATTRIBUTE, ex);//把异常信息保存到request域
}
...
}
- 默认没有任何解析器(上图的
HandlerExceptionResolverComposite
)能处理异常,所以最后异常会被抛出。 - 最终底层就会转发
/error
请求。会被底层的BasicErrorController
处理。
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//如果/template/error内没有4\*\*.html或5\*\*.html,
//modelAndView为空,最终还是返回viewName为error的modelAndView
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
...
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
//渲染页面
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
...
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
...
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
...
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
...
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
//找出合适error的View,如果/template/error内没有4\*\*.html或5\*\*.html,
//将会返回默认异常页面ErrorMvcAutoConfiguration.StaticView
//这里按需深究代码吧!
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
...
}
...
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
//看下面代码块的StaticView的render块
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
...
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class, WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {
...
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {
//将创建一个名为error的系统默认异常页面View的Bean
private final StaticView defaultErrorView = new StaticView();
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
// If the user adds @EnableWebMvc then the bean name view resolver from
// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
@Bean
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
private static class StaticView implements View {
private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
private static final Log logger = LogFactory.getLog(StaticView.class);
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (response.isCommitted()) {
String message = getMessage(model);
logger.error(message);
return;
}
response.setContentType(TEXT_HTML_UTF8.toString());
StringBuilder builder = new StringBuilder();
Object timestamp = model.get("timestamp");
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
response.setContentType(getContentType());
}
//系统默认异常页面html代码
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
"<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
.append("<div id='created'>").append(timestamp).append("</div>")
.append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
.append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
if (message != null) {
builder.append("<div>").append(htmlEscape(message)).append("</div>");
}
if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
response.getWriter().append(builder.toString());
}
private String htmlEscape(Object input) {
return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null;
}
private String getMessage(Map<String, ?> model) {
Object path = model.get("path");
String message = "Cannot render error page for request [" + path + "]";
if (model.get("message") != null) {
message += " and exception [" + model.get("message") + "]";
}
message += " as the response has already been committed.";
message += " As a result, the response may have the wrong status code.";
return message;
}
@Override
public String getContentType() {
return "text/html";
}
}
}
55、错误处理-【源码流程】几种异常处理原理
-
自定义错误页
- error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
-
@ControllerAdvice
+@ExceptionHandler
处理全局异常;底层是ExceptionHandlerExceptionResolver
支持的
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({ArithmeticException.class,NullPointerException.class}) //处理异常
public String handleArithException(Exception e){
log.error("异常是:{}",e);
return "login"; //视图地址
}
}
@ResponseStatus
+自定义异常 ;底层是ResponseStatusExceptionResolver
,把responseStatus注解的信息底层调用response.sendError(statusCode, resolvedReason)
,tomcat发送的/error
@ResponseStatus(value= HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserTooManyException extends RuntimeException {
public UserTooManyException(){
}
public UserTooManyException(String message){
super(message);
}
}
@Controller
public class TableController {
@GetMapping("/dynamic\_table")
public String dynamic\_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
//表格内容的遍历
List<User> users = Arrays.asList(new User("zhangsan", "123456"),
new User("lisi", "123444"),
new User("haha", "aaaaa"),
new User("hehe ", "aaddd"));
model.addAttribute("users",users);
if(users.size()>3){
throw new UserTooManyException();//抛出自定义异常
}
return "table/dynamic\_table";
}
}
- Spring自家异常如
org.springframework.web.bind.MissingServletRequestParameterException
,DefaultHandlerExceptionResolver
处理Spring自家异常。 -
response.sendError(HttpServletResponse.SC_BAD_REQUEST/*400*/, ex.getMessage());
- 自定义实现
HandlerExceptionResolver
处理异常;可以作为默认的全局异常处理规则
@Order(value= Ordered.HIGHEST_PRECEDENCE) //优先级,数字越小优先级越高
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
try {
response.sendError(511,"我喜欢的错误");
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
ErrorViewResolver
实现自定义处理异常response.sendError()
,error请求就会转给controller。- 你的异常没有任何人能处理,tomcat底层调用
response.sendError()
,error请求就会转给controller。 basicErrorController
要去的页面地址是ErrorViewResolver
。
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
...
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
Map<String, Object> model) {
//这里用到ErrorViewResolver接口
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
...
}
@FunctionalInterface
public interface ErrorViewResolver {
ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model);
}
56、原生组件注入-原生注解与Spring方式注入
官方文档 - Servlets, Filters, and listeners
使用原生的注解
@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("66666");
}
}
@Slf4j
@WebFilter(urlPatterns={"/css/\*","/images/\*"}) //my
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter初始化完成");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("MyFilter工作");
chain.doFilter(request,response);
}
@Override
public void destroy() {
log.info("MyFilter销毁");
}
}
@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("MySwervletContextListener监听到项目初始化完成");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("MySwervletContextListener监听到项目销毁");
}
}
最后还要在主启动类添加注解@ServletComponentScan
@ServletComponentScan(basePackages = "com.lun")//
@SpringBootApplication(exclude = RedisAutoConfiguration.class)
public class Boot05WebAdminApplication {
public static void main(String[] args) {
SpringApplication.run(Boot05WebAdminApplication.class, args);
}
}
Spring方式注入
ServletRegistrationBean
, FilterRegistrationBean
, and ServletListenerRegistrationBean
@Configuration(proxyBeanMethods = true)
public class MyRegistConfig {
@Bean
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet,"/my","/my02");
}
@Bean
public FilterRegistrationBean myFilter(){
MyFilter myFilter = new MyFilter();
// return new FilterRegistrationBean(myFilter,myServlet());
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/\*"));
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener(){
MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
return new ServletListenerRegistrationBean(mySwervletContextListener);
}
}
57、原生组件注入-【源码分析】DispatcherServlet注入原理
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
配置类
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
/\*
\* The bean name for a DispatcherServlet that will be mapped to the root URL "/"
\*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/\*
\* The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
\*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
//创建DispatcherServlet类的Bean
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
//注册DispatcherServlet类
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
...
}
DispatcherServlet
默认映射的是 /
路径,可以通过在配置文件修改spring.mvc.servlet.path=/mvc
。
58、嵌入式Servlet容器-【源码分析】切换web服务器与定制化
-
默认支持的WebServer
Tomcat
,Jetty
, orUndertow
。ServletWebServerApplicationContext
容器启动寻找ServletWebServerFactory
并引导创建服务器。
-
原理
- SpringBoot应用启动发现当前是Web应用,web场景包-导入tomcat。
- web应用会创建一个web版的IOC容器
ServletWebServerApplicationContext
。 ServletWebServerApplicationContext
启动的时候寻找ServletWebServerFactory
(Servlet 的web服务器工厂——>Servlet 的web服务器)。- SpringBoot底层默认有很多的WebServer工厂(
ServletWebServerFactoryConfiguration
内创建Bean),如:TomcatServletWebServerFactory
JettyServletWebServerFactory
UndertowServletWebServerFactory
- 底层直接会有一个自动配置类
ServletWebServerFactoryAutoConfiguration
。 ServletWebServerFactoryAutoConfiguration
导入了ServletWebServerFactoryConfiguration
(配置类)。ServletWebServerFactoryConfiguration
根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有TomcatServletWebServerFactory
TomcatServletWebServerFactory
创建出Tomcat服务器并启动;TomcatWebServer
的构造器拥有初始化方法initialize——this.tomcat.start();
- 内嵌服务器,与以前手动把启动服务器相比,改成现在使用代码启动(tomcat核心jar包存在)。
Spring Boot默认使用Tomcat服务器,若需更改其他服务器,则修改工程pom.xml:
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
定制Servlet容器
- 实现
WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
-
- 把配置文件的值和
ServletWebServerFactory
进行绑定
- 把配置文件的值和
- 修改配置文件
server.xxx
- 直接自定义
ConfigurableServletWebServerFactory
xxxxxCustomizer
:定制化器,可以改变xxxx的默认规则
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
59、定制化原理-SpringBoot定制化组件的几种方式(小结)
定制化的常见方式
- 修改配置文件
xxxxxCustomizer
- 编写自定义的配置类
xxxConfiguration
+@Bean
替换、增加容器中默认组件,视图解析器 - Web应用 编写一个配置类实现
WebMvcConfigurer
即可定制化web功能 +@Bean
给容器中再扩展一些组件
@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
}
@EnableWebMvc
+WebMvcConfigurer
—@Bean
可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能(高级功能,初学者退避三舍)。- 原理:
WebMvcAutoConfiguration
默认的SpringMVC的自动配置功能类,如静态资源、欢迎页等。- 一旦使用
@EnableWebMvc
,会@Import(DelegatingWebMvcConfiguration.class)
。 DelegatingWebMvcConfiguration
的作用,只保证SpringMVC最基本的使用- 把所有系统中的
WebMvcConfigurer
拿过来,所有功能的定制都是这些WebMvcConfigurer
合起来一起生效。 - 自动配置了一些非常底层的组件,如
RequestMappingHandlerMapping
,这些组件依赖的组件都是从容器中获取如。 public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
。
- 把所有系统中的
WebMvcAutoConfiguration
里面的配置要能生效必须@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
。- @EnableWebMvc 导致了WebMvcAutoConfiguration 没有生效。
- 原理:
原理分析套路
场景starter - xxxxAutoConfiguration
- 导入xxx组件 - 绑定xxxProperties
- 绑定配置文件项。
60、数据访问-数据库场景的自动配置分析与整合测试
导入JDBC场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
接着导入数据库驱动包(MySQL为例)。
<!--默认版本:-->
<mysql.version>8.0.22</mysql.version>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--<version>5.1.49</version>-->
</dependency>
<!--
想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、重新声明版本(maven的属性的就近优先原则)
-->
<properties>
<java.version>1.8</java.version>
<mysql.version>5.1.49</mysql.version>
</properties>
相关数据源配置类
-
DataSourceAutoConfiguration
: 数据源的自动配置。- 修改数据源相关的配置:
spring.datasource
。 - 数据库连接池的配置,是自己容器中没有DataSource才自动配置的。
- 底层配置好的连接池是:
HikariDataSource
。
- 修改数据源相关的配置:
-
DataSourceTransactionManagerAutoConfiguration
: 事务管理器的自动配置。 -
JdbcTemplateAutoConfiguration
:JdbcTemplate
的自动配置,可以来对数据库进行CRUD。- 可以修改前缀为
spring.jdbc
的配置项来修改JdbcTemplate
。 @Bean @Primary JdbcTemplate
:Spring容器中有这个JdbcTemplate
组件,使用@Autowired
。
- 可以修改前缀为
-
JndiDataSourceAutoConfiguration
: JNDI的自动配置。 -
XADataSourceAutoConfiguration
: 分布式事务相关的。
修改配置项
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
单元测试数据源
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
@SpringBootTest
class Boot05WebAdminApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test//用@org.junit.Test会报空指针异常,可能跟JUnit新版本有关
void contextLoads() {
// jdbcTemplate.queryForObject("select \* from account\_tbl")
// jdbcTemplate.queryForList("select \* from account\_tbl",)
Long aLong = jdbcTemplate.queryForObject("select count(\*) from account\_tbl", Long.class);
log.info("记录总数:{}",aLong);
}
}
61、数据访问-自定义方式整合druid数据源
Druid是什么?
它是数据库连接池,它能够提供强大的监控和扩展功能。
Spring Boot整合第三方技术的两种方式:
- 自定义
- 找starter场景
自定义方式
添加依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
配置Druid数据源:
@Configuration
public class MyConfig {
@Bean
@ConfigurationProperties("spring.datasource")//复用配置文件的数据源配置
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
// druidDataSource.setUrl();
// druidDataSource.setUsername();
// druidDataSource.setPassword();
return druidDataSource;
}
}
配置Druid的监控页功能:
-
Druid内置提供了一个
StatViewServlet
用于展示Druid的统计信息。官方文档 - 配置_StatViewServlet配置。这个StatViewServlet
的用途包括:- 提供监控信息展示的html页面
- 提供监控信息的JSON API
-
Druid内置提供一个
StatFilter
,用于统计监控信息。官方文档 - 配置_StatFilter -
WebStatFilter
用于采集web-jdbc关联监控的数据,如SQL监控、URI监控。官方文档 - 配置_配置WebStatFilter -
Druid提供了
WallFilter
,它是基于SQL语义分析来实现防御SQL注入攻击的。官方文档 - 配置 wallfilter
@Configuration
public class MyConfig {
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
//加入监控和防火墙功能功能
druidDataSource.setFilters("stat,wall");
return druidDataSource;
}
/\*\*
\* 配置 druid的监控页功能
\* @return
\*/
@Bean
public ServletRegistrationBean statViewServlet(){
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean =
new ServletRegistrationBean<>(statViewServlet, "/druid/\*");
//监控页账号密码:
registrationBean.addInitParameter("loginUsername","admin");
registrationBean.addInitParameter("loginPassword","123456");
return registrationBean;
}
/\*\*
\* WebStatFilter 用于采集web-jdbc关联监控的数据。
\*/
@Bean
public FilterRegistrationBean webStatFilter(){
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/\*"));
filterRegistrationBean.addInitParameter("exclusions","\*.js,\*.gif,\*.jpg,\*.png,\*.css,\*.ico,/druid/\*");
return filterRegistrationBean;
}
}
62、数据访问-druid数据源starter整合方式
官方文档 - Druid Spring Boot Starter
引入依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
分析自动配置:
- 扩展配置项
spring.datasource.druid
- 自动配置类
DruidDataSourceAutoConfigure
DruidSpringAopConfiguration.class
, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
DruidStatViewServletConfiguration.class
, 监控页的配置。spring.datasource.druid.stat-view-servlet
默认开启。DruidWebStatFilterConfiguration.class
,web监控配置。spring.datasource.druid.web-stat-filter
默认开启。DruidFilterConfiguration.class
所有Druid的filter的配置:
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
配置示例:
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
druid:
aop-patterns: com.atguigu.admin.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: admin
resetEnable: false
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '\*.js,\*.gif,\*.jpg,\*.png,\*.css,\*.ico,/druid/\*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false
63、数据访问-整合MyBatis-配置版
starter的命名方式:
- SpringBoot官方的Starter:
spring-boot-starter-*
- 第三方的:
*-spring-boot-starter
引入依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
配置模式:
- 全局配置文件
SqlSessionFactory
:自动配置好了SqlSession
:自动配置了SqlSessionTemplate
组合了SqlSession
@Import(AutoConfiguredMapperScannerRegistrar.class)
Mapper
: 只要我们写的操作MyBatis的接口标准了@Mapper
就会被自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class) : MyBatis配置项绑定类。
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration{
...
}
@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties{
...
}
配置文件:
spring:
datasource:
username: root
password: 1234
url: jdbc:mysql://localhost:3306/my
driver-class-name: com.mysql.jdbc.Driver
# 配置mybatis规则
mybatis:
config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/\*.xml #sql映射文件位置
mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 由于Spring Boot自动配置缘故,此处不必配置,只用来做做样。-->
</configuration>
Mapper接口:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lun.boot.mapper.UserMapper">
<select id="getUser" resultType="com.lun.boot.bean.User">
select * from user where id=#{id}
</select>
</mapper>
import com.lun.boot.bean.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
public User getUser(Integer id);
}
POJO:
public class User {
private Integer id;
private String name;
//getters and setters...
}
DB:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO\_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO\_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
Controller and Service:
@Controller
public class UserController {
@Autowired
private UserService userService;
@ResponseBody
@GetMapping("/user/{id}")
public User getUser(@PathVariable("id") Integer id){
return userService.getUser(id);
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;//IDEA下标红线,可忽视这红线
public User getUser(Integer id){
return userMapper.getUser(id);
}
}
配置private Configuration configuration;
也就是配置mybatis.configuration
相关的,就是相当于改mybatis全局配置文件中的值。(也就是说配置了mybatis.configuration
,就不需配置mybatis全局配置文件了)
# 配置mybatis规则
mybatis:
mapper-locations: classpath:mybatis/mapper/\*.xml
# 可以不写全局配置文件,所有全局配置文件的配置都放在configuration配置项中了。
# config-location: classpath:mybatis/mybatis-config.xml
configuration:
map-underscore-to-camel-case: true
小结
- 导入MyBatis官方Starter。
- 编写Mapper接口,需
@Mapper
注解。 - 编写SQL映射文件并绑定Mapper接口。
- 在
application.yaml
中指定Mapper配置文件的所处位置,以及指定全局配置文件的信息 (建议:配置在mybatis.configuration
)。
64、数据访问-整合MyBatis-注解配置混合版
你可以通过Spring Initializr添加MyBatis的Starer。
注解与配置混合搭配,干活不累:
@Mapper
public interface UserMapper {
public User getUser(Integer id);
@Select("select \* from user where id=#{id}")
public User getUser2(Integer id);
public void saveUser(User user);
@Insert("insert into user(`name`) values(#{name})")
@Options(useGeneratedKeys = true, keyProperty = "id")
public void saveUser2(User user);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lun.boot.mapper.UserMapper">
<select id="getUser" resultType="com.lun.boot.bean.User">
select * from user where id=#{id}
</select>
<insert id="saveUser" useGeneratedKeys="true" keyProperty="id">
insert into user(`name`) values(#{name})
</insert>
</mapper>
- 简单DAO方法就写在注解上。复杂的就写在配置文件里。
- 使用
@MapperScan("com.lun.boot.mapper")
简化,Mapper接口就可以不用标注@Mapper
注解。
@MapperScan("com.lun.boot.mapper")
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
65、数据访问-整合MyBatisPlus操作数据库
MyBatisPlus是什么
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
添加依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
MybatisPlusAutoConfiguration
配置类,MybatisPlusProperties
配置项绑定。SqlSessionFactory
自动配置好,底层是容器中默认的数据源。mapperLocations
自动配置好的,有默认值classpath*:/mapper/**/*.xml
,这表示任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。 建议以后sql映射文件放在 mapper下。- 容器中也自动配置好了
SqlSessionTemplate
。 @Mapper
标注的接口也会被自动扫描,建议直接@MapperScan("com.lun.boot.mapper")
批量扫描。- MyBatisPlus优点之一:只需要我们的Mapper继承MyBatisPlus的
BaseMapper
就可以拥有CRUD能力,减轻开发工作。
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lun.hellomybatisplus.model.User;
public interface UserMapper extends BaseMapper<User> {
}
66、数据访问-CRUD实验-数据列表展示
使用MyBatis Plus提供的IService
,ServiceImpl
,减轻Service层开发工作。
import com.lun.hellomybatisplus.model.User;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/\*\*
\* Service 的CRUD也不用写了
\*/
public interface UserService extends IService<User> {
//此处故意为空
}
import com.lun.hellomybatisplus.model.User;
import com.lun.hellomybatisplus.mapper.UserMapper;
import com.lun.hellomybatisplus.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
//此处故意为空
}
与下一节联合在一起
67、数据访问-CRUD实验-分页数据展示
与下一节联合在一起
68、数据访问-CRUD实验-删除用户完成
添加分页插件:
@Configuration
public class MyBatisConfig {
/\*\*
\* MybatisPlusInterceptor
\* @return
\*/
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
//这是分页拦截器
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setOverflow(true);
paginationInnerInterceptor.setMaxLimit(500L);
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
}
<table class="display table table-bordered table-striped" id="dynamic-table">
<thead>
<tr>
<th>#</th>
<th>name</th>
<th>age</th>
<th>email</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr class="gradeX" th:each="user: ${users.records}">
<td th:text="${user.id}"></td>
<td>[[${user.name}]]</td>
<td th:text="${user.age}">Win 95+</td>
<td th:text="${user.email}">4</td>
<td>
<a th:href="@{/user/delete/{id}(id=${user.id},pn=${users.current})}"
class="btn btn-danger btn-sm" type="button">删除</a>
</td>
</tr>
</tfoot>
</table>
<div class="row-fluid">
<div class="span6">
<div class="dataTables\_info" id="dynamic-table\_info">
当前第[[${users.current}]]页 总计 [[${users.pages}]]页 共[[${users.total}]]条记录
</div>
</div>
<div class="span6">
<div class="dataTables\_paginate paging\_bootstrap pagination">
<ul>
<li class="prev disabled"><a href="#">← 前一页</a></li>
<li th:class="${num == users.current?'active':''}"
th:each="num:${#numbers.sequence(1,users.pages)}" >
<a th:href="@{/dynamic\_table(pn=${num})}">[[${num}]]</a>
</li>
<li class="next disabled"><a href="#">下一页 → </a></li>
</ul>
</div>
</div>
</div>
#numbers
表示methods for formatting numeric objects.link
@GetMapping("/user/delete/{id}")
public String deleteUser(@PathVariable("id") Long id,
@RequestParam(value = "pn",defaultValue = "1")Integer pn,
RedirectAttributes ra){
userService.removeById(id);
ra.addAttribute("pn",pn);
return "redirect:/dynamic\_table";
}
@GetMapping("/dynamic\_table")
public String dynamic\_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
//表格内容的遍历
//从数据库中查出user表中的用户进行展示
//构造分页参数
Page<User> page = new Page<>(pn, 2);
//调用page进行分页
Page<User> userPage = userService.page(page, null);
model.addAttribute("users",userPage);
return "table/dynamic\_table";
}
69、数据访问-准备阿里云Redis环境
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--导入jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
RedisAutoConfiguration
自动配置类,RedisProperties 属性类 --> spring.redis.xxx是对redis的配置。- 连接工厂
LettuceConnectionConfiguration
、JedisConnectionConfiguration
是准备好的。 - 自动注入了
RedisTemplate<Object, Object>
,xxxTemplate
。 - 自动注入了
StringRedisTemplate
,key,value都是String - 底层只要我们使用
StringRedisTemplate
、RedisTemplate
就可以操作Redis。
外网Redis环境搭建:
- 阿里云按量付费Redis,其中选择经典网络。
- 申请Redis的公网连接地址。
- 修改白名单,允许
0.0.0.0/0
访问。
70、数据访问-Redis操作与统计小实验
相关Redis配置:
spring:
redis:
# url: redis://lfy:Lfy123456@r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com:6379
host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com
port: 6379
password: lfy:Lfy123456
client-type: jedis
jedis:
pool:
max-active: 10
# lettuce:# 另一个用来连接redis的java框架
# pool:
# max-active: 10
# min-idle: 5
测试Redis连接:
@SpringBootTest
public class Boot05WebAdminApplicationTests {
@Autowired
StringRedisTemplate redisTemplate;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Test
void testRedis(){
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set("hello","world");
String hello = operations.get("hello");
System.out.println(hello);
System.out.println(redisConnectionFactory.getClass());
}
}
Redis Desktop Manager:可视化Redis管理软件。
URL统计拦截器:
@Component
public class RedisUrlCountInterceptor implements HandlerInterceptor {
@Autowired
StringRedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
//默认每次访问当前uri就会计数+1
redisTemplate.opsForValue().increment(uri);
return true;
}
}
注册URL统计拦截器:
@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
@Autowired
RedisUrlCountInterceptor redisUrlCountInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(redisUrlCountInterceptor)
.addPathPatterns("/\*\*")
.excludePathPatterns("/","/login","/css/\*\*","/fonts/\*\*","/images/\*\*",
"/js/\*\*","/aa/\*\*");
}
}
Filter、Interceptor 几乎拥有相同的功能?
- Filter是Servlet定义的原生组件,它的好处是脱离Spring应用也能使用。
- Interceptor是Spring定义的接口,可以使用Spring的自动装配等功能。
调用Redis内的统计数据:
@Slf4j
@Controller
public class IndexController {
@Autowired
StringRedisTemplate redisTemplate;
@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
log.info("当前方法是:{}","mainPage");
ValueOperations<String, String> opsForValue =
redisTemplate.opsForValue();
String s = opsForValue.get("/main.html");
String s1 = opsForValue.get("/sql");
model.addAttribute("mainCount",s);
model.addAttribute("sqlCount",s1);
return "main";
}
}
71、单元测试-JUnit5简介
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的JUnit框架有很大的不同。由三个不同子项目的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
- JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。
- JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,JUnit3.x的测试引擎。
注意:
- SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容JUnit4需要自行引入(不能使用JUnit4的功能 @Test)
- JUnit 5’s Vintage已经从
spring-boot-starter-test
从移除。如果需要继续兼容Junit4需要自行引入Vintage依赖:
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
- 使用添加JUnit 5,添加对应的starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- Spring的JUnit 5的基本单元测试模板(Spring的JUnit4的是
@SpringBootTest
+@RunWith(SpringRunner.class)
):
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;//注意不是org.junit.Test(这是JUnit4版本的)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringBootApplicationTests {
@Autowired
private Component component;
@Test
//@Transactional 标注后连接数据库有回滚功能
public void contextLoads() {
Assertions.assertEquals(5, component.getFive());
}
}
Jupiter
英 [ˈdʒuːpɪtə®] 美 [ˈdʒuːpɪtər]
n. 木星(太阳系中最大的行星)
vintage
英 [ˈvɪntɪdʒ] 美 [ˈvɪntɪdʒ]
n. 特定年份(或地方)酿制的酒;酿造年份;采摘葡萄酿酒的期间(或季节);葡萄收获期(或季节)
adj. (指葡萄酒)优质的,上等的,佳酿的;古色古香的(指1917–1930年间制造,车型和品味受人青睐的);(过去某个时期)典型的,优质的;(某人的)最佳作品的
72、单元测试-常用测试注解
- @Test:表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest:表示方法是参数化测试。
- @RepeatedTest:表示方法可重复执行。
- @DisplayName:为测试类或者测试方法设置展示名称。
- @BeforeEach:表示在每个单元测试之前执行。
- @AfterEach:表示在每个单元测试之后执行。
- @BeforeAll:表示在所有单元测试之前执行。
- @AfterAll:表示在所有单元测试之后执行。
- @Tag:表示单元测试类别,类似于JUnit4中的@Categories。
- @Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore。
- @Timeout:表示测试方法运行如果超过了指定时间将会返回错误。
- @ExtendWith:为测试类或测试方法提供扩展类引用。
import org.junit.jupiter.api.\*;
@DisplayName("junit5功能测试类")
public class Junit5Test {
@DisplayName("测试displayname注解")
@Test
void testDisplayName() {
System.out.println(1);
System.out.println(jdbcTemplate);
}
@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
assertTrue(StringUtils.isPalindrome(candidate));
}
@Disabled
@DisplayName("测试方法2")
@Test
void test2() {
System.out.println(2);
}
@RepeatedTest(5)
@Test
void test3() {
System.out.println(5);
}
/\*\*
\* 规定方法超时时间。超出时间测试出异常
\*
\* @throws InterruptedException
\*/
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
@Test
void testTimeout() throws InterruptedException {
Thread.sleep(600);
}
@BeforeEach
void testBeforeEach() {
System.out.println("测试就要开始了...");
}
@AfterEach
void testAfterEach() {
System.out.println("测试结束了...");
}
@BeforeAll
static void testBeforeAll() {
System.out.println("所有测试就要开始了...");
}
@AfterAll
static void testAfterAll() {
System.out.println("所有测试以及结束了...");
}
}
73、单元测试-断言机制
断言Assertion是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是org.junit.jupiter.api.Assertions的静态方法。检查业务逻辑返回的数据是否合理。所有的测试运行结束以后,会有一个详细的测试报告。
JUnit 5 内置的断言可以分成如下几个类别:
简单断言
用来对单个值进行简单的验证。如:
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
@Test
@DisplayName("simple assertion")
public void simple() {
assertEquals(3, 1 + 2, "simple math");
assertNotEquals(3, 1 + 1);
assertNotSame(new Object(), new Object());
Object obj = new Object();
assertSame(obj, obj);
assertFalse(1 > 2);
assertTrue(1 < 2);
assertNull(null);
assertNotNull(new Object());
}
数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等。
@Test
@DisplayName("array assertion")
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
组合断言
assertAll()
方法接受多个 org.junit.jupiter.api.Executable
函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言。
@Test
@DisplayName("assert all")
public void all() {
assertAll("Math",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}
异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用@Rule
注解的ExpectedException
变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows()
,配合函数式编程就可以进行使用。
@Test
@DisplayName("异常测试")
public void exceptionTest() {
ArithmeticException exception = Assertions.assertThrows(
//扔出断言异常
ArithmeticException.class, () -> System.out.println(1 % 0));
}
超时断言
JUnit5还提供了Assertions.assertTimeout()为测试方法设置了超时时间。
@Test
@DisplayName("超时测试")
public void timeoutTest() {
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
快速失败
通过 fail 方法直接使得测试失败。
@Test
@DisplayName("fail")
public void shouldFail() {
fail("This should fail");
}
74、单元测试-前置条件
JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言assertions会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。
前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
@DisplayName("前置条件")
public class AssumptionsTest {
private final String environment = "DEV";
@Test
@DisplayName("simple")
public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD"));
}
@Test
@DisplayName("assume then do")
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"),
() -> System.out.println("In DEV")
);
}
}
assumeTrue
和 assumFalse
确保给定的条件为 true
或 false
,不满足条件会使得测试执行终止。
assumingThat
的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable
对象才会被执行;当条件不满足时,测试执行并不会终止。
75、单元测试-嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested
注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach
和@AfterEach
注解,而且嵌套的层次没有限制。
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
76、单元测试-参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
利用**@ValueSource**等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
- @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
- @NullSource: 表示为参数化测试提供一个null的入参
- @EnumSource: 表示为参数化测试提供一个枚举入参
- @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
- @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现**ArgumentsProvider
**接口,任何外部文件都可以作为它的入参。
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
System.out.println(string);
Assertions.assertTrue(StringUtils.isNotBlank(string));
}
@ParameterizedTest
@MethodSource("method") //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
System.out.println(name);
Assertions.assertNotNull(name);
}
static Stream<String> method() {
return Stream.of("apple", "banana");
}
迁移指南
在进行迁移的时候需要注意如下的变化:
- 注解在
org.junit.jupiter.api
包中,断言在org.junit.jupiter.api.Assertions
类中,前置条件在org.junit.jupiter.api.Assumptions
类中。 - 把
@Before
和@After
替换成@BeforeEach
和@AfterEach
。 - 把
@BeforeClass
和@AfterClass
替换成@BeforeAll
和@AfterAll。 - 把
@Ignore
替换成@Disabled
。 - 把
@Category
替换成@Tag
。 - 把
@RunWith
、@Rule
和@ClassRule
替换成@ExtendWith
。
77、指标监控-SpringBoot Actuator与Endpoint
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
官方文档 - Spring Boot Actuator: Production-ready Features
1.x与2.x的不同:
-
SpringBoot Actuator 1.x
- 支持SpringMVC
- 基于继承方式进行扩展
- 层级Metrics配置
- 自定义Metrics收集
- 默认较少的安全策略
-
SpringBoot Actuator 2.x
- 支持SpringMVC、JAX-RS以及Webflux
- 注解驱动进行扩展
- 层级&名称空间Metrics
- 底层使用MicroMeter,强大、便捷默认丰富的安全策略
如何使用
- 添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 访问
http://localhost:8080/actuator/**
。 - 暴露所有监控信息为HTTP。
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '\*' #以web方式暴露
- 测试例子
- http://localhost:8080/actuator/beans
- http://localhost:8080/actuator/configprops
- http://localhost:8080/actuator/metrics
- http://localhost:8080/actuator/metrics/jvm.gc.pause
- http://localhost:8080/actuator/metrics/endpointName/detailPath
actuator
英 [ˈæktjʊeɪtə] 美 [ˈæktjuˌeɪtər]
n. 致动(促动,激励,调节)器;传动(装置,机构);拖动装置;马达;操作机构;执行机构(元件);(电磁铁)螺线管;操纵装置(阀门);调速控制器;往复运动油(气)缸;作动筒
metric
英 [ˈmetrɪk] 美 [ˈmetrɪk]
adj. 米制的;公制的;按公制制作的;用公制测量的
n. 度量标准;[数学]度量;诗体;韵文;诗韵
78、指标监控-常使用的端点及开启与禁用
常使用的端点
ID | 描述 |
---|---|
auditevents | 暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件 。 |
beans | 显示应用程序中所有Spring Bean的完整列表。 |
caches | 暴露可用的缓存。 |
conditions | 显示自动配置的所有条件信息,包括匹配或不匹配的原因。 |
configprops | 显示所有@ConfigurationProperties 。 |
env | 暴露Spring的属性ConfigurableEnvironment |
flyway | 显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway 组件。 |
health | 显示应用程序运行状况信息。 |
httptrace | 显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository 组件。 |
info | 显示应用程序信息。 |
integrationgraph | 显示Spring integrationgraph 。需要依赖spring-integration-core 。 |
loggers | 显示和修改应用程序中日志的配置。 |
liquibase | 显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase 组件。 |
metrics | 显示当前应用程序的“指标”信息。 |
mappings | 显示所有@RequestMapping 路径列表。 |
scheduledtasks | 显示应用程序中的计划任务。 |
sessions | 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 |
shutdown | 使应用程序正常关闭。默认禁用。 |
startup | 显示由ApplicationStartup 收集的启动步骤数据。需要使用SpringApplication 进行配置BufferingApplicationStartup 。 |
threaddump | 执行线程转储。 |
如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:
ID | 描述 |
---|---|
heapdump | 返回hprof 堆转储文件。 |
jolokia | 通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core 。 |
logfile | 返回日志文件的内容(如果已设置logging.file.name 或logging.file.path 属性)。支持使用HTTPRange 标头来检索部分日志文件的内容。 |
prometheus | 以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus 。 |
其中最常用的Endpoint:
- Health:监控状况
- Metrics:运行时指标
- Loggers:日志记录
Health Endpoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:
- health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告。
- 很多的健康检查默认已经自动配置好了,比如:数据库、redis等。
- 可以很容易的添加自定义的健康检查机制。
Metrics Endpoint
提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到:
- 通过Metrics对接多种监控系统。
- 简化核心Metrics开发。
- 添加自定义Metrics或者扩展已有Metrics。
开启与禁用Endpoints
- 默认所有的Endpoint除过shutdown都是开启的。
- 需要开启或者禁用某个Endpoint。配置模式为
management.endpoint.<endpointName>.enabled = true
management:
endpoint:
beans:
enabled: true
- 或者禁用所有的Endpoint然后手动开启指定的Endpoint。
management:
endpoints:
enabled-by-default: false
endpoint:
beans:
enabled: true
health:
enabled: true
暴露Endpoints
支持的暴露方式
- HTTP:默认只暴露health和info。
- JMX:默认暴露所有Endpoint。
- 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入Spring Security,则会默认配置安全访问规则。
ID | JMX | Web |
---|---|---|
auditevents | Yes | No |
beans | Yes | No |
caches | Yes | No |
conditions | Yes | No |
configprops | Yes | No |
env | Yes | No |
flyway | Yes | No |
health | Yes | Yes |
heapdump | N/A | No |
httptrace | Yes | No |
info | Yes | Yes |
integrationgraph | Yes | No |
jolokia | N/A | No |
logfile | N/A | No |
loggers | Yes | No |
liquibase | Yes | No |
metrics | Yes | No |
mappings | Yes | No |
prometheus | N/A | No |
scheduledtasks | Yes | No |
sessions | Yes | No |
shutdown | Yes | No |
startup | Yes | No |
threaddump | Yes | No |
若要更改公开的Endpoint,请配置以下的包含和排除属性:
Property | Default |
---|---|
management.endpoints.jmx.exposure.exclude | |
management.endpoints.jmx.exposure.include | * |
management.endpoints.web.exposure.exclude | |
management.endpoints.web.exposure.include | info, health |
79、指标监控-定制Endpoint
定制 Health 信息
management:
health:
enabled: true
show-details: always #总是显示详细信息。可显示每个模块的状态信息
通过实现HealthIndicator
接口,或继承MyComHealthIndicator
类。
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 {
@Override
public Health health() {
int errorCode = check(); // perform some specific health check
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
}
/\*
构建Health
Health build = Health.down()
.withDetail("msg", "error service")
.withDetail("code", "500")
.withException(new RuntimeException())
.build();
\*/
@Component
public class MyComHealthIndicator extends AbstractHealthIndicator {
/\*\*
\* 真实的检查方法
\* @param builder
\* @throws Exception
\*/
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
//mongodb。 获取连接进行测试
Map<String,Object> map = new HashMap<>();
// 检查完成
if(1 == 2){
// builder.up(); //健康
builder.status(Status.UP);
map.put("count",1);
map.put("ms",100);
}else {
// builder.down();
builder.status(Status.OUT_OF_SERVICE);
map.put("err","连接超时");
map.put("ms",3000);
}
builder.withDetail("code",100)
.withDetails(map);
}
}
定制info信息
常用两种方式:
- 编写配置文件
info:
appName: boot-admin
version: 2.0.1
mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
mavenProjectVersion: @project.version@
- 编写InfoContributor
import java.util.Collections;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
@Component
public class ExampleInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"));
}
}
http://localhost:8080/actuator/info 会输出以上方式返回的所有info信息
定制Metrics信息
增加定制Metrics:
class MyService{
Counter counter;
public MyService(MeterRegistry meterRegistry){
counter = meterRegistry.counter("myservice.method.running.counter");
}
public void hello() {
counter.increment();
}
}
//也可以使用下面的方式
@Bean
MeterBinder queueSize(Queue queue) {
return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
}
定制Endpoint
@Component
@Endpoint(id = "container")
public class DockerEndpoint {
@ReadOperation
public Map getDockerInfo(){
return Collections.singletonMap("info","docker started...");
}
@WriteOperation
private void restartDocker(){
System.out.println("docker restarted....");
}
}
场景:
- 开发ReadinessEndpoint来管理程序是否就绪。
- 开发LivenessEndpoint来管理程序是否存活。
80、指标监控-Boot Admin Server
官方Github
可视化指标监控
What is Spring Boot Admin?
codecentric’s Spring Boot Admin is a community project to manage and monitor your Spring Boot ® applications. The applications register with our Spring Boot Admin Client (via HTTP) or are discovered using Spring Cloud ® (e.g. Eureka, Consul). The UI is just a Vue.js application on top of the Spring Boot Actuator endpoints.
81、高级特性-Profile环境切换
为了方便多环境适配,Spring Boot简化了profile功能。
- 默认配置文件
application.yaml
任何时候都会加载。 - 指定环境配置文件
application-{env}.yaml
,env
通常替代为test
, - 激活指定环境
- 配置文件激活:
spring.profiles.active=prod
- 命令行激活:
java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
(修改配置文件的任意值,命令行优先)
- 配置文件激活:
- 默认配置与环境配置同时生效
- 同名配置项,profile配置优先
@Profile条件装配功能
@Data
@Component
@ConfigurationProperties("person")//在配置文件中配置
public class Person{
private String name;
private Integer age;
}
application.properties
person:
name: lun
age: 8
public interface Person {
String getName();
Integer getAge();
}
@Profile("test")//加载application-test.yaml里的
@Component
@ConfigurationProperties("person")
@Data
public class Worker implements Person {
private String name;
private Integer age;
}
@Profile(value = {"prod","default"})//加载application-prod.yaml里的
@Component
@ConfigurationProperties("person")
@Data
public class Boss implements Person {
private String name;
private Integer age;
}
application-test.yaml
person:
name: test-张三
server:
port: 7000
application-prod.yaml
person:
name: prod-张三
server:
port: 8000
application.properties
# 激活prod配置文件
spring.profiles.active=prod
@Autowired
private Person person;
@GetMapping("/")
public String hello(){
//激活了prod,则返回Boss;激活了test,则返回Worker
return person.getClass().toString();
}
@Profile还可以修饰在方法上:
class Color {
}
@Configuration
public class MyConfig {
@Profile("prod")
@Bean
public Color red(){
return new Color();
}
@Profile("test")
@Bean
public Color green(){
return new Color();
}
}
可以激活一组:
spring.profiles.active=production
spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
82、高级特性-配置加载优先级
外部化配置
官方文档 - Externalized Configuration
Spring Boot uses a very particular PropertySource
order that is designed to allow sensible overriding of values. Properties are considered in the following order (with values from lower items overriding earlier ones)(1优先级最低,14优先级最高):
- Default properties (specified by setting
SpringApplication.setDefaultProperties
). @PropertySource
annotations on your@Configuration
classes. Please note that such property sources are not added to theEnvironment
until the application context is being refreshed. This is too late to configure certain properties such aslogging.*
andspring.main.*
which are read before refresh begins.- Config data (such as
application.properties
files) - A
RandomValuePropertySource
that has properties only inrandom.*
. - OS environment variables.
- Java System properties (
System.getProperties()
). - JNDI attributes from
java:comp/env
. ServletContext
init parameters.ServletConfig
init parameters.- Properties from
SPRING_APPLICATION_JSON
(inline JSON embedded in an environment variable or system property). - Command line arguments.
properties
attribute on your tests. Available on@SpringBootTest
and the test annotations for testing a particular slice of your application.@TestPropertySource
annotations on your tests.- Devtools global settings properties in the
$HOME/.config/spring-boot
directory when devtools is active.
import org.springframework.stereotype.\*;
import org.springframework.beans.factory.annotation.\*;
@Component
public class MyBean {
@Value("${name}")//以这种方式可以获得配置值
private String name;
// ...
}
- 外部配置源
- Java属性文件。
- YAML文件。
- 环境变量。
- 命令行参数。
- 配置文件查找位置
- classpath 根路径。
- classpath 根路径下config目录。
- jar包当前目录。
- jar包当前目录的config目录。
- /config子目录的直接子目录。
- 配置文件加载顺序:
- 当前jar包内部的
application.properties
和application.yml
。 - 当前jar包内部的
application-{profile}.properties
和application-{profile}.yml
。 - 引用的外部jar包的
application.properties
和application.yml
。 - 引用的外部jar包的
application-{profile}.properties
和application-{profile}.yml
。
- 当前jar包内部的
- 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项。
83、高级特性-自定义starter细节
starter启动原理
- starter的pom.xml引入autoconfigure依赖
#mermaid-svg-OAmFW5eMkvgEHu1N {font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N .error-icon{fill:#552222;}#mermaid-svg-OAmFW5eMkvgEHu1N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-OAmFW5eMkvgEHu1N .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-OAmFW5eMkvgEHu1N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-OAmFW5eMkvgEHu1N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OAmFW5eMkvgEHu1N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OAmFW5eMkvgEHu1N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OAmFW5eMkvgEHu1N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OAmFW5eMkvgEHu1N .marker.cross{stroke:#333333;}#mermaid-svg-OAmFW5eMkvgEHu1N svg{font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OAmFW5eMkvgEHu1N .label{font-family:“trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N .cluster-label text{fill:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N .cluster-label span{color:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N .label text,#mermaid-svg-OAmFW5eMkvgEHu1N span{fill:#333;color:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N .node rect,#mermaid-svg-OAmFW5eMkvgEHu1N .node circle,#mermaid-svg-OAmFW5eMkvgEHu1N .node ellipse,#mermaid-svg-OAmFW5eMkvgEHu1N .node polygon,#mermaid-svg-OAmFW5eMkvgEHu1N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OAmFW5eMkvgEHu1N .node .label{text-align:center;}#mermaid-svg-OAmFW5eMkvgEHu1N .node.clickable{cursor:pointer;}#mermaid-svg-OAmFW5eMkvgEHu1N .arrowheadPath{fill:#333333;}#mermaid-svg-OAmFW5eMkvgEHu1N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OAmFW5eMkvgEHu1N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OAmFW5eMkvgEHu1N .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-OAmFW5eMkvgEHu1N .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-OAmFW5eMkvgEHu1N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OAmFW5eMkvgEHu1N .cluster text{fill:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N .cluster span{color:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OAmFW5eMkvgEHu1N :root{–mermaid-font-family:“trebuchet ms”,verdana,arial,sans-serif;}
starter
autoconfigure
spring-boot-starter
- autoconfigure包中配置使用
META-INF/spring.factories
中EnableAutoConfiguration
的值,使得项目启动加载指定的自动配置类 - 编写自动配置类
xxxAutoConfiguration
->xxxxProperties
-
@Configuration
@Conditional
@EnableConfigurationProperties
@Bean
- …
- 引入starter —
xxxAutoConfiguration
— 容器中放入组件 ----绑定xxxProperties
---- 配置项
自定义starter
收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
.5px;}#mermaid-svg-OAmFW5eMkvgEHu1N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-OAmFW5eMkvgEHu1N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-OAmFW5eMkvgEHu1N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-OAmFW5eMkvgEHu1N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-OAmFW5eMkvgEHu1N .marker.cross{stroke:#333333;}#mermaid-svg-OAmFW5eMkvgEHu1N svg{font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-OAmFW5eMkvgEHu1N .label{font-family:“trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N .cluster-label text{fill:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N .cluster-label span{color:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N .label text,#mermaid-svg-OAmFW5eMkvgEHu1N span{fill:#333;color:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N .node rect,#mermaid-svg-OAmFW5eMkvgEHu1N .node circle,#mermaid-svg-OAmFW5eMkvgEHu1N .node ellipse,#mermaid-svg-OAmFW5eMkvgEHu1N .node polygon,#mermaid-svg-OAmFW5eMkvgEHu1N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-OAmFW5eMkvgEHu1N .node .label{text-align:center;}#mermaid-svg-OAmFW5eMkvgEHu1N .node.clickable{cursor:pointer;}#mermaid-svg-OAmFW5eMkvgEHu1N .arrowheadPath{fill:#333333;}#mermaid-svg-OAmFW5eMkvgEHu1N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-OAmFW5eMkvgEHu1N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-OAmFW5eMkvgEHu1N .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-OAmFW5eMkvgEHu1N .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-OAmFW5eMkvgEHu1N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-OAmFW5eMkvgEHu1N .cluster text{fill:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N .cluster span{color:#333;}#mermaid-svg-OAmFW5eMkvgEHu1N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-OAmFW5eMkvgEHu1N :root{–mermaid-font-family:“trebuchet ms”,verdana,arial,sans-serif;}
starter
autoconfigure
spring-boot-starter
- autoconfigure包中配置使用
META-INF/spring.factories
中EnableAutoConfiguration
的值,使得项目启动加载指定的自动配置类 - 编写自动配置类
xxxAutoConfiguration
->xxxxProperties
-
@Configuration
@Conditional
@EnableConfigurationProperties
@Bean
- …
- 引入starter —
xxxAutoConfiguration
— 容器中放入组件 ----绑定xxxProperties
---- 配置项
自定义starter
收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
[外链图片转存中…(img-vyHRwBZ2-1715905213746)]
[外链图片转存中…(img-9GU02b6x-1715905213746)]
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!