学习来源:B站尚硅谷《Spring注解驱动开发》
内容较多,请结合CSDN网页右侧的目录组件阅读本文
文章未完结。如有错误欢迎批评指正,感谢!
注解的value参数指明要扫描的包路径,比如value="cn.louzen",即cn.louzen路径下的所有注解了 @Component 及其子注解如 @Controller、@Service、@Repository、@Configuration等 的类都会被扫描并放入容器中
作用域:类
JDK8及以上,@ComponentScan 是可以重复放在同一个类上的,或者用@ComponentScans
@ComponentScan(basePackages={"cn.louzen"})
public class MainConfig {}
看它扫描到的所有组件:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String definitionName : definitionNames) {
System.out.println(definitionName);
}
可以在@ComponentScan中指定要扫描(includeFilters参数)或不扫描(excludeFilters参数)哪个包,
其中,type指明依据哪种类型过滤,classes指明要过滤的内容,
指定不扫描:
@ComponentScan(basePackages={"cn.louzen"}, excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class MainConfig {}
指定要扫描:
@ComponentScan(basePackages={"cn.louzen"}, includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)
public class MainConfig {}
指定要扫描时要设置 useDefaultFilters = false,让默认过滤规则不生效
设置默认过滤规则不生效:use-default-filters="false"
指定过滤规则
参数:
type:(过滤类型,取值是 org.springframework.context.annotation.FilterType 枚举类型)
ANNOTATION:按照注解过滤(常用)
ASSIGNABLE_TYPE:按照指定具体哪个类过滤(常用)
ASPECTJ:按照ASPECTJ表达式过滤
REGEX:按照正则表达式过滤
CUSTOM:自定义规则,规则类必须是org.springframework.core.type.filter.TypeFilter接口的实现类
classes:(对应type的值)
可以放多个 @ComponentScan
@ComponentScans(value = {
@ComponentScan(basePackages={"cn.louzen"})
})
给容器中注册组件:(详情见下文对每个注解的解释)
1、包扫描 + 组件表注注解:(@ComponentScan扫描,@Component及其子注解注册组件)
2、@Bean:导入第三方包里的组件
3、@Import:快速给容器中导入一个组件,前提是组件的创建就是运行无参构造等简单的方式,不需要配置属性等操作
3.1、@Import({要注册进容器中的组件类.class}):容器中就会自动注册这个组件,id默认是组件类的全限定名(见下面@Import的内容)
3.2、@Import(ImportSelector实现类.class):ImportSelector接口,方法selectImports,方法返回需要导入的组件的全限定名的字符串数组(见下面@Import的内容)
3.3、@Import(ImportBeanDefinitionRegistrar实现类.class):BeanDefinition注册类,把所有需要添加到容器中的bean,可以调用 ImportBeanDefinitionRegistrar 的 registerBeanDefinition 方法手动注册进来(见下面@Import的内容)
4、使用Spring提供的FactoryBean(工厂Bean)接口,定义一个方法返回值是工厂类型,方法+@Bean可实现工厂的产品对象进容器
被@Bean修饰的方法,参数中的对象类型是从Spring上下文中获取的
作用域:方法、注解
属性:
value(= name):bean名,xml配置中对应的id属性
name(= value):与value互为别名,一样的
initMethod:指定初始化方法
destroyMethod:指定销毁方法
对普通的方法,bean类型为方法返回值的类型,id默认是方法名(@Bean的参数可以指定id):
@Bean
public Person person() {
return new Person("lisi", 20);
}
获取对象:
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(MainConfig.class);
Person person = applicationContext.getBean(Person.class);
同时,我们可以通过 applicationContext 上下文对象的一系列方法获取上下文的内容
比如获取一个类(type)在上下文中的所有bean name:
String[] beanNamesForType = applicationContext.getBeanNamesForType(Person.class);
for (String beanName : beanNamesForType) {
System.out.println(beanName);
}
对工厂方法,方法返回值虽然是工厂类型,但创建并放入容器的bean的类型为工厂方法的产品的类型,id默认还是方法名(可定义):
@Bean
public ColorFactoryBean colorFromFactory() {
return new ColorFactoryBean();
}
工厂类需要实现FactoryBean接口
ColorFactoryBean工厂类的定义见下面代码块“ ColorFactoryBean工厂类的定义 ”
注意:通过 applicationContext.getBean("工厂方法名,如colorFromFactory") 的方式默认获得的是产品类型的对象,想获取工厂类实例的话,就要在前面加&,如 getBean("&colorFromFactory")
public class ColorFactoryBean implements FactoryBean<Color> {
// 返回一个Color对象,这个对象会添加进容器中
@Override
public Color getObject() throws Exception {
System.out.println("ColorFactoryBean...getObject...");
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
// 控制是否单例,
// true:此bean单实例,在容器中保存一份;false:多实例,每次获取每次new,不保存进容器
@Override
public boolean isSingleton() {
return true;
}
}
bean的生命周期:
bean的创建(construct) -> 初始化(初始化方法) -> 销毁(销毁方法)
容器管理bean的生命周期:
我们可以自定义初始化和销毁方法,容器在bean进行到当前生命周期的时候会调用我们自定义的这些方法
创建对象的时机:
单实例:容器启动的时候创建,可以设置为懒加载
多实例:用到的时候才创建
初始化:
对象创建完成,并赋值好,再调用初始化方法
销毁:
单实例:容器关闭的时候销毁
多实例:容器不会管理这个bean,容器不会调用销毁方法
方式一:
通过设置@Bean的initMethod、destroyMethod 指定初始化方法、指定销毁方法
用例见下面代码块“ @Bean设置initMethod、destroyMethod ”(singleton)
方式二:
通过让bean实现InitializingBean接口,实现方法afterPropertiesSet来定义初始化方法
通过让bean实现DisposableBean接口,实现方法destroy来定义销毁方法
用例见下面代码块“ 通过接口实现bean初始化销毁 ”
方式三:
通过注解实现
@PostConstruct:
在bean创建完成并且属性赋值完成后,执行初始化
作用域:方法(bean的初始化方法)
@PreDestroy:
在bean将要被从容器中移除之前,执行销毁
作用域:方法(bean的销毁方法)
用例见下面代码块“ 注解实现bean初始化销毁 ”
实现原理见下面代码块“ Spring底层使用BeanPostProcessor ”
方式四:
通过实现bean的后置处理器接口BeanPostProcessor实现,后置处理器作用是在bean初始化前后进行一些处理工作
org.springframework.beans.factory.config.BeanPostProcessor
接口方法:
postProcessBeforeInitialization(用于初始化前):
运行时机是在前三种bean的初始化方式运行之前(initMethod、afterPropertiesSet、@PostConstruct)
postProcessAfterInitialization(用于初始化后):
运行时机是在前三种bean的初始化方式运行之后(initMethod、afterPropertiesSet、@PostConstruct)
注意:BeanPostProcessor接口的实现类是单独的,不是bean类实现的,这不同于前面的实现方式
注意:BeanPostProcessor接口的两个方法都是针对初始化的,没有销毁的方法
注意:后置处理器的接口不用跟bean产生直接联系,后置处理器实现类创建并放入容器后会自动在bean的初始化过程中发挥作用
用例见下面代码块“ 后置处理器接口实现初始化 ”
几种方式的运行顺序:
见下面代码块“ 初始化注销方式执行顺序 ”
bean初始化过程源码:
见下面代码块“ bean初始化过程源码 ”
Spring底层对BeanPostProcessor的使用:
见下面代码块“ Spring底层使用BeanPostProcessor ”
// 实体类
public class Car {
public Car() {
System.out.println("car constructor");
}
public void init() {
System.out.println("car ... init .... ");
}
public void destroy() {
System.out.println("car ... destroy .... ");
}
}
// 组建注册
@Bean(initMethod = "init", destroyMethod = "destroy")
public Car car() {
return new Car();
}
// 测试用例
@Test
public void test01() {
// 这里会输出 car constructor
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
// 这里会输出 car ... init ....
System.out.println("容器创建完成");
applicationContext.close();
// 这里会输出 car ... destroy ....
}
// 输出内容与 “ @Bean设置initMethod、destroyMethod ” 代码块中的一样
@Component
public class Cat implements InitializingBean, DisposableBean {
public Cat() {
System.out.println("cat constructor");
}
@Override
public void destroy() throws Exception {
System.out.println("cat...destroy...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("cat...afterPropertiesSet...");
}
}
// 输出内容与 “ @Bean设置initMethod、destroyMethod ” 代码块中的一样
@Component
public class Dog {
public Dog() {
System.out.println("Dog constructor");
}
// 对象创建并赋值之后调用
@PostConstruct
public void init() {
System.out.println("Dog ... PostConstruct ...");
}
// 容器移除对象之前
@PreDestroy
public void destroy() {
System.out.println("Dog ... PreDestroy ...");
}
}
// 在初始化前后进行处理
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
public MyBeanPostProcessor() {
System.out.println("MyBeanPostProcessor...constructor...");
}
// 参数bean:容器刚创建的实例
// 参数beanName:bean的名字
// 返回值:要返回的实例,默认是直接返回bean,我们也可以返回经过处理后的
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("MyBeanPostProcessor...postProcessBeforeInitialization...");
return bean;
}
// 参数、返回值含义:同上
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("MyBeanPostProcessor...postProcessAfterInitialization...");
return bean;
}
}
/*
* MyBeanPostProcessor 初始化的输出:
* MyBeanPostProcessor...constructor...
* MyBeanPostProcessor...postProcessBeforeInitialization...
* MyBeanPostProcessor...postProcessAfterInitialization...
*/
// 这里的bean以cat为例
cat constructor // cat构造方法
MyBeanPostProcessor...postProcessBeforeInitialization... // 后置处理器,初始化前的方法
cat...@PostConstruct注解指定的方法
cat...InitializingBean接口的afterPropertiesSet方法
cat...@Bean注解的initMethod参数
MyBeanPostProcessor...postProcessAfterInitialization... // 后置处理器,初始化后的方法
容器创建完成
cat...@PreDestroy注解指定的方法
cat...DisposableBean接口的destroy方法
cat...@Bean注解的destroyMethod参数
populateBean(beanName, mbd, instanceWrapper) // 为bean的属性赋值
initializeBean(beanName, exposedObject, mbd) // 初始化bean
{
applyBeanPostProcessorsBeforeInitialization(bean, beanName); // 后置处理器,初始化前方法
{
遍历得到容器中所有的BeanPostProcessor,
挨个执行其 postProcessBeforeInitialization(result, beanName) 方法,
一旦有BeanPostProcessor的此方法返回null,就不会再执行后面的BeanPostProcessor,
}
invokeInitMethods(beanName, wrappedBean, mbd); // 执行初始化
applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); // 后置处理器,初始化后方法
{
过程类似 applyBeanPostProcessorsBeforeInitialization
}
}
Spring底层有很多对BeanPostProcessor的使用
应用举例:
ApplicationContextAwareProcessor:
是BeanPostProcessor的子接口,我们的bean实现此接口后可以注入Spring的应用上下文applicationContext
用例及原理见下面代码块“ 使用ApplicationContextAwareProcessor ”
AsyncAnnotationBeanPostProcessor:
用于@Async注解的处理
ApplicationContextAwareProcessor:
可以帮我们组件中注入IOC容器对象
BeanValidationPostProcessor:
对象创建完给bean赋值后,进行数据校验
InitDestroyAnnotationBeanPostProcessor:
处理@PostConstruct和@PreDestroy注解的
原理:InitDestroyAnnotationBeanPostProcessor类的postProcessBeforeInitialization方法(对应@PostConstruct)和postProcessBeforeDestruction方法(对应@PreDestroy)
AutowiredAnnotationBeanPostProcessor:
在对象创建完后处理@Autowired标注的内容,将组件从IOC中拿出来进行注入
Spring底层用BeanPostProcessor实现了:
bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async,xxx BeanPostProcessor
@Component
public class Dog implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// applicationContext 就是IOC容器对象
this.applicationContext = applicationContext;
}
}
// 原理:
// ApplicationContextAwareProcessor类的postProcessBeforeInitialization方法
获取对象:
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Person person = (Person) applicationContext.getBean("person");
指定对象是单实例还是多实例:中 scope="singleton" 或 scope="prototype"
默认对象是单实例,设置懒加载:中 lazy-init="true"
指定初始化方法:中 init-method="",方法必须不能带参数,但可以抛异常
指定销毁方法:中 destroy-method="",方法必须不能带参数,但可以抛异常
定义bean的作用域和生命过程
作用域:类,方法
用例:在@Bean修饰的方法上加@Scope,指定方法产生的对象是单实例还是多实例
参数 value 取值:
singleton:单实例(默认值),= ConfigurableBeanFactory#SCOPE_SINGLETON
默认在IOC容器启动时调用方法创建对象放进容器,以后每次获取该类型对象都直接从容器中拿(map.get)
可以配置为懒加载,即容器启动的时候不创建对象,而是在第一次使用(获取)这个对象的时候创建对象并初始化,放进容器,以后每次使用都用同一个这个对象,使用@Lazy
prototype:多实例,= ConfigurableBeanFactory#SCOPE_PROTOTYPE
IOC容器启动时不会调用方法创建对象,对象也不会放进容器,而是我们每次获取对象时会调用方法创建一个新的给我们
request:同一次请求创建一个实例,= org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
session:同一个session创建一个实例,= org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
注意:request和session开发中不会使用
用于对象的懒加载,只针对单例有效
因为Spring的对象默认在启动时就要集中创建出来并放入IOC容器中,导致启动时会消耗大量资源,所以有了懒加载
用例:在@Bean修饰的方法上加@Lazy
用在bean类中的方法上,用于指定初始化方法
用在bean类中的方法上,用于指定销毁方法
表名是组件,被扫描到后会被创建并放入IOC容器中
@Component及其子注解,注册的对象的名字默认是“首字母小写的类名”
表名是接口类,@Component 的子注解
表名是服务层类,@Component 的子注解
表名是持久层类,@Component 的子注解
现在持久层框架一般是用 Mybatis 或 Mybatis-Plus,@Repository 用 @Mapper 代替
表名是配置类,@Component 的子注解
作用域:类
方法一:
@Import({要注册进容器中的组件类.class}):容器中就会自动注册这个组件,id默认是组件类的全限定名
使用举例:
@Configuration
@Import({Color.class})
public class MainConfig2 {}
方法二:
@Import(ImportSelector实现类.class),ImportSelector接口,方法selectImports,方法返回需要导入的组件的全限定名的字符串数组
使用举例:
@Configuration
@Import({MyImportSelector.class})
public class MainConfig2 {}
ImportSelector实现类代码见下面代码块
org.springframework.context.annotation.ImportSelector
方法三:
@Import(ImportBeanDefinitionRegistrar实现类.class):BeanDefinition注册类,把所有需要添加到容器中的bean,可以调用 ImportBeanDefinitionRegistrar 的 registerBeanDefinition 方法手动注册进来
使用举例:
@Configuration
@Import({MyImportBeanDefinitionRegistrar.class})
public class MainConfig2 {}
见下面代码块“ ImportBeanDefinitionRegistrar实现类 ”
注意:使用ImportSelector实现类的方式,实现方法selectImports的返回值不能是null会报错,可以是空字符串数组
注意:如果类上还有 @Conditional({Condition实现类.class}) 有条件地注册,如果Condition实现类中的条件不符合,这个类就不会被执行,@Import也不会执行,相应的类不会被注册进容器
注意:由于MyImportSelector实现了ImportSelector接口,所以此类的对象不会注册进容器
public class MyImportSelector implements ImportSelector {
// AnnotationMetadata:包含当前标注@Import注解的类的所有注解信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 返回值不能是null,会报错
return new String[]{"cn.louzen.bean.Color", "cn.louzen.bean.Red", "cn.louzen.bean.Person"};
}
@Override
public Predicate<String> getExclusionFilter() {
return ImportSelector.super.getExclusionFilter();
}
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry, importBeanNameGenerator);
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean definition = registry.containsBeanDefinition("cn.louzen.bean.Red");
boolean definition2 = registry.containsBeanDefinition("cn.louzen.bean.Blue");
if (definition && definition2) {
// 指定Bean的定义信息(Bean的类型、Scope等)
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
// 注册Bean到容器,这里可以指定组件id
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}
按照条件判断要不要创建某对象并放入IOC容器
按照一定条件判断,满足条件给容器中注册bean
作用域:类,方法,这里是可以被注册进容器中的类、方法(被@Component及其子注解、@Bean等修饰的类、方法)
参数:
value:
类型:Class extends Condition>[] // 这里要用到Condition接口的实现类
使用举例:(用在方法上)
@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person02() {
return new Person("linus", 48);
}
使用举例:(用在类上)
@Conditional({WindowsCondition.class})
@Configuration
public class MainConfig2 {}
Condition 是一个接口(org.springframework.context.annotation.Condition),
接口返回true就运行其所修饰的类、方法,否则不运行
有一个方法 boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata):
context:判断条件能使用的上下文(环境)
medadata:注解信息
Condition实现类见下面代码段
注意:Condition的matches方法的逻辑不仅只返回true或false,还可以在逻辑中创建bean并放进容器
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO 判断是否Linux系统
// 1. 能获取到IOC使用的beanfactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 2. 获取类加载器
ClassLoader classLoader = context.getClassLoader();
// 3. 获取当前环境信息
Environment environment = context.getEnvironment();
// 4. 获取容器中注册的类(可以创建新对象并放入这里面)
BeanDefinitionRegistry registry = context.getRegistry();
// 这里通过判断环境变量决定方法返回值,也可以通过判断context中的其他的内容判断
String property = environment.getProperty("os.name");
return (Objects.nonNull(property) && property.contains("linux"));
}
}
不配置havingValue的值
1.假如没有配置这个name的配置
1.1@ConditionalOnProperty(prefix = “app”,name=“name”,matchIfMissing = false)
假如没有配置这个name的配置,若matchIfMissing为false,则不会加载此配置类
假如配置了这个name的配置,若matchIfMissing为false,则会加载此配置类
1.2@ConditionalOnProperty(prefix = “app”,name=“name”,matchIfMissing = true)
假如没有配置这个name的配置,若matchIfMissing为true,则仍会加载此配置类
假如配置了这个name的配置,若matchIfMissing为true,则会加载此配置类
总结,若未配置havingValue的值,matchIfMissing为true则无论是否有配置都会加载配置类,matchIfMissing为false,有配置加载类,无配置不加载类。
配置错误的havingValue的值
2.1@ConditionalOnProperty(prefix = “app”,name=“name”,havingValue = “name1”,matchIfMissing = false)
假如配置了错误的name值,若matchIfMissing为false,配置类不会加载
假如配置了正确的name值,若matchIfMissing为false,配置类会加载
2.2@ConditionalOnProperty(prefix = “app”,name=“name”,havingValue = “name1”,matchIfMissing = true)
假如配置了错误的name值,若matchIfMissing为true,配置类不会加载
假如配置了正确的name值,若matchIfMissing为true,配置类会加载
总结,只要配置了正确的havingValue值,无论matchIfMissing怎么设置,都会加载,只要配置的havingValue值不正确,无论
matchIfMissing怎么设置,都不会加载。
————————————————
版权声明:本文为CSDN博主「岸河」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42145871/article/details/118310230
指定配置文件
作用域:类
可以放在任何组件(@Component或其子注解标注的类)上,对应配置文件就会生效
使用举例:@PropertySource("classpath:/blue.properties")
与@Value配合,@PropertySource指定配置文件,@Value将配置文件中的配置赋值给属性
Profile:
Spring为我们提供的可以根据当前环境,动态地激活和切换一系列组件的功能
比如,我们可能有开发环境、测试环境、生产环境等,每个环境的数据源都不一样,或不同环境希望暴露不同的类等需求
指定组件在哪个环境的情况下才能被注册到容器中;不指定,任何环境下都能注册这个组件
作用域:类,方法
属性:value
1、@Profile作用于方法(被@Bean标注的方法),只有这个环境被激活的时候组件才能被注册进容器中
使用样例见下面“ @Profile作用于@Bean方法 ”
2、@Profile作用于类(被@Configuration标注的配置类),指定环境被激活时整个配置里所有的配置都会生效
3、没有标注@Profile的类和@Bean方法,任何情况都会加载(@Bean方法加载的前提是其所在的配置类会被加载)
注意:默认是default环境:@Profile("default"),如果设置了环境参数default也就失效了
注意:类上的@Profile与类中@Bean方法上的@Profile可以同时存在。若环境为test:若类上为@Profile("dev"),则整个类和类中@Bean方法都不会被加载进IOC容器;若类上为@Profile("test"),类中两个@Bean一个@Profile("test")另一个@Profile("dev"),则类和test的方法返回的对象会被加载进IOC,dev的@Bean方法不会执行
激活@Profile:
1、运行参数:项目运行时的java命令添加参数,-Dspring.profiles.active=test,就可以激活@Profile("test"),其标注的类、方法返回的对象就会被注册到容器中
2、代码方式激活:见下面“ 代码激活@Profile ”
注意:@Profile要放在可以被注册进IOC容器的类、方法上面(被标注@Component及其子注解、@Bean)才会生效
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware {
@Value("${db.user}")
private String user;
private String driverClass;
private StringValueResolver resolver;
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.resolver = resolver;
driverClass = this.resolver.resolveStringValue("${db.driverClass}");
}
@Profile("default")
@Bean("defaultDataSource")
public DataSource dataSourceDefault(@Value("${db.password}") String psw) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(psw);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/pig_job");
dataSource.setDriverClass(driverClass);
return dataSource;
}
}
public class IOCTestProfile {
@Test
public void test02() {
// 1、创建一个 applicationContext(无参构造)
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 2、设置需要激活的环境
applicationContext.getEnvironment().setActiveProfiles("test", "dev");
// 3、注册主配置类
applicationContext.register(MainConfigOfProfile.class);
// 4、启动刷新容器
applicationContext.refresh();
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
System.out.println(name);
}
applicationContext.close();
}
}
在注解中相当于标签的子标签的value属性,是给属性赋值的注解
作用域:属性、方法、入参、注解
Spring注解
使用形式:
1、基本数值,直接@Value("...")
2、SpEL表达式,#{},@Value("#{key}")
3、${}取出配置文件中的值(在运行环境里面的变量值),@Value("${key}")
注意:在纯Spring框架下(非SpringBoot),当用${}取数据时,需要指定配置文件,两种方法,一是xml配置文件中,二是在配置类中(相当于配置文件)通过@PropertySource注解指定(在配置类中创建用配置中变量初始化属性的bean)
自动装配:
Spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值
实现方式:
1、@Autowired自动注入
2、@Source和@Inject
3、将@Autowired置于构造器、参数、方法、属性上
4、Aware接口
默认优先按照 byType 自动注入,如果有多个,就将属性名作为id去容器中匹配并注入,如果还是有多个就会报错
自动装配默认一定要将属性赋值好,没有在容器中找到目标对象就会报错,如果目标对象可以为空则要设置它的属性required=false
作用域:属性、方法、构造器、参数
注解属于Spring
如果想要优先按照名称注入,就要配合 @Qualifier注解,如@Autowired () @Qualifier ( "baseDao" )
如果不想每次都用@Qualifier对要注入的组件进行指定,可以用@Primary来指定首选的组件
原理:
AutowiredAnnotationBeanPostProcessor 后置处理器
使用样例:
1. 属性
见下面“ @Autowired用于属性 ”
2. 方法(set方法)
见下面“ @Autowired用于方法 ”
3. 构造器(有参构造器)
见下面“ @Autowired用于构造器 ”
4. 参数(@Bean标注的方法参数)
见下面“ @Autowired用于参数 ”
可用于@Bean标注的方法位置(被@Bean标注的方法的参数可以加@Autowired,也可以省略@Autowired,Spring会自动从IOC容器中找的)
注意:虽然@Autowired可以标注在不同位置,但其本质都是将组件从IOC容器取出来并进行赋值
@Component
public class Boss {
@Autowired
private Car car;
public Car getCar() {return car;}
public void setCar(Car car) {this.car = car;}
}
// @Autowired标注在set方法上,Spring容器创建当前对象时就会调用它完成对属性的赋值,set方法的参数是从Spring的IOC容器中获取的
@Component
public class Boss {
private Car car;
public Car getCar() {return car;}
@Autowired
public void setCar(Car car) {this.car = car;}
}
// @Autowired标注在有参构造器上,或有参构造器的方法参数前面
// 如果一个类的构造器只有一个有参的,则标在构造器方法上的@Autowired,或标在有参构造器参数前的@Autowired可以省略
// 如果这个类有无参构造方法,则即使有参构造方法加了@Autowired注解Spring也不会调用,只会用无参构造方法
@Component
public class Boss {
private Car car;
// @Autowired 唯一的有参构造方法上面或有参构造方法的入参前
public Boss(@Autowired Car car) {
this.car = car;
}
public Car getCar() {return car;}
public void setCar(Car car) {this.car = car;}
}
// @Bean标注的方法的参数中的对象,默认是从IOC容器中获取的,参数前的@Autowired加不加都可
@Configuration
public class MainConfigOfAutowired {
@Bean
public Color color(Car car) {
Color color = new Color();
color.setCar(car);
return color;
}
}
配合@Autowired 就可以实现按照名称注入
@Autowired + @Qualifier 可以达到与 @Resource 相同的效果
注解属于Spring
作用域:类、方法
指定组件,让Spring进行装配的时候,默认首选被注解修饰的bean
注解属于Spring
默认按照 byName 的方式自动注入
注解的参数name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型
不支持@Primary,不支持像@Autowired一样的required=false的属性
作用域:属性、set方法
@Resource装配顺序:
1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配就按照类型匹配
属于J2EE
用这个注解需要导入javax.Inject依赖才能用
和@Autowired功能一样,但没有required属性
属于J2EE
<dependency>
<groupId>javax.injectgroupId>
<artifactId>javax.injectartifactId>
<version>1version>
dependency>
这是以Aware为父接口的一系列子接口,实现的机制都是后置处理器,前面讲的 BeanPostProcessor
自定义组件想要使用Spring容器底层的一些组件(ApplicationContext、BeanFactory 等等)
自定义组件实现xxxAware接口,在创建对象的时候,会调用接口规定的方法注入相关组件,把Spring底层的一些组件注入到自定义的Bean中
使用样例见下面代码块“ Aware接口样例 ”
每个Aware都有对应的Processor,xxxAware的方法由xxxProcessor来运行,例如ApplicationContextAware的方法在ApplicationContextProcessor有相关逻辑负责运行
@Component
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
// ApplicationContextAware 接口中的方法,用于拿到IOC容器对象
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("ioc容器:" + applicationContext);
this.applicationContext = applicationContext;
}
// BeanNameAware 接口中的方法,用于拿到bean name(或 id)
@Override
public void setBeanName(String name) {
System.out.println("beanName = " + name);
}
// EmbeddedValueResolverAware接口中的方法,拿到解析器,
// 用于解析字符串,拿到字符串中的#{配置}、${表达式}等占位符的值
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
// os.name是配置文件中设置的值
String str = resolver.resolveStringValue("你好${os.name}, 我是#{18*20}");
System.out.println("解析后的内容:" + str);
}
}
@Lazy
AOP(Aspect Orient Programming):
指在程序运行期间动态地将某段代码切入到指定方法指定位置进行运行的编程方式
原理是动态代理(详细见下面代码块“ AOP动态代理原理 ”)
AOP的代码实现:(实现样例见下面代码块“ AOP代码实现 ”)
1、导入aop模块:Spring AOP,导入的包不是speing-aop而是spring-aspects
2、定义一个业务逻辑类(MathCalculator),在业务逻辑运行的时候将打印日志(方法运行前、运行后、运行异常)
3、定义一个日志切面类(LogAspects),切面类里的方法是要动态感知到业务逻辑运行到哪里然后执行一系列的切面操作
切面类中有通知方法:
前置通知 @Before(logStart):在目标方法(div)运行之前运行
后置通知 @After(logEnd):在目标方法(div)运行结束后运行,无论方法正常还是异常结束都会调用
返回通知 @AfterReturning(logReture):在目标方法(div)正常返回之后运行
异常通知 @AfterThrowing(logException):在目标方法(div)运行出现异常后运行
环绕通知 @Around:动态代理,我们手动目标方法运行(joinPoint.proceed())
4、给切面类的目标方法标注通知注解
5、将切面类和目标业务逻辑类(目标方法所在的类)都加入到容器中
6、必须告诉Spring哪个类是切面类(给切面类上加一个注解@Aspect)
7、开启基于注解的切面功能:
xml配置:
代码配置:在配置类上加注解@EnableAspectJAutoProxy(在Spring中有很多@EnableXXX都是用来开启某项功能某项配置的)
三步:
1、将业务逻辑组件和切面类都加入到容器中,告诉Spring哪个是切面类(@Aspect)
2、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
3、开启Spring基于注解的AOP模式@EnableAspectJAutoProxy
注意:切面方法如果想拿到目标方法的入参、返回值、异常等信息,就要在切面方法的入参加上JoinPoint
注意:如果不需要JoinPoint中的信息可以从入参把它去掉,如果需要,要把JoinPoint写在参数第一位,否则Spring无法识别
public class MathCalculator {
public int div(int i, int j) {
System.out.println("MathCalculator...div...");
return i/j;
}
}
@Aspect
public class LogAspects {
// 抽取公共的切入点表达式 @Pointcut("execution(xxxx)")
// 1、本类的切面注解引用此切入点表达式只需要在切面注解中放入方法名
// 2、其他的切面类想引用此切入点表达式就要在切面注解中放入方法的全限定名
// * 表示MathCalculator类中的所有方法,.. 表示任意多任意类型的参数都可
@Pointcut("execution(public int cn.louzen.aop.MathCalculator.*(..))")
public void pointCut() {}
// @Before:在目标方法之前切入;切入点表达式:指定在哪个方法切入
// 如果不需要JoinPoint中的信息可以从入参把它去掉,如果需要,要把JoinPoint写在参数第一位,否则Spring无法识别
@Before("execution(public int cn.louzen.aop.MathCalculator.div(int, int))")
// @Before("execution(public int cn.louzen.aop.MathCalculator.*(..))")
public void logStart(JoinPoint joinPoint) {
// 目标方法的名字
String methodName = joinPoint.getSignature().getName();
// 目标方法的入参
Object[] args = joinPoint.getArgs();
System.out.printf("%s运行。。。@Before参数列表是;{%s}\n", methodName, Arrays.asList(args));
}
// 直接用切入点表达式就不需要再写execution了
@After("cn.louzen.aop.LogAspects.pointCut()")
public void logEnd(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.printf("%s结束。。。@After", methodName);
}
// 如果不需要返回值就写成:@AfterReturning("pointCut()")
// returning="result"是指定返回值参数名为result
@AfterReturning(value = "pointCut()", returning = "result")
public void logReture(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.printf("%s正常返回。。。@AfterReturning运行结果;{%s}\n", methodName, result.toString());
}
// 如果不需要返回值就写成:@AfterThrowing("pointCut()")
// throwing = "exception"是指定异常的参数名为exception
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void logException(JoinPoint joinPoint, Exception exception) {
String methodName = joinPoint.getSignature().getName();
System.out.printf("%s异常。。。@AfterThrowing异常信息;{%s}\n", methodName, exception);
}
}
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {
// 业务逻辑类加入容器中
@Bean
public MathCalculator mathCalculator() {
return new MathCalculator();
}
// 切面类加入容器中
@Bean
public LogAspects logAspects() {
return new LogAspects();
}
}
public class IOCTestAOP {
@Test
public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
// 1、不要自己new对象,要从容器中取才会有Spring的aop加强效果
// MathCalculator mathCalculator = new MathCalculator();
MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
mathCalculator.div(1, 0);
applicationContext.close();
}
}
div运行。。。@Before参数列表是;{[1, 1]}
MathCalculator...div...
div正常返回。。。@AfterReturning运行结果;{1}
div结束。。。@After
div运行。。。@Before参数列表是;{[1, 0]}
MathCalculator...div...
div异常。。。@AfterThrowing异常信息;{java.lang.ArithmeticException: / by zero}
div结束。。。@After
有两种情况:
目标类实现了接口,使用JDK动态代理:
目标类没有实现接口,使用CGLIB动态代理:
AOP原理:【看给容器中注册了什么组件,这个组件什么时候工作,这个组件的功能是什么】
1、@EnableAspectJAutoProxy注解
@Import({AspectJAutoProxyRegistrar.class}),给容器中导入AspectJAutoProxyRegistrar
AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,详见上面“ SpringIoC->组件注册->@Import ”的内容,将指定组件注册进容器中
给容器中注册 AnnotationAwareAspectJAutoProxyCreator 对象(自动代理创建器)
2、AnnotationAwareAspectJAutoProxyCreator,自动代理创建器
类的继承关系:
AnnotationAwareAspectJAutoProxyCreator
-> AspectJAwareAdvisorAutoProxyCreator
-> AbstractAdvisorAutoProxyCreator
-> AbstractAutoProxyCreator
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware
关注这两个接口,后置处理器(在bean初始化完成前后做的事情)、自动装配BeanFactory
get 幂等
post 非幂等
put 幂等
delete 幂等
如果请求url后面跟着多个同名参数,形如:url?param=1¶m=2¶m=3
我们Controller方法要用@RequestParam来接
1. 如果我们用method(@RequestParam Integer param)来接,param会匹配第一个参数值,即 param=1
2. 如果我们用method(@RequestParam Integer[] params 或 List params)来接,params=[1,2,3]
遇到指定异常就回滚:@Transactional(rollbackFor = Exception.class)
SpringBoot自2.4开始内置JUnit5,之前版本用JUnit4
开启SpringBoot测试
作用域:类,放在类名上
SpringBoot自2.4版本开始用JUnit5,启用测试只需要这一个注解即可,不同于JUnit4
这是复合注解:
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
JUnit5的@Test注解在jupiter包下,JUnit4的@Test不是,注意区分
可重复测试,比如 @RepeatedTest(5) 就是重复跑 5 次
有了这个注解可以不用写@Test
作用域:注解、方法
用于展示测试结果的名称,在控制台更好辨认
作用域:类、方法
替换JUnit4的@Before
本类中每个@Test测试方法运行前都要先运行本注解修饰的方法
注意:运行单个方法是点方法旁边的运行符号,或选中方法名右键运行
替换JUnit4的@After
本类中每个@Test测试方法运行后都会运行本注解修饰的方法
注意:与上类似
替换JUnit4的@BeforeClass
运行整个测试类前都要先运行本注解修饰的方法
注意:运行整个测试类是点类名旁边的运行符号,或选中类名右键运行,被此注解修饰的方法必须都是静态的
替换JUnit4的@AfterClass
运行整个测试类后都会运行本注解修饰的方法
注意:与上类似
替换JUnit4的@Category
表示单元测试类别
替换JUnit4的@Ignore
表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
在执行整个测试类时不会执行这个方法
表示测试方法运行如果超过了指定时间将会返回错误
为测试类或测试方法提供扩展类引用
JUnit5的此注解替代了JUnit4中的@RunWith
嵌套测试
作用域:类
外层的@Test不能驱动内层的@BeforeEach、@AfterEach、@BeforeAll、@AfterAll这些注解的方法运行;内层的@Test可以
用于开启参数化测试
被注释的方法的参数列表中可以定义入参
作用域:方法、注解
配合@ValueSource、@NullSource、@EnumSource、@CsvFileSource、@MethodSource将这些注解中的数据放进被@ParameterizedTest注释的方法参数中
为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
表示为参数化测试提供一个null的入参
表示为参数化测试提供一个枚举入参
表示读取指定CSV文件内容作为参数化测试入参
表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)