1. spring容器注册Bean的方式
1. @Bean注解,注册第三方组件
2. @ComponentScan扫描组件,扫描2中所描述的组件
3. @Import
4. FactoryBean(工厂bean)
5. xml配置
2. 组件的标注
1. @Component
泛指各种组件,就是说当我们的类不属于各种归类的时候
2. @Controller
用于标注控制层,相当于struts中的action层
3. @Service
用于标注服务层,主要用来进行业务的逻辑处理
4. @Repository
用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件
5. @Configuration
用于标注配置文件
其底层都是@Component实现
3. @ComponentScan组件扫描
1. ComponentScan使用
@ComponentScan(value = "com.wang.spring",includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class,Service.class})})
2. FilterType枚举
ANNOTATION 根据注解匹配
ASSIGNABLE_TYPE 根据给定的类型
ASPECTJ 根据表达式匹配
REGEX 正则表达式
CUSTOM 自定义匹配规则,需要实现TypeFilter接口重写match方法
// 自定义匹配规则实现接口
@FunctionalInterface
public interface TypeFilter {
/**
* metadataReader 当前正在扫描到得类信息
* metadataReaderFactory 其他任何类信息
*/
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException;
}
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.filter.TypeFilter;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 可单独重复指定和ComponentScans中包含多个ComponentScan效果相同
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
* 用于扫描带注释的基本包,可为多个包名数组
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 同value功能一致,两者互斥,只能使用其中之一
*/
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
/**
* 表示是否自动检测被注释标注的类
*/
boolean useDefaultFilters() default true;
/**
* 指定哪些类型的组件能被扫描
* 注意:使用时需要将useDefaultFiltes自动检测设置为flase,否则不生效
* @see #resourcePattern()
* @see #useDefaultFilters()
*/
Filter[] includeFilters() default {};
/**
* 指定哪些类型的组件不被扫描
*/
Filter[] excludeFilters() default {};
/**
* Specify whether scanned beans should be registered for lazy initialization.
* <p>Default is {@code false}; switch this to {@code true} when desired.
* @since 4.1
*/
boolean lazyInit() default false;
/**
* Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
* include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
4. 组件注册
4.1 @Scope指定bean的作用域
1. scope取值有四种:
singleton 单例(默认)
IOC容器初始化时就会创建该对象实例放入容器内,每次getBean获取的都是同一个对象实例
prototype 多实例
每次getBean获取时才会创建对象实例,且每次得到不同实例
request 同一个请求创建一个实例
session 同一个session创建一个实例
2. 示例:
@Scope("prototype")
@Bean
public Person getPerson() {
return new Person("张三");
}
3. scope注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
/**
* 默认值,同scopeName作用一样
*/
@AliasFor("scopeName")
String value() default "";
/**
* 指定要为带注释的组件/bean使用的范围的名称。
*/
@AliasFor("value")
String scopeName() default "";
/**
* 指定是否应该将组件配置为范围代理
* 如果是,代理应该是基于接口的还是基于子类的。
* <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
* that no scoped proxy should be created unless a different default
* has been configured at the component-scan instruction level.
* <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
* @see ScopedProxyMode
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
4.2 @Lazy懒加载
1. 懒加载:容器启动时不创建对象实例,第一次getBean获取Bean时才创建对象实例
2. 示例:
@Lazy
@Bean
public Person getPerson() {
return new Person("张三");
}
3. @Lazy注解:
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR,
ElementType.PARAMETER, ElementType.FIELD})
// 可多次使用
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
/**
* 是否进行延迟初始化
*/
boolean value() default true;
}
4.3 @Conditional条件注册组件
1. @Conditional注解在类或方法上
类上:满足条件,该类中的所有配置(例如:注解@Bean的所有方法)才能生效
方法上:满足条件,该方法中的配置生效
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* 实现Condition接口的的子类,重写matches方法,返回true才会加载
*
*/
Class<? extends Condition>[] value();
}
2. Condition接口详解
@FunctionalInterface
public interface Condition {
/**
* context 上下文环境,可获得当前运行环境、bean工厂、bean定义注册信息、类加载器等
* metadata 注解信息
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
3. 示例:当matchs方法返回true时,才将Person组件注册到容器中
// value为实现Condition接口的类
@Conditional(value = {? implement Condition})
@Bean
public Person getPerson() {
return new Person("张三");
}
4.4 @Import快捷注册组件
1. 方便快捷的给容器中注册组件,只能注释在类上
2. @import导入的组件的id跟其他方式导入的区别:默认是类的全类名(包+类名),其他方式导入id为首字母小写的类名
3. 示例:
// User类
public class User {
}
@Configuration
// 导入User组件,bean的id为User类所在包名+类名
@Import({User.class})
public class User {
}
4. @Import注解详解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* 可传入ImportSelector接口的实现类,并注册其方法的返回值类数组
* 可传入ImportBeanDefinitionRegistrar接口的实现类,并将其方法中注册到BeanDefinitionRegistry的bean定义导入容器
*/
Class<?>[] value();
}
4.4.1 ImportSelector接口 + @Import批量注册组件
1. ImportSelector接口详解
public interface ImportSelector{
/**
* 返回值要被注册到容器中的所有类的全类名数组
* importingClassMetadata 可获取当前扫描到的类上的所有注解
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
2. 自定义实现
public class MyImportSelector implement ImportSelector{
// 返回值就是要被注册到容器中的所有类的全类名
@Override
String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.wangl.example.User","com.wangl.example.Role"};
}
}
3. 示例:
// 导入User组件、导入MyImportSelector类中selectImports方法返回的所有类
// MyImportSelector类本身不会被注册
@Configuration
@Import({User.class,MyImportSelector.class})
public class User {
}
4.4.2 ImportBeanDefinitionRegistrar + @Import批量注册组件
1. ImportBeanDefinitionRegistrar接口详解
public interface ImportBeanDefinitionRegistrar {
/**
* 根据给定的数据注册bean定义
* importingClassMetadata 当前扫描到的类的所有注解信息
* registry bean定义信息
*/
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
2. 示例:
// 导入User组件、导入MyImportBeanDefinitionRegistrar类中registerBeanDefinitions方法注册到bean定义中的类
// 自定义类MyImportBeanDefinitionRegistrar不会被注册
@Configuration
@Import({User.class,MyImportBeanDefinitionRegistrar.class})
public class User {
}
3. 自定义实现
public class MyImportBeanDefinitionRegistrar implement ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// User类的bean定义信息
RootBeanDifinition beanDifinition = new RootBeanDifinition(User.class);
// 注册bean定义到容器中
registry.registerBeanDefinition("bean的id",beanDifinition);
}
}
4.5 FactoryBean接口注册bean
1. 实现接口,重写其方法。
public class User implements FactoryBean {
/**
* @Description 返回一个Role对象,这个对象会被注册到IOC容器中。而不是User对象
* @Param []
* @return
*/
@Override
public Object getObject() throws Exception {
return new Role();
}
/**
* @Description 是否为单实例和@Scope注解类似 - true为单实例
* @Param []
* @return
*/
@Override
public boolean isSingleton() {
return true;
}
/**
* @Description 返回bean对象的类型
* @Param []
* @return
*/
@Override
public Class<?> getObjectType() {
return Role.class;
}
}
2. 注意事项
// 注册的其实是Role类,调用了FactoryBean的getObject方法
@Bean
public User getUser() {
return new User();
}
3. 要从IOC容器中获取自定义的FactoryBean本身User的bean对象,其id前需要加”&“符号,源码是给其id前加了该前缀
getBean("&user")
5. bean的生命周期
-
doCreateBean(),创建对象
-
populateBean(),填充属性
-
initializeBean{
3.1 beanPostProcessorBefore(),bean前置处理器
3.2 init(),bean初始化
3.3 beanPostProcessorAfter(),bean后置处理器
}
-
destroy(),对象销毁
5.1 对象创建
- 单实例:容器启动的时候创建对象
- 多实例:每次获取的时候创建对象
5.2 对象初始化
对象实例化、属性赋值完成后调用初始化方法
5.3 对象销毁
- 单实例:容器关闭的时候销毁
- 多实例:容器只创建不进行管理,也就不会自动调用销毁方法
5.4 初始化、销毁方法的实现方式
5.4.1 @Bean - 注解属性指定初始化、销毁
1. 示例
// 返回值为哪个类,该类的实例对象添加初始化和销毁方法
@Bean(value = "user",initMethod = "初始化方法名",destroyMethod = "销毁方法名")
public User getUser() {
return new User();
}
5.4.2 InitializingBean接口初始化、DisposableBean接口销毁
1. 示例
// 注册User组件,哪个类实现接口,该类的实例对象添加初始化和销毁方法
@Component
public class User implements InitializingBean, DisposableBean {
/**
* @Description User对象的初始化方法
* @Param []
* @return
*/
@Override
public void afterPropertiesSet() throws Exception {
}
/**
* @Description User对象的销毁方法
* @Param []
* @return
*/
@Override
public void destroy() throws Exception {
}
}
5.4.3 @PostConstruct - 对象初始化
1. 执行时机
在对象创建完成且属性赋值完成后执行该注解标注的初始化方法,注解只能在方法上。标注在哪个类中的方法上,就为该类的实例对象添加初始化方法
2. @PostConstruct注解
该注解是jdk实现,不是spring框架。PostConstruct的实现其实也是一个BeanPostProcessor,只不过这个BeanPostProcessor实现了PriorityOrdered接口, 所以他的优先顺序也会比普通的postProcessBeforeInitialization优先执行
3. 初始化方法执行的顺序
构造函数 => PostConstruct => postProcessBeforeInitialization =>
InitializingBean => initMethod => postProcessAfterInitialization
4. 源码
package javax.annotation;
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PostConstruct {
}
5.4.4 @PreDestroy - 对象销毁
1. 执行时机
在IOC容器关闭要清理对象之前执行,注解只能在方法上。标注在哪个类中的方法上,就为该类的实例对象添加销毁方法
2. @PreDestroy注解
该注解是jdk实现,不是spring框架。
3. 销毁方法执行的顺序
4. 源码
package javax.annotation;
@Documented
@Retention (RUNTIME)
@Target(METHOD)
public @interface PreDestroy {
}
5.4.5 BeanPostProcessor后置处理器
所有实现该接口的处理器,IOC容器会在每个bean的初始化前后遍历所有的实现,调用其方法
/**
* 会在所有bean初始化前后调用其两个方法
*/
public interface BeanPostProcessor {
/**
* 在bean的初始化之前调用,即5.4.1中的init-mothod、5.4.2中的afterPropertiesSet方法之前调用、
* @PostConstruct标注的方法之前调用
*/
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* 在bean的初始化之后调用,即5.4.1中的init-mothod、5.4.2中的afterPropertiesSet方法之后调用
* @PostConstruct标注的方法之后调用
*/
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
6. 属性赋值
6.1 @Value - 属性赋值
1. 基本数值:字符串、int等
2. SpeL(spring表达式):#{20-15} = 给属性赋值5
3. 获取配置文件中值(运行在环境变量中的值):${对应配置文件key}
yml和application配置文件springboot启动容器时将其加入环境变量中,所以可以直接获取
其余如:properties文件中的值要获取,需要先将properties配置文件加载到spring环境变量中,详细请看6.2加载配置文件
6.2 @PropertySource - 加载properties文件
1. 作用:将配置文件加载到环境变量(environment)中
2. 使用:@PropertySource(value={"classpath://application.properties","也可写文件绝对路径"})
value支持两种形式读取配置文件:
1. 项目的根路径:classpath:/com/myco/app.properties
2. 文件绝对路径:file:/com/myco/app.properties
3. @PropertySource注解的value属性可传入多个配置文件,也可重复多个@PropertySource注解,还可使用
@PropertySources注解传入多个@PropertySource,三种方式殊途同归
6.3 @Autowired - 自动装配
1. 装配方式
1. 默认优先按照类型在容器中找对应组件
2. 若该类型的组件有多个实现,优先获取该注解所标注的属性的名称作为id的组件
3. 如果需要获取指定id的组件,可联合使用@Qualifier注解指定要获取的组件名称
2. @Autowired默认是一定要找到对应的组件,如果未找到直接报错(NoSuchBeanDefinitionException)
1. @Autowired(required = false)指定未找到组件时,也可不装配,即组件为null
3. 标注位置
构造器、@Bean标注方法的参数、set方法、类属性
4. 标注位置详解
1. 【构造器】
1.1 构造器参数包含该类的所有属性且都赋值,都会自动装配成功。
1.2 构造器参数少于属性或有未进行构造赋值的参数,该参数都不会自动装配
1.3 当只有一个有参构造时,可省略@Autowired注解,构造中进行赋值的参数也会装配成功
1.4 多个有参构造时,不能省略。且需要按需在构造方法上标注注解
2. 【set方法】
2.1 实例化bean后,会调用set方法给对应属性赋值。且set方法的参数对象也是从容器中获取,所以
其形参都需要注入到容器中
3. 【@Bean标注方法的参数】
3.1 @Bean标注的方法,其方法的形参默认都是从容器中获取,参数是否标注@Autowired注解都无所 谓,所以其形参都需要注入到容器中
5. 具体实现:Autowired是由AutowiredAnnotationBeanPostProcessor类解析完成装配功能
6.3.1 @Qualifier - 自动装配指定组件
1. 当@Autowired标注的属性有多个实现,需要获取指定id的组件时,可联合使用@Qualifier注解指定要获取的组件名称
2. 明确指定后优先级最高,高于@Autowired的默认选择,也高于@Primary标注的首选
3. 示例:
public class User {
// 自动装配
@Qualifier("role2")
@Autowired
private Role role;
}
// 注册Role组件,id为:role
@Component
public Class Role {
}
@Configuration
public Class Config {
// 注册Role组件,id为:role2
@Bean("role2")
public Role getRole() {
return new Role();
}
}
6.3.2 @Primary - 自动装配首选组件
1. 当@Autowired标注的属性有多个实现,需要首选获取某个实现时,可使用@Primary注解
2. 示例:
public class User {
// 自动装配,会首选role2进行注册
@Autowired
private Role role;
}
// 注册Role组件,id为:role
@Component
public Class Role {
}
@Configuration
public Class Config {
// 注册首选Role组件,id为:role2
@Primary
@Bean("role2")
public Role getRole() {
return new Role();
}
}
6.4 @Resource
1. 所属java规范
JSR250
2. 作用
同@Autowired一样可实现自动装配功能,但不支持联合@Qualifier、@Qualifier使用。
3. 装配方法
默认按照属性名称进行装配组件
可指定组件名称进行装配:@Resource(name="role2")
6.5 @Inject
1. 所属java规范
JSR330
2. 使用
需要导入包:javax.inject包,才能使用
3. 作用
同@Autowired一样可实现自动装配功能,可以支持联合@Qualifier、@Qualifier使用,但是不能指定属性是否可以为非必需的:required = false
7. Aware接口
1. 作用
自定义组件需要使用Spring容器底层实现的一些组件时,可使自定义组件实现spring提供的对应Aware接口
2. aware功能的实现
XXAware接口 ========》 XXAwareProcessor解析实现
3. 种类(非全部)
1. BeanNameAware#setBeanName - bean名称
2. BeanClassLoaderAware#setBeanClassLoader
3. BeanFactoryAware#setBeanFactory - bean工厂
4. EnvironmentAware#setEnvironment
5. EmbeddedValueResolverAware#setEmbeddedValueResolver- 字符串解析器(解析#{}、 ${}等占位符,${}可获取到容器启动后环境变量中的值,#{}可执行spring提供表达式)
6. ResourceLoaderAware#setResourceLoader - 资源加载器
7. ApplicationEventPublisherAware#setApplicationEventPublisher
8. MessageSourceAware#setMessageSource - 国际化
9. ApplicationContextAware#setApplicationContext - IOC容器
10. ServletContextAware#setServletContext
11. LoadTimeWeaverAware#loadTimeWeaverAware
12. ImportAware#setImportMetadata
13. ImportAware > BeanNameAware#setBeanName
7.1 接口的执行顺序及实现
1、其中 1~3 的执行顺序在 AbstractAutowireCapableBeanFactory 类中,该类继承的 AbstractBeanFactory#createBean 方法中,按照 doCreateBean > initializeBean > invokeAwareMethods 顺序调用,在 invokeAwareMethods 方法中,按照以下顺序处理 Aware 接口
private void invokeAwareMethods(final String beanName, final Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
ClassLoader bcl = getBeanClassLoader();
if (bcl != null) {
((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
}
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
}
}
}
2、其中 3~8 的执行顺序在 ApplicationContextAwareProcessor
类中,该类实现的 BeanPostProcessor#postProcessBeforeInitialization
方法中,按照以下顺序处理的 Aware 接口
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
}
3、 9 在 ServletContextAwareProcessor
中处理,该类实现的 BeanPostProcessor#postProcessBeforeInitialization
方法中,按照以下顺序处理的 Aware 接口:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (getServletContext() != null && bean instanceof ServletContextAware) {
((ServletContextAware) bean).setServletContext(getServletContext());
}
if (getServletConfig() != null && bean instanceof ServletConfigAware) {
((ServletConfigAware) bean).setServletConfig(getServletConfig());
}
return bean;
}
4、10 只有在使用 @EnableLoadTimeWeaving 或者存在 LoadTimeWeaver 实现的 Bean 时才会调用,顺序也很靠后。
11 只有被其他配置类 @Import(XX.class) 时才会调用,这个调用对 XX.class 中的所有 @Bean 来说顺序是第 1 的。
12 为了证明 11,注意 11 和 12 和前面所有输出不是同一个 Bean
8. @Profile - 组件生效环境
1. 指定组件可被注册到容器中的环境
2. 加了@Profile环境标识注解的bean,只有当指定的环境激活时才能被注册到容器中,默认是default环境
3. 当@Profile写在配置类上,只有当前环境是指定的环境时,整个配置类中的所有配置才能生效,否则都不生效
4. 没有指定生效环境的类或方法,在任何环境下都生效
5. 示例
1. 可标注在数据源方法上,切换环境变更数据源链接
2. @Profile("test")、@Profile("dev")、@Profile("prod"),可自定义,推荐有实际意义
6. 环境指定
1. 通过IDE开发工具指定JVM环境VM options : -Dspring.profiles.active=test
2. 通过applicationContext指定:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 获取容器环境并指定当前生效环境
applicationContext.getEnvironment().setActiveProfiles("dev");
// 手动注册配置文件
applicationContext.register(MainConfig.class);
// 手动刷新容器
applicationContext.refresh();