likes
comments
collection
share

面试金字塔尖之springboot

作者站长头像
站长
· 阅读数 76

一、概述

基于 Spring 的全新框架,旨在简化 Spring Framework 应用的初始搭建和开发过程,核心设计思想是“约定优于配置”,使用最少的配置,以最快的速度来启动和运行 Spring 项目。 其特点如下:

  • 遵循“约定优于配置”的原则,使用 Spring Boot 只需要很少的配置或使用默认的配置。
  • 使用 JavaConfig,避免使用 XML 的繁琐。
  • 提供 Starters(启动器),简化 Maven 配置,避免依赖冲突。
  • 提供内嵌 Servlet 容器,可选择内嵌 Tomcat、Jetty 等容器,不需要单独的 Web 服务器。这意味着不再需要启动 Tomcat 或其他任何中间件。
  • 提供了一系列项目中常见的非功能特性,如安全监控、应用监控、健康检测等。
  • 与微服务的天然集成。

​ Spring Boot 是在 Spring 的基础上发展而来的,它不是为了取代 Spring,而是为了简化 Spring 应用的创建、运行、调试、部署,让开发者更容易地使用 Spring。

官网:

https://docs.spring.io/spring-boot/docs/2.7.11/reference/html/features.html#features.spring-application

二、入门回顾

2.1 基本步骤:

步骤一:创建普通maven项目

步骤二:pom.xml引入boot父工程

<parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.7.4</version>
  </parent>

步骤三:引入某功能的起步依赖

<!--web起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

不需要写依赖坐标的版本号,已经被spring-boot-starter-parent管理!

步骤四:创建引导类/启动类

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

步骤五:配置文件application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/tlias?&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
  servlet:
    multipart:
      #一次请求中每个文件不得大于2M
      max-file-size: 2MB
      #一次请求中总文件不得大于2M
      max-request-size: 2MB

步骤六:开发业务代码

@RestController
public class CommonController {
    @PostMapping("/hello")
    public String uploadImage(String name){
        return "hello" + name;
    }
}

2.2 注意事项

1)版本控制

spring-boot-starter-parent对大部分功能坐标版本都进行了管理!

> ```
> dependencyManagement
> maven的版本控制,只负责控制项目中依赖版本号,不负责引入依赖!
> ```

### 2)起步依赖坐标

```java
spring-boot-starter-web是springboot提前设定好的坐标,其中管理了web开发、运行所需依赖

spring-boot-starter-web起步依赖又依赖了其他的坐标(与该功能相关的使用坐标)。利用maven的依赖传递特性,将某功能所需的依赖都拉过来!

3)配置文件

基于“约定优于配置”思想,大部分配置都有默认值,如需变更,则在配置文件中覆盖即可。

三、配置文件说明

https://docs.spring.io/spring-boot/docs/2.7.11/reference/html/features.html#features.external-config

3.1 配置文件类型

1) .properties
2) .yml
3) .yaml

内容语法:

key=value
key: value

配置项读取

1、@Value("${key}")

2、@ConfigurationProperties(prefix = "")

3.2 配置文件使用优先级

properties
yml
yaml

放置目录放到classpath:/config (优先级更高)

Java系统属性配置 (格式: -Dkey=value)

-Dserver.port=8001

命令行参数 (格式:--key=value)

--server.port=8002

作用1:

开发时启动多个应用:

作用2:

项目部署时启动指定参数。

java -jar spring-boot-entry.jar --server.port=8110

其他:

可以在jar的位置放置配置文件,其中配置项优先级高于jar中配置项

java -jar spring-boot-entry.jar --spring.profiles.active=prod

四、bean管理

4.1 自己的bean管理

维护项目中自己想要放到spring容器中的对象(bean)

@Component //及其衍生注解 
@Controller
@Service
@Configration
@Mapper //(mybatis-spring借助spring的IOC技术完成对象管理,并交给spring容器)

要想自己的bean受管理,类需要声明在启动类所在包下!

4.2 第三方bean管理(掌握)

使用@Bean管理

@Configuration
public class DruidConfig {

    @Bean//类似于将@Configuration写在类上;
    public DataSource dataSource(@Qualifier("druidParam") DruidParam druidParam){


        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(druidParam.getDriverName());
        druidDataSource.setUrl(druidParam.getUrl());
        druidDataSource.setPassword(druidParam.getPassword());
        druidDataSource.setUsername(druidParam.getUsername());
        return druidDataSource;
    }
}

如何在@Bean注解基础上注入属性(DI)

​ //需求:druidDataSource属性赋值,值不是写死的!而是通过配置文件进行读取

datasource:
  driverName: com.mysql.cj.jdbc.Driver
  url: jdbc:mysql:///tlias
  username: root
  password: root123
@Component
@ConfigurationProperties(prefix = "datasource")
@Data
public class DruidParam {
    private String driverName;
    private String url;
    private String username;
    private String password;
}
@Configuration
public class DruidConfig {

    //需求:druidDataSource属性赋值,值不是写死的!而是通过配置文件进行读取
    @Bean//类似于将@Configuration写在类上;
    public DataSource dataSource(@Qualifier("druidParam") DruidParam druidParam){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(druidParam.getDriverName());
        druidDataSource.setUrl(druidParam.getUrl());
        druidDataSource.setPassword(druidParam.getPassword());
        druidDataSource.setUsername(druidParam.getUsername());
        return druidDataSource;
    }
}

五、自动装配原理

5.1 了解自动配置过程描述

1)启动类

//@SpringBootApplication springboot关键注解 作用繁重
@SpringBootApplication
public class ReadApp {
    public static void main(String[] args) {
        // run 做spring容器管理 bean自动创建
        SpringApplication.run(ReadApp.class, args);
    }
}

2)@SpringBootApplication

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

3)@EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

4)@Import(AutoConfigurationImportSelector.class)

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//AutoConfigurationImportSelector类
            //String[] 返回值 放着要被自动管理bean对象的 全限定类名字符串
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
        // 获取自动配置类
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
}

5)getAutoConfigurationEntry(annotationMetadata);

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取自动配置类 全限定类名
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

6)getCandidateConfigurations(annotationMetadata, attributes);

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = new ArrayList<>(
				SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
		ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;

META-INF/spring.factories

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

7)spring容器拿到这些字符串基于IOC管理bean

难道都要生成这些配置bean和里面的bean?不是的,需要@Conditional进行判断,只有判断通过的才创建bean对象,并放到容器。

5.2 条件判断

  • @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。

  • @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。

  • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。

5.3 @Import注解

作用:

① 导入Bean

② 导入配置类

③ 导入 ImportSelector 实现类。一般用于加载配置文件中的类

④ 导入 ImportBeanDefinitionRegistrar 实现类。

作用2演示:

第三方包中有一个配置类,其中有一个bean

@Configuration
public class JacksonConfig {

    @Bean
//    @ConditionalOnProperty(name = "shen", havingValue = "ikun")
//    @ConditionalOnMissingBean
    public ObjectMapper objectMapper(){
        return new ObjectMapper();
    }
}

我们自己的代码中可以直接导入

@SpringBootApplication
@Import({JacksonConfig.class})
public class MyUse3DApp {
    public static void main(String[] args) {
        SpringApplication.run(MyUse3DApp.class, args);
    }
}

作用3演示:

第三方包中声明一个ImportSelector:

public class ImportJacksonBeanSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"org.itcast.jackson.bean.JacksonConfig"};
    }
}

我们自己的代码中可以直接导入

@SpringBootApplication
@Import({ImportJacksonBeanSelector.class})
public class MyUse3DApp {
    public static void main(String[] args) {
        SpringApplication.run(MyUse3DApp.class, args);
    }
}

5.4 @EnableXxx注解

底层就是@Import注解,为了美观,使用Enable注解

第三方包中声明一个EnableJK注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ImportJacksonBeanSelector.class)
public @interface EnableJK {
}

我们自己的代码中可以直接使用

@SpringBootApplication
@EnableJK
public class MyUse3DApp {
    public static void main(String[] args) {
        SpringApplication.run(MyUse3DApp.class, args);
    }
}

面试题:boot自动配置过程

1)读取自动配置类

2)spring容器管理bean(有@Conditional注解),基于IOC底层源码(工厂模式+反射)

六、自定义starter

面试题:你能否自己写一个starter。

写starter的目的:抽取统一功能,只要你的项目依赖了,就可以完成自动装配,代码中可以直接注入使用!

可以模仿springboot中的starter,也可以模仿pagehelper的

需求:

抽取阿里云OSS功能,完成一个文件上传功能的starter,将来该功能可以被重复依赖!

转载自:https://juejin.cn/post/7227333256076541989
评论
请登录