<groupId>com.github.whvcsegroupId>
<artifactId>easy-captchaartifactId>
<version>1.6.2version>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<optional>trueoptional>
dependency>
<2>排除依赖:如果依赖了spring-boot-starter-web,spring-boot-starter-web中依赖了spring-boot-starter-json,添加了当前项目就不会传递依赖于spring-boot-starter-json。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jsonartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<scope>compilescope>
dependency>
一个资源(项目)除了可以依赖于另一个资源(项目)外,还可以继承另一个资源(项目)。在一个资源依赖于另一个资源时,它会间接依赖于其他资源;而在一个资源继承另一个资源时,它会继承这个资源对依赖的配置。
<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.0modelVersion>
<groupId>com.zyfgroupId>
<artifactId>ssmartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<spring.context>5.3.22spring.context>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.context}version>
dependency>
dependencies>
dependencyManagement>
project>
<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.0modelVersion>
<groupId>com.zyfgroupId>
<artifactId>controllerartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>com.zyfgroupId>
<artifactId>ssmartifactId>
<version>1.0-SNAPSHOTversion>
<relativePath>../ssm/pom.xmlrelativePath>
parent>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
dependency>
dependencies>
project>
SpringBoot是Spring开源组织下的一个子项目,降低了使用SpringBoot的难度,简省了Spring的配置,提供了各种启动器。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookService"
class="com.itheima.service.impl.BookServiceImpl"
scope="singleton"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
beans>
使用XML文件初始化IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.itheima"/>
beans>
@ComponentScan
public class TotalConfig {
@Bean
public Cat cat(){
return new Cat();
}
}
<2>使用类文件初始化IOC容器,这个类也会被实列化并添加到容器中
ApplicationContext ctx = new AnnotationConfigApplicationContext(TotalConfig.class);
<3>配置类:含有添加了@Bean注解的方法的类。当这个配置类被实例化称为bean并且被放入IOC容器时,它的被加了@Bean注解的方法的返回值也将被放入IOC容器。
public class Config {
@Bean
public Cat cat(){
return new Cat();
}
}
<4>为什么需要配置类:
在使用@Component注解时,它只能去扫描当前项目中的包,并且如果是对项目的更新,之前项目中可能使用的xml文件加载bean而没有使用注解,这就导致使用@Component无法实列化这些类并将其放入容器中成为bean。使用配置类就可以解决这个问题。
<5>为什么要在配置类上添加@Configuration或@Component
添加@Configuration或@Component注解只是为了让配置类称为一个bean,可以被加载到容器中
<6>@Configuration和@Component的区别
@Configuration注解中含有@Component注解,可以使得一个类被扫描到并且初始化为Bean。但相比于@Component注解,@Configuration多了proxyBeanMethods()方法,默认返回值为true。当proxyBeanMethods()返回值为ture时,添加了@Configuration的类并不会被实列化,被实列化的是它的代理类。当我们从容器中获取这个实列后,调用它的添加了@Bean注解的方式时,不会重新创建对象,而是从容器中去取对应的ben。如果proxyBeanMethods()返回值为false,则每次调用添加了@Bean注解的方式时都会重新创建一新的对象。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
比如对于下面这个类,它被实列化并且添加到IOC容器中时会创建一个Cat对象,这个Cat对象也会被添加到IOC容器中。如果调用Config对象的cat()方法就会去IOC容器中去取Cat对象而不是重新创建对象。但如果添加的是@Configuration(proxyBeanMethods = false)注解,每次调用cat()方法都会重新创建对象。
@Configuration
public class Config {
@Bean
public Cat cat(){
return new Cat();
}
}
public class CatFactoryBean implements FactoryBean<Cat> {
@Override
public Cat getObject() throws Exception {
return new Cat();
}
@Override
public Class<?> getObjectType() {
return Cat.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
<2>FactoryBean的作用:有的对象的创建是复杂的,如果都写在配置类中将会使配置类变得臃肿。
<3>FactoryBean在配置类中的使用:
直接在配置类中创建对应的FactoryBean并返回即可,不过需要注意的是虽然cat()方法返回值为CatFactoryBean,但放入容器中的并不是CatFactoryBean的实列而是CatFactoryBean中getObject()方法返回的实列。
@Configuration(proxyBeanMethods = false)
public class Config {
@Bean
public CatFactoryBean cat(){
return new CatFactoryBean();
}
}
如下,如果在一个类上使用了@Import注解,当这个类被实例化并放入IOC容器称为一个Bean时,@Import注解中的类也会被实例化并且放入IOC容器称为一个bean。
@Component
@Import(Cat.class)
public class Config {
}
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig5.class); ctx.register(Cat.class);
当使用@Import注解导入这个类时(通过在这个类上加@Component注解让这个类成为bean没有这个效果),selectImports方法返回值中的类将会被实例化并放入IOC容器中成为bean。
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata metadata) {
return new String[]{"com.example.yuanli.example.Cat"};
}
}
实现ImportBeanDefinitionRegistrar接口,实现其中的registerBeanDefinitions方法。先定义一个BeanDefinition ,然后将BeanDefinition 注册到registry中,BeanDefinition中的Class对象对应的类就会被实现化并放入IOC容器中。需要注意的是MyImportBeanDefinitionRegistrar类也需要使用@Import注解导入。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(Cat.class)
.getBeanDefinition();
registry.registerBeanDefinition("cat", beanDefinition);
}
}
与ImportBeanDefinitionRegistrar唯一不同的是,这里的bean会被最后创建。
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(Cat.class)
.getBeanDefinition();
registry.registerBeanDefinition("cat", beanDefinition);
}
}
在4、5、6、7三种方式创建bean时都可以使用编码的方式来控制Bean的加载
用户控制加载bean的注解都以@Conditional开头,常用的有@ConditionalOnBean,@ConditionalOnClass,@ConditionalOnMissingBean,@ConditionalOnMissingClass。这些注解可以添加在类上也可以添加配置类的方法上。只有这个注解的条件满足了才能加载对应的Bean。
@Configuration
@ConditionalOnClass(name = "com.example.yuanli.example.Cat") //@ConditionalOnClass(Cat.class)也可
public class Config {
}
<2>在方法上加@ConditionalOnClass注解:如下,只有在存在Cat类时才会加载Cat类对应的bean。
@Configuration
public class Config {
@Bean
@ConditionalOnClass(name = "com.example.yuanli.example.Cat")
public Cat cat(){
return new Cat();
}
}
@Configuration
@ConditionalOnBean(name = "dog")
public class Config {
}
<2>在方法上加@ConditionalOnBean注解:如下,只有在存在dog这个bean时才会加载Cat类对应的bean。
@Configuration
public class Config {
@Bean
@ConditionalOnBean(name = "dog")
public Cat cat(){
return new Cat();
}
}
将bean运行需要的资源抽取成独立的属性类,然后在这个bean中注入属性类。
@Component
@Data
@ConfigurationProperties(prefix = "animal")
public class Animal {
private Cat cat;
private Mouse mouse;
}
属性类Animal依赖的类Cat和Mouse,只是一个普通的提供了set方法的类
@Data
public class Cat {
private String name;
private Integer age;
}
@Data
public class Mouse {
private String name;
private Integer age;
}
注入属性类,可以像使用普通的bean一样用@Resource等注入,也可以使用@EnableConfigurationProperties注解注入,不过需要注意是使用@EnableConfigurationProperties注解时需要提供构造函数,IOC容器会通过构造函数注入属性bean。
@Component
@EnableConfigurationProperties(Animal.class)
public class Config {
private Animal animal;
public Config(Animal animal) {
this.animal = animal;
}
public void say(){
System.out.println(animal.getCat().getName() + ":" + animal.getMouse().getName());
}
}
@SpringBootApplication是SpringBoot的核心注解。
@SpringBootApplication主要由一些注解构成
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class})})
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
}
再看一下RedisProperties类,部分代码如下。可以看到,这个类中的变量就是我们常用的对Redis的配置,同时我们也可以通过再yml文件或者properties文件中进行配置来覆盖RedisProperties类中的默认值。
@ConfigurationProperties(
prefix = "spring.redis"
)
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String username;
private String password;
private int port = 6379;
private boolean ssl;
private Duration timeout;
private Duration connectTimeout;
private String clientName;
private RedisProperties.ClientType clientType;
private RedisProperties.Sentinel sentinel;
private RedisProperties.Cluster cluster;
private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
}
经过上面的分析我们发现@Import({AutoConfigurationImportSelector.class})注解就是实现springboot中自动配置的关键。
Spring去收集开发者常用的技术和这些技术常用的参数的信息。由此构成了技术集(这些技术集对应这一个一个的类比如说RedisTemplate,而这些技术集由一堆配置类创建,比如上面提到的RedisAutoConfiguration类)和设置集(也就是一堆属性类,比如RedisProperties类)。
加载用户自定义的bean和导入的其他坐标,形成初始化环境(比如用户添加了对Redis的依赖)。
SpringBoot初始化容器,并且尝试去加载这些技术集(也就是实例化各种配置类,比如RedisAutoConfiguration类,这些配置类会创建技术集)。当然并不是所有的配置类都会被实例化,只有满足条件的配置类才会被实例化(比如对于RedisAutoConfiguration,如果用户没有添加对Redis的依赖,RedisAutoConfiguration就不会被加载)。
对于一个技术比如Redis,它需要各种配置各种参数。springboot通过在配置类中导入属性类就可以实现对参数的配置。比如RedisAutoConfiguration类通过@Import标签导入了RedisProperties类。
用户可以通过在.yml中或者.properties文件中进行配置来实现对properties类中属性的覆盖。
SpringBoot可以实现自动配置,而实现自动配置需要依赖的项目满足下面两个条件(也就是上面介绍的自动配置的元素):
<1>要在resources目录下建立一个META-INF目录,并且META-INF目录中提供一个spring.factories文件,这个文件中提供了配置类的类路径。
<2>要提供spring.factories文件中对应的配置类(有的配置类可能还需要提供属性类)。
满足上面两个条件的项目就能够成为一个starter,springboot添加了这个starter的依赖后就可以直接使用里面的功能而无需额外的配置。
分为springboot自己提供的starter和第三方stater。
在SpringBoot程序中,在主程序中会调用SpringApplication.run(App.class)
方法,而这个方法最终会调用(new SpringApplication(primarySources)).run(args)
这行代码。new SpringApplication(primarySources)
创建了一个SpringApplication对象,然后调用其run方法。
springboot会将读取环境属性(Environment),系统配置(spring.factories), 参数(Arguments、 application.properties),然后将这些信息抽象为对象。
在调用new SpringApplication(primarySources)创建一个SpringApplication对象时就会创建上面的这些属性对应的对象,SpringApplication(primarySources)会调用下面的代码。
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
# 初始化资源加载器
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
# 初始化配置类的类名信息(格式转换)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
# 确认当前容器加载的类型
this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
# 获取系统配置引导信息
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
# 获取ApplicationContextInitializer.class对应的实例
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
# 初始化监听器,对初始化过程及运行过程进行干预
this.mainApplicationClass = this.deduceMainApplicationClass();
}
SpringApplication对象的run方法就是用来创建一个Spring容器对象并且加载各种配置。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
public class MyListener implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
System.out.println("=================");
}
}
<2>在项目的resources目标下创建META-INF文件夹,并在这个文件夹中创建spring.factories文件,添加如下内容。
org.springframework.context.ApplicationListener=\
com.example.demo.day1.MyListener
spring简配依赖配置的两个主要途径就是提供了spring-boot-starter-parent,和各种starter(如spring-boot-starter-web等)。
<1>spring-boot-starter-parent:主要的作用就是调(tiao)包,保证条件依赖时不会冲突。
spring-boot-starter-parent项目继承自spring-boot-dependencies,而spring-boot-dependencies对各种依赖进行了调包,保证了各种依赖不会发生冲突。在使用依赖,不需要指定,会直接使用spring-boot-dependencies配置的version。
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.1version>
<relativePath/>
parent>
<2>各种starter:合并了常用功能需要使用到的依赖。
它整合了若干项目,在用户使用一个功能时(比如web),不需要要再配置各种依赖(比如spring-context,spring-mvc),直接依赖(spring-boot-starter-web)就可。
sprinboot将一些常用的服务器抽象为对象(比如tomcat),并将这些对象交由IOC容器管理,需要访问服务器时就直接执行这些对象的方法。修改服务器的端口通过在yml中添加如下配置实现。
server:
port: 8080