01-basic

1. Summary

b2aa5676d3464961921f9a155df72722

1. 架构演变之路

image-20231208090402731
  1. 单一应用架构
    • 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化CRUD工作量的数据访问框架(ORM)是关键
  2. 垂直应用架构
    • 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键
  3. 分布式服务架构
    • 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键
  4. 流动计算架构
    • 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键

2. Spring Boot特点

BUILD ANYTHING WITH SPRING BOOT

Spring Boot is the starting point for building all Spring-based applications. Spring Boot is designed to get you up and running as quickly as possible, with minimal upfront configuration of Spring.

  • Get started in seconds using Spring Initializr
  • Build anything: REST APl, Websocket, web, streaming, tasks, and more
  • Simplified security
  • Rich support for SQL and NoSQL
  • Embedded runtime support: Tomcat, Jetty, and Undertow
  • Developer productivity tools such as LiveReload and Auto Restart
  • Curated dependencies that just work
  • Production-ready features such as tracing, metrics, and health status
  • Works in your favorite IDE: Spring Tool Suite, IntelliJ IDEA, and NetBeans

3. new SpringBoot

image-20231208103802729
image-20231208103404146
@SpringBootApplication
@ComponentScan("com.listao")  // 启动类位置乱放,需要@ComponentScan指定扫描路径
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 






1. 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.listao</groupId>
    <artifactId>spring_boot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring_boot</name>
    <description>spring_boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>




 
 
 
 
 
 








<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath>../../spring-boot-dependencies</relativePath>
    </parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <packaging>pom</packaging>
    <name>Spring Boot Starter Parent</name>
    <description>Parent pom providing dependency and plugin management for applications
        built with Maven</description>
    <url>https://projects.spring.io/spring-boot/#/spring-boot-starter-parent</url>




 





 



<?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>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.2.RELEASE</version>
    <packaging>pom</packaging>
    <name>Spring Boot Dependencies</name>
    <description>Spring Boot Dependencies</description>
    <url>https://projects.spring.io/spring-boot/#</url>
    <licenses>
        <license>
            <name>Apache License, Version 2.0</name>
            <url>https://www.apache.org/licenses/LICENSE-2.0</url>
        </license>
    </licenses>
    <scm>
        <url>https://github.com/spring-projects/spring-boot</url>
    </scm>
    <properties>
        <activemq.version>5.15.11</activemq.version>
        <byte-buddy.version>1.10.4</byte-buddy.version>
        <caffeine.version>2.8.0</caffeine.version>
        <cassandra-driver.version>3.7.2</cassandra-driver.version>
        <classmate.version>1.5.1</classmate.version>
        <commons-codec.version>1.13</commons-codec.version>
        <!--  ...  -->
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot</artifactId>
                <version>2.2.2.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>antlr</groupId>
                <artifactId>antlr</artifactId>
                <version>${antlr2.version}</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-access</artifactId>
                <version>${logback.version}</version>
            </dependency>
            <!--  ...  -->
        </dependencies>
    </dependencyManagement>
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.johnzon</groupId>
                    <artifactId>johnzon-maven-plugin</artifactId>
                    <version>${johnzon.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.jetbrains.kotlin</groupId>
                    <artifactId>kotlin-maven-plugin</artifactId>
                    <version>${kotlin.version}</version>
                </plugin>
                <!--  ...  -->
            </plugins>
        </pluginManagement>
    </build>
</project>





 














 









 




















 















2. starter

image-20231208095137737

3. banner

.__  .__          __
|  | |__| _______/  |______    ____
|  | |  |/  ___/\   __\__  \  /  _ \
|  |_|  |\___ \  |  |  / __ \(  <_> )
|____/__/____  > |__| (____  /\____/
             \/            \/

4. 配置文件

2.7.1. Loading YAMLopen in new window

1. yml

  • YAML不是一种标记语言(YAML Ain't Markup Language)的递归缩写。在开发这种语言时,"Yet Another Markup Language"(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为中心,而用反向缩略语重命名
  • 是一种直观的能够被电脑识别的数据序列化格式,是一个可读性高并且容易被人类阅读,容易和脚本语言交互,用来表达资料序列的编程语言。类似于标准通用标记语言(XML)的子集,语法却比XML简单很多
  • 基本原则
    1. 大小写敏感
    2. 使用缩进表示层级关系
    3. 禁止使用tab缩进,只能使用空格键
    4. 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级
    5. 使用#表示注释
    6. 字符串可以不用引号标注
  • 多环境配置
spring:
  profiles:
    active: test
---
spring:
  profiles: dev
server:
  port: 8080
---
spring:
  profiles: test
server:
  port: 8081
  • 参数的设置
person:
  name: zhangsan
  age: 12
  sex:desc: my name is ${person.name},my age is ${person.age}
person.name=zhangsan
person.age=12
person.gender=
person.desc=my name is ${person.name},my age is ${person.age}
# application.yml
person:
  last_name: zhangsan
  age: 12
  sex:email: ooxx@qq.com
  likes:
    - book
    - movie
    - girl
@ConfigurationProperties(prefix = "person")
@Component
@Validated
@Data
public class Person {

    @Value("${person.name}")
    private String lastName;
    @Value("#{1+2}")
    private Integer age;
    private String sex;
    @Email
    private String email;

    private List<String> likes;
}
 















@SpringBootTest
class ApplicationTests {

    @Autowired
    Person person;

    @Test
    void contextLoads() {
        System.out.println("person = " + person);
    }
}

2. @ConfigProp & @Value

  • 松散绑定即下划线和驼峰的转化
@ConfigurationProperties@Value
功能批量注入配置文件中的属性一个个指定
松散绑定(松散语法)支持不支持
SpEL表达式不支持支持
JSR303数据校验支持不支持
复杂类型封装支持不支持

3. 配置文件优先级

  1. /config
  2. /
  3. classpath:/config/
  4. classpath:/

4. 多环境配置

  • 在实际开发中,一套代码可能会被同时部署到开发、测试、生产等多个服务器中
  • 每个环境中诸如数据库密码等这些个性化配置是避免不了的。虽然可以通过自动化运维部署的方式使用外部参数在服务启动时临时替换属性值,但运维成本增高
# application-dev.yaml
server:
  port: 8080
# application-test.yaml
server:
  port: 8081
# application.yaml
spring:
  profiles:
    active: test

5. annotation

  • 元注解的作用:负责注解其他注解,java中定义了四个标准的meta-annotation类型,用来提供对其他annotation类型作说明
  • 这些类型和它们所支持的类在java.lang.annotation包中
    • @Target:用来描述注解的使用范围(注解可以用在什么地方)
    • @Retention:表示需要在什么级别保存该注释信息,描述注解的生命周期
      • Source < Class < Runtime
    • @Documented:说明该注解将被包含在javadoc中
    • @Inherited:说明子类可以继承父类中的该注解

1. 自定义注解

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnno {

    String name() default "ooxx";
}

class Test {

    @MyAnno(name = "hhhh")
    private String name;

    @MyAnno
    public void ooxx() {

    }
}
 
 
 















6. web

  • SpringBoot非常适合用于开发web应用程序。可以使用嵌入式Tomcat、Jetty、Undertow来创建一个独立(self-contained)的HTTP服务器。大多数web应用程序使用spring-boot-starter-web模块来快速搭建和运行,也可以选择使用spring-boot-starter-webflux模块来构建响应式(reactive)web应用程序
  • spring-boot-autoconfigure-2.2.2.RELEASE.jar => spring.factories
# 最重要,自动装配
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# ...

2. integration

1. Servlet

package com.listao.spring_boot.servlet;

@WebServlet(name = "myServlet", urlPatterns = "/servlet")
public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("MyServlet ......");
        super.doGet(req, resp);
    }
}


 



 




@SpringBootApplication
@ServletComponentScan
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    // 将自定义MyServlet添加到IOC,当配置了urlMappings后,MyServlet上的`@WebServlet`的`urlPatterns`不再生效
    @Bean
    public ServletRegistrationBean<MyServlet> getServletRegistrationBean() {
        ServletRegistrationBean<MyServlet> bean = new ServletRegistrationBean<>(new MyServlet(), "/servletBean");
        bean.setLoadOnStartup(1);
        return bean;
    }

}

 









 





2. Filter

package com.listao.spring_boot.filter;

@WebFilter(filterName = "MyFilter", urlPatterns = "/filter")
public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("init......");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("filter......");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        System.out.println("destory......");
    }
}


 
 

















3. Listener

  • Listener是Servlet规范定义的一种特殊类,用于监听ServletContext, HttpSession, ServletRequest等域对象的创建、销毁、属性变更事件。在事件发生前、后做一些必要的处理:
    1. 统计在线人数、在线用户
    2. 系统启动时加载初始化信息
    3. 统计网站访问量
    4. 记录用户访问路径
package com.listao.spring_boot.listener;

public class MyHttpSessionListener implements HttpSessionListener {

    public static int online = 0;

    @Override
    public void sessionCreated(HttpSessionEvent hse) {
        System.out.println("创建session......");
        online++;
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent hse) {
        System.out.println("销毁session......");
    }
}


 














@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ServletComponentScan
// @ComponentScan("com.listao")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    // 将自定义MyServlet添加到IOC,当配置了urlMappings之后,MyServlet自己的urlPatterns不再生效
    @Bean
    public ServletRegistrationBean<MyServlet> getServletRegistrationBean() {
        ServletRegistrationBean<MyServlet> bean = new ServletRegistrationBean<>(new MyServlet(), "/servletBean");
        bean.setLoadOnStartup(1);
        return bean;
    }

    @Bean
    public ServletListenerRegistrationBean listenerRegister() {
        ServletListenerRegistrationBean slrb = new ServletListenerRegistrationBean();
        slrb.setListener(new MyHttpSessionListener());
        return slrb;
    }
}




















 



package com.listao.spring_boot.controller;

@Controller
public class MyController {

    @RequestMapping("/session")
    public String session(HttpSession httpSession) {
        httpSession.setAttribute("k", "v");
        return "session";
    }

    @RequestMapping("/online")
    public String online() {
        return "当前在线人数:" + MyHttpSessionListener.online + "人";
    }
}

4. thymeleaf

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
显示消息为:<p th:text="${msg}"></p>
</body>
</html>
    @RequestMapping("/thymeleaf1")
    public String thymeleaf1(Model model) {
        model.addAttribute("msg", "hello, thymeleaf");
        return "01_html";
    }

1. 静态资源加载路径

http://localhost:8080/1.jsopen in new window

  1. classpath:/META-INF/resources/
  2. classpath:/resources/
  3. classpath:/static/
  4. classpath:/public/
image-20231208192158144
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {

	private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
			"classpath:/resources/", "classpath:/static/", "classpath:/public/" };

	/**
	 * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
	 * /resources/, /static/, /public/].
	 */
	private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

	/**
	 * Whether to enable default resource handling.
	 */
	private boolean addMappings = true;

	private final Chain chain = new Chain();

	private final Cache cache = new Cache();

}



 






 











  • 使用了MVC的ResourceHttpRequestHandler,可以添加自定义的WebMvcAutoConfigurationAdapter并重写addResourceHandlers()来修改此行为
package org.springframework.boot.autoconfigure.web.servlet;

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

	// Defined as a nested config to ensure WebMvcConfigurer is not read when not
	// on the classpath
	@Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {

		private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);

		private final ResourceProperties resourceProperties;

		private final WebMvcProperties mvcProperties;

		private final ListableBeanFactory beanFactory;

		private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;

		final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;

		public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
		}

		@Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
			CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
			if (!registry.hasMappingForPattern("/webjars/**")) {
				customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
						.addResourceLocations("classpath:/META-INF/resources/webjars/")
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
			}
			String staticPathPattern = this.mvcProperties.getStaticPathPattern();
			if (!registry.hasMappingForPattern(staticPathPattern)) {
				customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
						.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
			}
		}

    }

}





















 












 






















 







2. webjars

image-20231208192511182
<dependency>
    <groupId>org.webjars.npm</groupId>
    <artifactId>jquery</artifactId>
    <version>3.7.1</version>
</dependency>
image-20231208192841639
image-20231209093409409

3. 首页配置

package org.springframework.boot.autoconfigure.web.servlet;

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

	/**
	 * Configuration equivalent to {@code @EnableWebMvc}.
	 */
	@Configuration(proxyBeanMethods = false)
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {

		private final ResourceProperties resourceProperties;

		private final WebMvcProperties mvcProperties;

		private final ListableBeanFactory beanFactory;

		private final WebMvcRegistrations mvcRegistrations;

		private ResourceLoader resourceLoader;

		public EnableWebMvcConfiguration(ResourceProperties resourceProperties,
				ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
				ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
			this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
			this.beanFactory = beanFactory;
		}

		@Bean
		public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
				FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {

			WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
					new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
					this.mvcProperties.getStaticPathPattern());
			welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
			return welcomePageHandlerMapping;
		}

    }

}

5. SpringMVC

SpringMVC框架是一个mvc的web框架,允许创建@Controller@RestController的bean来处理传入的HTTP请求,控制器中的方法通过@RequestMapping注解映射到HTTP。SpringBoot提供了适用于大多数SpringMVC应用的自动配置

  • 引入ContentNegotiatingViewResolverBeanNameViewResolver的bean视图解析器
  • 支持服务静态资源,包括对WebJar的支持
  • 自动注册Converter(网页传入的数据封装成对象,完成数据类型的转化)、GenericConverterFormatter的bean(将日期转换成规定的格式)
  • 支持HttpMessageConverter,用来转换http请求和响应
  • 自动注册MessageCodesResolver,定义错误代码生成规则
  • 支持静态index.html
  • 支持自定义Favicon
  • 自动使用ConfigurableWebBindingInitializer的bean,将请求树绑定到JavaBean中
package org.springframework.boot.autoconfigure.web.servlet;

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

	// Defined as a nested config to ensure WebMvcConfigurer is not read when not
	// on the classpath
	@Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {

		private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);

		private final ResourceProperties resourceProperties;

		private final WebMvcProperties mvcProperties;

		private final ListableBeanFactory beanFactory;

		private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;

		final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;

		public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
		}

		@Bean
		@ConditionalOnBean(ViewResolver.class)
		@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
		public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
			ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
			resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
			// ContentNegotiatingViewResolver uses all the other view resolvers to locate
			// a view so it should have a high precedence
			resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
			return resolver;
		}

    }

}














































 









1. ContentNegotiatingViewResolver

image-20240225195819295
package org.springframework.web.servlet.view;

public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {

	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
		Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
		// 获得 MediaType 数组
		List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
		if (requestedMediaTypes != null) {
			// 1.. 获得匹配的 View 数组
			List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
			// 筛选最匹配的 View 对象
			View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
			// 如果筛选成功,则返回
			if (bestView != null) {
				return bestView;
			}
		}

		String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
				" given " + requestedMediaTypes.toString() : "";

		// 如果匹配不到 View 对象,则根据 useNotAcceptableStatusCode ,返回 NOT_ACCEPTABLE_VIEW 或 null
		if (this.useNotAcceptableStatusCode) {
			if (logger.isDebugEnabled()) {
				logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
			}
			return NOT_ACCEPTABLE_VIEW;
		}
		else {
			logger.debug("View remains unresolved" + mediaTypeInfo);
			return null;
		}
	}

	private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
			throws Exception {

		// 创建 View 数组
		List<View> candidateViews = new ArrayList<>();
		// 来源一,通过 viewResolvers 解析出 View 数组结果,添加到 candidateViews 中
		if (this.viewResolvers != null) {
			Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
			// 遍历 viewResolvers 数组
			for (ViewResolver viewResolver : this.viewResolvers) {
				// 1... 情况一,获得 View 对象,添加到 candidateViews 中
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					candidateViews.add(view);
				}
				// 情况二,带有拓展后缀的方式,获得 View 对象,添加到 candidateViews 中
				for (MediaType requestedMediaType : requestedMediaTypes) {
					// 获得 MediaType 对应的拓展后缀的数组(默认情况下未配置)
					List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
					// 遍历拓展后缀的数组
					for (String extension : extensions) {
						// 带有拓展后缀的方式,获得 View 对象,添加到 candidateViews 中
						String viewNameWithExtension = viewName + '.' + extension;
						view = viewResolver.resolveViewName(viewNameWithExtension, locale);
						if (view != null) {
							candidateViews.add(view);
						}
					}
				}
			}
		}
		// 来源二,添加 defaultViews 到 candidateViews 中
		if (!CollectionUtils.isEmpty(this.defaultViews)) {
			candidateViews.addAll(this.defaultViews);
		}
		return candidateViews;
	}

	@Override
	protected void initServletContext(ServletContext servletContext) {
		// 扫描所有 ViewResolver 的 Bean 们
		Collection<ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
		// 情况一,如果 viewResolvers 为空,则将 matchingBeans 作为 viewResolvers
		// BeanNameViewResolver、ThymeleafViewResolver、ViewResolverComposite、InternalResourceViewResolver
		if (this.viewResolvers == null) {
			this.viewResolvers = new ArrayList<>(matchingBeans.size());
			for (ViewResolver viewResolver : matchingBeans) {
				if (this != viewResolver) {
					// 排除自己
					this.viewResolvers.add(viewResolver);
				}
			}
		}
		// 情况二,如果 viewResolvers 非空,则和 matchingBeans 进行比对,判断哪些未进行初始化,进行初始化
		else {
			for (int i = 0; i < this.viewResolvers.size(); i++) {
				ViewResolver vr = this.viewResolvers.get(i);
				// 已存在在 matchingBeans 中,说明已经初始化,则直接 continue
				if (matchingBeans.contains(vr)) {
					continue;
				}
				// 不存在在 matchingBeans 中,说明还未初始化,则进行初始化
				String name = vr.getClass().getName() + i;
				obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
			}

		}
		// 排序 viewResolvers 数组
		AnnotationAwareOrderComparator.sort(this.viewResolvers);
		// 设置 cnmFactoryBean 的 servletContext 属性
		this.cnmFactoryBean.setServletContext(servletContext);
	}

}












 



































 






























 
































  • 上面代码分析,SpringBoot在IOC中去找视图解析器。因此,给容器自定义添加视图解析器
	// 自定义视图解析器
    @Bean
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    // 一个静态内部类,视图解析器需要实现ViewResolver接口
    private static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String s, Locale locale) throws Exception {
            return null;
        }
    }
  • 想保留SpringBoot的MVC功能,并且需要添加其他MVC配置(interceptor、formatter、视图控制器等),可以添加自定义的WebMvcConfigurerAdapter类型的@Configuration类,但不能带@EnableWebMvc注解
  • 如果想自定义 RequestMappingHandlerMappingRequestMappingHandlerAdapter或者ExceptionHandlerExceptionResolver实例,可以声明一个WebMvcRegistrationsAdapter实例来提供这些组件
  • 想完全掌控SpringMVC,可以添加自定义注解了@EnableWebMvc@Configuration配置类
package com.listao.spring_boot.config;

@Configuration
class MyConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/ooxx").setViewName("01_html");
        registry.addViewController("/login.html").setViewName("login");
    }
}

6. JDBC

  • SpringFramework为SQL数据库提供了广泛的支持。从直接使用JdbcTemplate进行JDBC访问到完全的对象关系映射(Object Relational Mapping)技术。Spring_Data提供了更多级别的功能,直接从接口创建Repository实现,并约定使用方法名生成查询
<!-- springboot_data -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
spring:
  messages:
    basename: i18n/login
  datasource:
    username: root
    password: ******
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://ip:port/mca?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8
@SpringBootTest
class DBTests {

    @Autowired
    DataSource dataSource;

    @Test
    void jdbc() throws Exception {
        System.out.println("dataSource = " + dataSource.getClass());
        Connection connection = dataSource.getConnection();
        System.out.println("connection = " + connection);
        connection.close();
    }

}
dataSource = class com.zaxxer.hikari.HikariDataSource
2023-12-09 18:12:35.436  INFO 6918 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-12-09 18:12:35.438  WARN 6918 --- [           main] com.zaxxer.hikari.util.DriverDataSource  : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
2023-12-09 18:12:36.060  INFO 6918 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
connection = HikariProxyConnection@1656197285 wrapping com.mysql.cj.jdbc.ConnectionImpl@7c52fc81
 




1. CRUD

  1. 有了数据源com.zaxxer.hikari.HikariDataSource,然后拿到数据库连接java.sql.Connection,使用连接和原生的JDBC语句来操作数据库
  2. 即使不使用第三方数据库操作框架(eg:MyBatis等),Spring本身也对原生的JDBC做了轻量级的封装
    • org.springframework.jdbc.core.JdbcTemplate。CRUD方法都在JdbcTemplate
  3. SpringBoot不仅提供了默认的数据源,同时默认已经配置好了JdbcTemplate放在了容器中,程序员只需自己注入即可使用
  4. JdbcTemplate的自动配置原理是依赖org.springframework.boot.autoconfigure.jdbc包下的org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
@RestController
public class JdbcController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @GetMapping("/selAll")
    public List<Map<String, Object>> selAll() {
        String sql = "select * from tmp";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }

    @GetMapping("/addTmp")
    public String addTmp() {
        String sql = "insert into tmp(id, name) values(1111, 'zhangsan')";
        jdbcTemplate.update(sql);
        return "success";
    }

    @GetMapping("/updTmp/{id}")
    public String updTmp(@PathVariable("id") Integer id) {
        String sql = "update tmp set name=? where id = " + id;
        String name = "list";
        jdbcTemplate.update(sql, name);
        return "update success";
    }

    @GetMapping("/delTmp/{id}")
    public String delTmp(@PathVariable("id") Integer id) {
        String sql = "delete from tmp where id = " + id;
        jdbcTemplate.update(sql);
        return "delete success";
    }
}




 






























7. DruidDataSource

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>
spring:
  messages:
    basename: i18n/login
  datasource:
    username: root
    password: ******
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://ip:port/mca?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8
    type: com.alibaba.druid.pool.DruidDataSource








 
2023-12-15 15:41:04.631  INFO 68604 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
connection = com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@21e45a6f
 

1. SC

package org.springframework.boot.autoconfigure.jdbc;

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {

    public DataSourceAutoConfiguration() {
    }

    static class EmbeddedDatabaseCondition extends SpringBootCondition {
        private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();

        EmbeddedDatabaseCondition() {
        }

        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            ConditionMessage.Builder message = ConditionMessage.forCondition("EmbeddedDataSource", new Object[0]);
            if (this.anyMatches(context, metadata, new Condition[]{this.pooledCondition})) {
                return ConditionOutcome.noMatch(message.foundExactly("supported pooled data source"));
            } else {
                EmbeddedDatabaseType type = EmbeddedDatabaseConnection.get(context.getClassLoader()).getType();
                return type == null ? ConditionOutcome.noMatch(message.didNotFind("embedded database").atAll()) : ConditionOutcome.match(message.found("embedded database").items(new Object[]{type}));
            }
        }
    }

    static class PooledDataSourceAvailableCondition extends SpringBootCondition {
        PooledDataSourceAvailableCondition() {
        }

        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            ConditionMessage.Builder message = ConditionMessage.forCondition("PooledDataSource", new Object[0]);
            return DataSourceBuilder.findType(context.getClassLoader()) != null ? ConditionOutcome.match(message.foundExactly("supported DataSource")) : ConditionOutcome.noMatch(message.didNotFind("supported DataSource").atAll());
        }
    }

    static class PooledDataSourceCondition extends AnyNestedCondition {
        PooledDataSourceCondition() {
            super(ConfigurationPhase.PARSE_CONFIGURATION);
        }

        @Conditional({PooledDataSourceAvailableCondition.class})
        static class PooledDataSourceAvailable {
            PooledDataSourceAvailable() {
            }
        }

        @ConditionalOnProperty(
            prefix = "spring.datasource",
            name = {"type"}
        )
        static class ExplicitType {
            ExplicitType() {
            }
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({PooledDataSourceCondition.class})
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    @Import({DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class})
    protected static class PooledDataSourceConfiguration {
        protected PooledDataSourceConfiguration() {
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @Conditional({EmbeddedDatabaseCondition.class})
    @ConditionalOnMissingBean({DataSource.class, XADataSource.class})
    @Import({EmbeddedDataSourceConfiguration.class})
    protected static class EmbeddedDatabaseConfiguration {
        protected EmbeddedDatabaseConfiguration() {
        }
    }

}













 
















 









 


























 










 





2. config

spring:
  datasource:
    username: root
    password: ******
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://ip:port/mca?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false






 
 
 
 
 
 
 
 
 
 
 
package com.listao.spring_boot.config;

@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
}





 


 


@Test
void druid() throws Exception{
    System.out.println(dataSource.getClass());
    DruidDataSource druidDataSource = (DruidDataSource) dataSource;
    System.out.println("druidDataSource = " + druidDataSource);
    System.out.println("InitialSize() = " + druidDataSource.getInitialSize());
    System.out.println("MaxActive() = " + druidDataSource.getMaxActive());
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
    connection.close();
}
class com.alibaba.druid.pool.DruidDataSource
druidDataSource = {
	CreateTime:"2023-12-15 16:13:39",
	ActiveCount:0,
	PoolingCount:0,
	CreateCount:0,
	DestroyCount:0,
	CloseCount:0,
	ConnectCount:0,
	Connections:[
	]
}

[
]
InitialSize() = 5
MaxActive() = 20
package org.springframework.boot.autoconfigure.http;

@ConfigurationProperties(
    prefix = "spring.http"
)
public class HttpProperties {
    private boolean logRequestDetails;
    private final Encoding encoding = new Encoding();
}
package org.springframework.boot.autoconfigure.jdbc;

@ConfigurationProperties(
    prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
    private ClassLoader classLoader;
    private String name;
    private boolean generateUniqueName;
    private Class<? extends DataSource> type;
    private String driverClassName;
    private String url;
    private String username;
    private String password;
    private String jndiName;
    private DataSourceInitializationMode initializationMode;
    private String platform;
    private List<String> schema;
    private String schemaUsername;
    private String schemaPassword;
    private List<String> data;
    private String dataUsername;
    private String dataPassword;
    private boolean continueOnError;
    private String separator;
    private Charset sqlScriptEncoding;
    private EmbeddedDatabaseConnection embeddedDatabaseConnection;
    private Xa xa;
    private String uniqueName;
}









 
 
 
 
 
















package com.alibaba.druid.pool;

public class DruidDataSource extends DruidAbstractDataSource
        implements DruidDataSourceMBean, ManagedDataSource, Referenceable, Closeable, Cloneable, ConnectionPoolDataSource, MBeanRegistration {

    private static final Log LOG = LogFactory.getLog(DruidDataSource.class);
    private static final long serialVersionUID = 1L;
    private volatile long recycleErrorCount;
    private long connectCount;
    private long closeCount;
    private volatile long connectErrorCount;
    private long recycleCount;
    private long removeAbandonedCount;
    private long notEmptyWaitCount;
    private long notEmptySignalCount;
    private long notEmptyWaitNanos;
    private int keepAliveCheckCount;
    private int activePeak;

    public void configFromPropety(Properties properties) {
        {
            String property = properties.getProperty("druid.name");
            if (property != null) {
                this.setName(property);
            }
        }
        {
            String property = properties.getProperty("druid.url");
            if (property != null) {
                this.setUrl(property);
            }
        }
        {
            String property = properties.getProperty("druid.username");
            if (property != null) {
                this.setUsername(property);
            }
        }
        {
            String property = properties.getProperty("druid.password");
            if (property != null) {
                this.setPassword(property);
            }
        }
        // ...
        {
            String property = properties.getProperty("druid.initialSize");
            if (property != null && property.length() > 0) {
                try {
                    int value = Integer.parseInt(property);
                    this.setInitialSize(value);
                } catch (NumberFormatException e) {
                    LOG.error("illegal property 'druid.initialSize'", e);
                }
            }
        }
        // ...
        {
            String property = properties.getProperty("druid.maxActive");
            if (property != null && property.length() > 0) {
                try {
                    int value = Integer.parseInt(property);
                    this.setMaxActive(value);
                } catch (NumberFormatException e) {
                    LOG.error("illegal property 'druid.maxActive'", e);
                }
            }
        }
        // ...
    }
}





















 





 





 





 






 











 












public abstract class DruidAbstractDataSource extends WrapperAdapter implements DruidAbstractDataSourceMBean, DataSource, DataSourceProxy, Serializable {
    private static final long                          serialVersionUID                          = 1L;
    private final static Log                           LOG                                       = LogFactory.getLog(DruidAbstractDataSource.class);

    public final static int                            DEFAULT_INITIAL_SIZE                      = 0;
    public final static int                            DEFAULT_MAX_ACTIVE_SIZE                   = 8;
    public final static int                            DEFAULT_MAX_IDLE                          = 8;
    public final static int                            DEFAULT_MIN_IDLE                          = 0;
    public final static int                            DEFAULT_MAX_WAIT                          = -1;
    public final static String                         DEFAULT_VALIDATION_QUERY                  = null;                                                //
    public final static boolean                        DEFAULT_TEST_ON_BORROW                    = false;
    public final static boolean                        DEFAULT_TEST_ON_RETURN                    = false;
    public final static boolean                        DEFAULT_WHILE_IDLE                        = true;
    public static final long                           DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = 60 * 1000L;
    public static final long                           DEFAULT_TIME_BETWEEN_CONNECT_ERROR_MILLIS = 500;
    public static final int                            DEFAULT_NUM_TESTS_PER_EVICTION_RUN        = 3;

    public static final long                           DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS    = 1000L * 60L * 30L;
    public static final long                           DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS    = 1000L * 60L * 60L * 7;
    public static final long                           DEFAULT_PHY_TIMEOUT_MILLIS                = -1;

    protected volatile boolean                         defaultAutoCommit                         = true;
    protected volatile Boolean                         defaultReadOnly;
    protected volatile Integer                         defaultTransactionIsolation;
    protected volatile String                          defaultCatalog                            = null;

    protected String                                   name;

    protected volatile String                          username;
    protected volatile String                          password;
    protected volatile String                          jdbcUrl;
    protected volatile String                          driverClass;


    protected volatile int                             initialSize                               = DEFAULT_INITIAL_SIZE;
    protected volatile int                             maxActive                                 = DEFAULT_MAX_ACTIVE_SIZE;
    protected volatile int                             minIdle                                   = DEFAULT_MIN_IDLE;
    protected volatile int                             maxIdle                                   = DEFAULT_MAX_IDLE;
    protected volatile long                            maxWait                                   = DEFAULT_MAX_WAIT;
    protected int                                      notFullTimeoutRetryCount                  = 0;

    // ...
}




























 
 
 
 


 
 







3. 监控

  • Druid数据源还具有监控的功能,并提供了一个web界面方便用户进行查看
  • 加入log4j的日志依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>
spring:
  datasource:
    username: root
    password: ******
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://ip:port/mca?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL
    type: com.alibaba.druid.pool.DruidDataSource
    # druid数据源专有配置。SpringBoot默认是不注入这些属性值,需要自己绑定
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    # 配置监控统计拦截的filters
    #   stat:监控统计、log4j:日志记录、wall:防御sql注入
    # 报错:`java.lang.ClassNotFoundException: org.apache.log4j.Priority`则导入log4j依赖
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500






 



















package com.listao.spring_boot.config;

@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

    @Bean
    public ServletRegistrationBean druidServletRegistrationBean() {
        ServletRegistrationBean<Servlet> servletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        Map<String, String> initParams = new HashMap<>();
        initParams.put("loginUsername", "admin");
        initParams.put("loginPassword", "admin");
        // 表示只有本机可以访问
        // initParams.put("allow", "localhost");

        // deny:Druid 后台拒绝谁访问
        // initParams.put("msb", "192.168.1.20"); // 表示禁止此ip访问

        // 为空或者为null时,表示允许所有访问
        initParams.put("allow", "");

        servletRegistrationBean.setInitParameters(initParams);
        return servletRegistrationBean;
    }

    // 配置Druid监控之web监控的filter
    // WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new WebStatFilter());

        // exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
        Map<String, String> initParams = new HashMap<>();
        initParams.put("exclusions", "*.js,*.css,/druid/*");
        bean.setInitParameters(initParams);

        // '/*':表示过滤所有请求
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }
}








 






 
 






















 



 



image-20231210110849855

8. 多数据源配置

1. DataSourceAspect

package com.listao.spring_boot.multiDB;

@Aspect
@Order(1)
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.listao.spring_boot.multiDB.DataSource)")
    public void dsPointCut() {
    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        // 1.
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource != null) {
            // 2.
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }
        try {
            return point.proceed();
        } finally {
            // 销毁数据源,在执行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
}
















 


 









2. @DataSource

  • 设置拦截数据源的注解,可以设置在具体的类上,或者在具体的方法上
package com.listao.spring_boot.multiDB;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

    /**
     * 切换数据源
     */
    DataSourceType value() default DataSourceType.REMOTE;
}





 






package com.listao.spring_boot.multiDB;

public enum DataSourceType {
    REMOTE,
    LOCAL
}



 
 

3. DynamicDataSourceContextHolder

package com.listao.spring_boot.multiDB;

public class DynamicDataSourceContextHolder {

    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSourceType(String dataSourceType) {
        System.out.printf("切换到{%s}数据源", dataSourceType);
        System.out.println();
        CONTEXT_HOLDER.set(dataSourceType);
    }

    /**
     * 获取数据源变量
     */
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空数据源变量
     */
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}







 





















4. DataSourceConfig

package com.listao.spring_boot.multiDB;

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.remote")
    public DataSource remoteDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.local")
    public DataSource localDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource remoteDataSource, DataSource localDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.REMOTE.name(), remoteDataSource);
        targetDataSources.put(DataSourceType.LOCAL.name(), localDataSource);
        // (默认数据源, 数据源组)
        return new DynamicDataSource(remoteDataSource, targetDataSources);
    }
}






 





 




 









spring:
  datasource:
    local:
      username: root
      password: ******
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://ip:port/mca?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL
    remote:
      username: root
      password: ******
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://ip:port/kgbase?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL


 




 




5. DynamicDataSource

  • 动态切换数据源,创建一个AbstractRoutingDataSource的子类,重写determineCurrentLookupKey()
package com.listao.spring_boot.multiDB;

public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * @param defaultTargetDataSource 默认目标数据源
     * @param targetDataSources       切换的数据源
     */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        // afterPropertiesSet()将targetDataSources的属性写入resolvedDataSources中的
        super.afterPropertiesSet();
    }

    /**
     * 根据Key获取数据源的信息
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}








 










 



1. AbstractRoutingDataSource
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

	@Nullable
	private Map<Object, Object> targetDataSources;

	@Nullable
	private Object defaultTargetDataSource;

	@Nullable
	private Map<Object, DataSource> resolvedDataSources;

	@Nullable
	private DataSource resolvedDefaultDataSource;

	@Override
	public void afterPropertiesSet() {
		if (this.targetDataSources == null) {
			throw new IllegalArgumentException("Property 'targetDataSources' is required");
		}
		this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
		this.targetDataSources.forEach((key, value) -> {
			Object lookupKey = resolveSpecifiedLookupKey(key);
			DataSource dataSource = resolveSpecifiedDataSource(value);
			// 1.
			this.resolvedDataSources.put(lookupKey, dataSource);
		});
		if (this.defaultTargetDataSource != null) {
			this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
		}
	}

	protected Object resolveSpecifiedLookupKey(Object lookupKey) {
		return lookupKey;
	}

	@Override
	public Connection getConnection() throws SQLException {
	    // 1..
		return determineTargetDataSource().getConnection();
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		return determineTargetDataSource().getConnection(username, password);
	}

	/**
	 * Retrieve the current target DataSource. Determines the
	 * {@link #determineCurrentLookupKey() current lookup key}, performs
	 * a lookup in the {@link #setTargetDataSources targetDataSources} map,
	 * falls back to the specified
	 * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
	 * @see #determineCurrentLookupKey()
	 */
	protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		// 1... DynamicDataSource
		Object lookupKey = determineCurrentLookupKey();
		// 2..
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
			dataSource = this.resolvedDefaultDataSource;
		}
		if (dataSource == null) {
			throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
		}
		return dataSource;
	}

}



 


 

















 







 





 


















 

 










6. 循环依赖bug

  • 启动项目过程会发生循环依赖,直接修改启动类@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@ServletComponentScan
@ComponentScan("com.listao")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
 








9. mybatis

<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Tmp {
    private String id;
    private String name;
    private Date updtime;
}
@Mapper
@Repository
public interface TmpMapper {

    List<Tmp> selAll();

    Tmp selTmpById(Integer empno);

    Integer addTmp(Tmp emp);

    Integer updTmp(Tmp emp);

    Integer delTmp(Integer empno);
}
<?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.listao.spring_boot.mapper.TmpMapper">

    <select id="selAll" resultType="Tmp">
        select *
        from tmp
    </select>

    <select id="selTmpById" resultType="Tmp">
        select *
        from tmp
        where id = #{id}
    </select>

    <insert id="addTmp" parameterType="Tmp">
        insert into tmp (id, name)
        values (#{id}, #{name})
    </insert>

    <update id="updTmp" parameterType="Tmp">
        update tmp
        set name=#{name}
        where id = #{id}
    </update>

    <delete id="delTmp" parameterType="int">
        delete
        from tmp
        where id = #{id}
    </delete>
</mapper>
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.listao.spring_boot.entity
@SpringBootTest
class MybatisTests {

    @Autowired
    TmpMapper tmpMapper;

    @Test
    void selAll() {
        List<Tmp> tmps = tmpMapper.selAll();
        System.out.println("tmps = " + tmps);
    }

    // 根据id选择用户
    @Test
    void selTmpById() {
        Tmp tmp = tmpMapper.selTmpById(1234);
        System.out.println("tmp = " + tmp);
    }

    // 添加一个用户
    @Test
    void addEmp() {
        Integer row = tmpMapper.addTmp(new Tmp("1", "ooxx", new Date()));
        System.out.println("row = " + row);
    }

    // 修改一个用户
    @Test
    void updEmp() {
        Integer upd = tmpMapper.updTmp(new Tmp("1", "upd", new Date()));
        System.out.println("upd = " + upd);
    }

    // 根据id删除用户
    @Test
    void deleteEmp() {
        Integer row = tmpMapper.delTmp(1234);
        System.out.println("row = " + row);
    }

}