@Bean注解详解:

1.常见问题思考:

问题1:为啥下面我们明明使用了@Bean 注解但是在spring容器中却找不到该catDemo啊?

1
2
3
4
5
6
public class config {
@Bean
public Cat catDemo(){
return new Cat("小猫!");
}
}

原因:单独一个@Bean修饰的 只是一个bean的定义,并没有注册到spring容器中!spring中要使用@Bean修饰 的bean需要将该bean注册到spring容器中!可以使用@Componet 注解 或者该注解修饰的注解将该类注册到容器中的同时连带注册@Bean 修饰的bean .
解决方法:

1
2
3
4
5
6
7
8
9
10
11
1.在类上加入@Componet注解或者是@Componet注解修饰的注解,把该类注入到spring容器,然后由spring
容器扫描处理@Bean注解修饰的方法!
//@Configuration
// @Component
public class config {
@Bean
public Cat catDemo(){
return new Cat("小猫!");
}
}
//其实上面的@Configuration注解其也是被@Componet注解修饰了的

理解:

SpringBoot - @Bean注解详解_springboot @bean注解-CSDN博客
1.@Bean注解就是把我们将要实例化的对象转化成一个Bean,放在Spring容器中,等我们使用时,就会和@Autowired、@Resource配合到使用拿到该实例。注册BEAN的方法有@ComponentScan、@Bean、@Import、@Component、@Repository、@ Controller、@Service 、 @Configration等等。
2.@Bean 注解:作用在方法上,声明当前方法的返回值是一个Bean对象;单独只有这个注解修饰的方法,只是实现了对bean的一种定义,其类没有注册到spring容器中,那么该方法返回的bean也没有注册到spring容器中!
3.注解@Bean被声明在方法上,该方法都需要有一个返回类型,这个方法的返回类型就是注册到IOC容器中的类型,接口和类都是可以作为返回类型,介于面向接口原则,提倡返回类型为接口。该注解主要用在@Configuration注解的类的方法上,也可以用在@Component注解的类的方法上,添加的bean的id为方法名。
4.使用要点
(1)@Bean 注解:作用在方法上,方法都需要有一个返回类型;
(2)@Bean 注解:用于表示当前方法返回一个 Spring 容器管理的 Bean;
(3)@Bean 的默认的名字和方法名一致(一般Bean都是首字母小写,因为方法名的首字母一般都是小写的);
(4)@Bean 注解:一般和 @Component 或者 @Configuration 一起使用;
(5)@Bean 注解:默认作用域为单例作用域,可通过 @Scope(“prototype”) 设置为原型作用域;
(6)@Bean 注解:可以接受一个 String 数组来设置多个别名;
(7)@Configration 注解类中可以声明多个 @Bean 的方法,并且声明的 Bean 与 Bean 之间是可以有依赖关系的;
5.组合使用
@Bean 注解常常与 @Scope、@Lazy,@DependsOn 和 @link Primary 注解一起使用:
(1)@Profile 注解:为不同环境下不同的配置提供了支持,如开发和生产环境的数配置是不同的;
(2)@Scope 注解:将 Bean 的作用域从单例改变为指定的作用域;
(3)@Lazy 注解:只有在默认单例作用域的情况下才有实际效果;
(4)@DependsOn 注解:表示在当前 Bean 创建之前需要先创建特定的其他 Bean的场景时,添加该注解。

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Data
public class MyHello {
public void init() {
System.out.println("初始化...");
}
public void destroy() {
System.out.println("销毁...");
}
public String get() {
return "获取信息...";
}
}
@Component
public class config {
// 下面的bean注解声明了改bean的初始化方法和销毁方法!
@Bean(initMethod = "init", destroyMethod = "destroy")
public MyHello hi() {
return new MyHello();
}
}
@RestController
@AllArgsConstructor
public class HelloController {
// 下面使用的是构造器注入bean依赖方法!当类中只有一个构造器方法时,可以省略@Autowied注解
private MyHello hello;

@GetMapping("/test")
public String test() {
hello.get();
return "OK";
}
}

@Configuration:

【Spring注解必知必会】深度解析@Configuration注解 - 掘金
注解源码:
一般修饰在类上!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {


@AliasFor(annotation = Component.class)
String value() default "";


boolean proxyBeanMethods() default true;

}

属性说明:

  • value: 自定义当前组件或者说bean的名称,实际就是@Component的value属性。
  • proxyBeanMethods: 判断是否bean的方法应该被代理,默认是true,后面原理解析中重点分析。

元注解说明:

  • 该注解只能使用在类,接口、枚举、其他注解上
  • 该注解的生命周期是运行时JVM
  • @Component元注解,相当于拥有了@Component注解的能力,可以被ComponentScan扫描到,变成spring中的Bean。

proxyBeanMethods()属性:

1.proxyBeanMethods属性默认为true,当其值为false时,将不会进行代理处理,并且每次调用@Bean注解标注的方法时都会创建一个新的Bean实例。

@Component注解:

说明:
用来标记的类是一个“组件”或者说是一个Bean,Spring会自动扫描标记;注解的类作为一个Spring Bean对象。;该注解只能使用在类,接口,枚举,和其他注解上!
注解源码:

1
2
3
4
5
6
7
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}

属性说明:
value:自定义当前组件或者说bean的名称!如果不配置,默认为组件的首字母小写的类名!

@Scope:

【Spring注解必知必会】全面了解@Scope - 掘金
源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {


@AliasFor("scopeName")
String value() default "";


@AliasFor("value")
String scopeName() default "";


ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

}

注解有3个属性,value和scopeName一样,用来表示注解的作用于范围。proxyMode用来为spring bean设置代理。
作用域(value或者scopeName)属性范围

  • singleton: 默认值,单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例。
  • prototype: 原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例。
  • request: 对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效。
  • session: 对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效。

默认的作用域范围为singleton。
proxyMethod属性:

  • DEFAULT:proxyMode的默认值,一般情况下等同于NO,即不需要动态代理。
  • NO:不需要动态代理,即返回的是Bean的实例对象。
  • INTERFACES:代理的对象是一个接口,即@Scope的作用对象是接口,这种情况是基于jdk实现的动态代理。
  • TARGET_CLASS:代理的对象是一个类,即@Scope的作用对象是一个类,上面例子中的ClassB就可以用这种代理,是以生成目标类扩展的方式创建代理,基于CGLib实现动态代理。

后面通过实例来讲解下我们为什么要有这个属性。

使用注解:

前面讲了该注解作用在类上或者方法上,但是其实它前提必须是一个Bean,所以存在下面两种情况:
作用在类上
搭配@Component、@Service注解

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class Student {

private String name;

private Integer age;

public Student() {
System.out.println("实例化学生对象~~~");
}
}

作用在方法上
搭配@Bean注解使用

1
2
3
4
5
6
7
8
9
10
@Configuration
public class ScopeConfig {

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Student getStudent() {
return new Student();
}
}

通常我们不配置Scope情况下的Bean的作用域都是单例模式singleton,不进行任何代理。

实例演示:

我们前面讲解了通过Bean如何控制我们Bean的作用域范围,那我们通过例子演示验证下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PrototypeBean {

PrototypeBean() {
System.out.println("实例化 PrototypeBean");
}

public void init() {
System.out.println("初始化 PrototypeBean");
}

public void destroy() {
System.out.println("销毁 PrototypeBean");
}
}

1
2
3
4
5
6
@Bean(initMethod = "init", destroyMethod = "destroy")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}

验证代码:

1
2
3
4
5
6
7
PrototypeBean prototypeBean1 = context.getBean(PrototypeBean.class);
System.out.println(prototypeBean1);
PrototypeBean prototypeBean2 = context.getBean(PrototypeBean.class);
System.out.println(prototypeBean2);
System.out.println(prototypeBean1 == prototypeBean2);
context.close();

执行结果:

1
2
3
4
5
6
7
8
实例化 PrototypeBean
初始化 PrototypeBean
com.alvinlkk.scope.PrototypeBean@26a94fa5
实例化 PrototypeBean
初始化 PrototypeBean
com.alvinlkk.scope.PrototypeBean@464a4442
false

小结:

  1. prototype多例模式,每次在调用getBean() 获取实例时,都会重新实例化,初始化。
  2. prototype多例模式,它的Bean实例对象则不受IOC容器的管理,最终由GC来销毁。
  3. ingleton单实例模式下,多次getBean()取到的对象是一样的。
  4. 针对单实例bean的话,容器启动的时候,bean的对象就创建了,而且容器销毁的时候,也会调用Bean的销毁方法。

@ConfigurationProperties:

@ConfigurationProperties注解的作用是什么?
将标注了@ConfigurationProperties注解的Spring容器中的Bean与配置文件中的属性进行一一绑定,用于更加快速、方便的读取配置文件的内容。
也就是:先将属性对象注册到Spring容器中,再进行属性的绑定。(正是因为该特性,即使在方法走set了某些属性,其也会被配置文件中的属性覆盖)
@ConfigurationProperties注解可以搭配使用的注解有哪些?
只要能够将属性对象注册到Spring容器中的注解都可以搭配使用。
@RestController、@Service、@Repository、@Component、@Configuration、@Bean都可以和@ConfigurationProperties注解搭配使用。
@ConfigurationProperties注解如何使用?:

1
2
3
4
5
1.需要将配置对象类注入到Spring容器中,一般使用@Component(组件类)进行注入,当然@RestController@Service@Repository都属于@Component,因为他们有更准确的意义,所以一般使用@Component;或者不使用组件类,而是在启动类上使用@EnableConfigurationProperties(DroolsProperties.class),将配置对象类注入到Spring容器中;或者在定义Bean的时候将配置信息和配置对象的属性进行一一绑定。
2.需要指定前缀,用于表示该前缀下面的配置信息需要和配置对象的属性进行一一绑定;
3.只能加载全局配置文件(application.yml/.properties,application-XXX.yml/.properties);
4.支持 数据校验,如:不能为空,长度,正则等校验;
5.支持复杂的数据类型,如:list、map;

①. 使用@Component + @ConfigurationProperties(prefix = “spring.drools”),这两个注解都标注在配置对象上。

1
2
3
4
5
6
7
DroolsProperties.java如下:

@Data
@Component
@ConfigurationProperties(prefix = "spring.drools")
public class DroolsProperties {}

②. @EnableConfigurationProperties(DroolsProperties.class) + @ConfigurationProperties(prefix = “spring.drools”) ,注意:配置对象上的注解只有@ConfigurationProperties,@EnableConfigurationProperties标注在启动类上。

1
2
3
4
5
@Data
@ConfigurationProperties(prefix = "spring.drools")
public class DroolsProperties {
...
}
1
2
3
4
5
6
7
8
9
10
11
MySpringApplication.java如下:

@EnableCustomSwagger2
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableConfigurationProperties(DroolsProperties.class)
public class MySpringApplication{
public static void main(String[] args) {
SpringApplication.run(MySpringApplication.class, args);
}
}

③. @Bean + @ConfigurationProperties(prefix = “spring.drools”),配置类上没有注解,在定义Bean时将将配置信息和配置对象的属性进行一一绑定。

1
2
3
4
@Data
public class DroolsProperties {
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@EnableCustomSwagger2
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MySpringApplication{
@Bean
@ConfigurationProperties(prefix = "person")
public DroolsProperties droolsProperties (){
DroolsProperties p = new DroolsProperties();
return p;
}

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

@EnableConfigurationProperties:

@EnableConfigurationProperties注解的作用是什么?
将标注了@ConfigurationProperties注解的类注入到Spring容器中。该注解是用来开启对@ConfigurationProperties注解的支持。也就是@EnableConfigurationProperties注解告诉Spring容器能支持@ConfigurationProperties注解。

@EnableConfigurationProperties注解如何使用?

一般情况下会定义两个文件,一个用于绑定application.xml中的配置信息,一个用于定义配置类。一般情况下@EnableConfigurationProperties搭配@Configuration和@SpringBootApplication使用!
①. 定义一个属性类用于绑定配置信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Data
@ConfigurationProperties(prefix = "spring.drools")
public class DroolsProperties {
// 规则文件和决策表的路径(多个目录使用逗号分割)
private String path;
// 更新缓存的轮询周期 - 单位:秒(默认30秒)
private Long update;
// 模式: stream 或 cloud(默认stream模式)
private String mode;
// 是否开启监听器:true = 开, false = 关闭(默认开启)
private boolean listener;
// 是否自动更新:true = 开, false = 关闭(默认开启)
private boolean autoUpdate;
// 是否开启DRL的语法检查: true = 开, false = 关闭(默认开启)
private boolean verify;
// 是否开启REDIS的缓存: true = 开, false = 关闭(默认开启)
private boolean useRedis;
}

②. 定义一个配置类用于开启文件属性的绑定功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 配置类
@Configuration
// 开启属性文件绑定功能
@EnableConfigurationProperties(DroolsProperties.class)
public class DroolsConfig {
@Bean
@ConditionalOnMissingBean(name = "kieTemplate")
public KieTemplate kieTemplate(DroolsProperties droolsProperties) {
KieTemplate kieTemplate = new KieTemplate();
kieTemplate.setPath(droolsProperties.getPath());
kieTemplate.setMode(droolsProperties.getMode());
if (droolsProperties.isAutoUpdate()) {
// 启用自动更新
kieTemplate.setUpdate(droolsProperties.getUpdate());
} else {
// 关闭自动更新
kieTemplate.setUpdate(999999L);
}
kieTemplate.setListener(droolsProperties.isListener());
kieTemplate.setVerify(droolsProperties.isVerify());
kieTemplate.setUseRedis(droolsProperties.isUseRedis());
return kieTemplate;
}
}

@Import 注解:

spring注解之@Import注解的三种使用方式 - 掘金

@Import注解须知:

1、@Import只能用在类上 ,@Import通过快速导入的方式实现把实例加入spring的IOC容器中
 2、加入IOC容器的方式有很多种,@Import注解就相对很牛皮了,@Import注解可以用于导入第三方包 ,当然@Bean注解也可以,但是@Import注解快速导入的方式更加便捷
3、@Import注解有三种用法
该注解主要是将第三方包注入IOC容器中!@Import 注入的容器明明都有相关注解了为什么还需要额外的@Import 的注解了?
那是因为spring启动类能扫描到的注范围有限!使用该注解可以让spring启动类扫描到第三方包里面的相关注解!

简单示例分析:

image.png
image.png

@Import的三种用法:

1、直接填class数组方式 2、ImportSelector方式【重点】 3、ImportBeanDefinitionRegistrar方式

第一种用法:直接填class数组:

直接填对应的class数组,class数组可以有0到多个。

1
2
3
4
5
@Import({ 类名.class , 类名.class... })
public class TestDemo {

}

对应的import的bean都将加入到spring容器中,这些在容器中bean名称是该类的全类名

第二种用法:ImportSelector方式

这种方式的前提就是一个类要实现ImportSelector接口,假如我要用这种方法,目标对象是Myclass这个类,分析具体如下:

1
2
3
4
5
6
7
8
public class Myclass implements ImportSelector {
//既然是接口肯定要实现这个接口的方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[0];
}
}

分析实现接口的selectImports方法中的:

  • 1、返回值: 就是我们实际上要导入到容器中的组件全类名【重点
  • 2、参数: AnnotationMetadata表示当前被@Import注解给标注的所有注解信息【不是重点】

需要注意的是selectImports方法可以返回空数组但是不能返回null,否则会报空指针异常!

1
2
3
4
5
6
7
public class Myclass implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.yc.Test.TestDemo3"};
}
}

1
2
3
4
5
6
7
8
9
@Import({TestDemo2.class,Myclass.class})
public class TestDemo {
@Bean
public AccountDao2 accountDao2(){
return new AccountDao2();
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 打印容器中的组件测试
*/
public class AnnotationTestDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestDemo.class); //这里的参数代表要做操作的类

String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String name : beanDefinitionNames){
System.out.println(name);
}

}
}

image.png

第三种用法:ImportBeanDefinitionRegistrar方式:

这种用法比较自定义化注册:

1
2
3
4
5
6
7
8
public class Myclass2 implements ImportBeanDefinitionRegistrar {
//该实现方法默认为空
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {

}
}

参数分析:

  • 第一个参数:annotationMetadata 和之前的ImportSelector参数一样都是表示当前被@Import注解给标注的所有注解信息
  • 第二个参数表示用于注册定义一个bean
1
2
3
4
5
6
7
8
9
10
public class Myclass2 implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
//指定bean定义信息(包括bean的类型、作用域...)
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestDemo4.class);
//注册一个bean指定bean名字(id)
beanDefinitionRegistry.registerBeanDefinition("TestDemo4444",rootBeanDefinition);
}
}

1
2
3
4
5
6
7
8
9
10
@Import({TestDemo2.class,Myclass.class,Myclass2.class})
public class TestDemo {

@Bean
public AccountDao2 accountDao222(){
return new AccountDao2();
}

}

image.png

@ConditionalOnMissingBean:

对比说明:
@ConditionalOnBean有则注入;@ConditionalOnMissBean没有则注入;@Conditional条件满足则注入。

1
2
3
4
5
6
7
8
9
@ConditionalOnMissingBean注解只作用在@Bean定义的方法上
建议仅仅在自动配置类中使用此注解,虽然放在其他类中也不会报错
该注解仅能匹配已经被当前应用上下文管控的Bean定义
若候选Bean是被其他配置类创建的,需要使用@AutoConfigureBefore@AutoConfigureOrder
进行配置类先后注入顺序的控制,确保这个条件在其后运行
Condition相关的处理是在包扫描的时候执行的,因此Bean的添加顺序和包扫描的顺序有关,
而包扫描的顺序依赖包名和类名的字符排序,同时和maven的pom文件中包引入的顺序也有关系,
先引入的包先被扫描到,所以在实际的项目中,我们可以修改类路径或者调整包引入顺序来调整Bean
的添加顺序

@Autowired:

@Autowired是一种注解,可以对构造器、方法、参数、字段和注解进行标注,源码如下:

1
2
3
4
5
6
7
8
9
10
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
// @Autowired 只有一个required元素,默认是true
// require=ture 时,表示解析被标记的属性/方法,在容器中一定有对应的bean存在,否则报错。
// require=false时,表示解析被标记的属性/方法,在容器中没有找到对应的bean依然不会报错。
boolean required() default true;
}

(1). @Autowired一般用在Controller层中用来装配Service对象,当同一个Service接口对象有多个实现时,需要使用@Qualifier来制定具体的装载依赖,否则会报错,除非用@Autowired标注的属性/方法的类型是Map或者List:No qualifying bean of type ‘com.hadoopx.file.service.ISysFileService’ available: expected single matching bean but found 2: LocalSysFileServiceImpl,FastDfsSysFileServiceImpl,当然这个问题也可以使用@Primary来解决。
NoUniqueBeanDefinitionException,说明有多个满足条件的bean进行自动装配,程序无法正确做出判断使用哪一个,通过将@Qualifier注解与我们想要使用的特定Spring bean的名称一起进行装配,Spring框架就能从多个相同类型并满足装配要求的bean中找到我们想要的
(2). @Autowired根据类型进行自动装配,如果需要按名称进行装配,则需要配合@Qualifier使用。
(3). @Autowired在装配依赖对象时,默认要求依赖对象必须存在,如果允许为NULL,需要设置required属性为false。
(4). 被@Autowired标注的属性/方法所在的类,需要是Spring容器中的bean。
(5). 被@Autowired标注的属性/方法的类型是Map且Key为String类型,Spring则会把所有符合该类型的bean装载到Map中,key是bean名称,value则为该bean的实例。在简单工程模式中,可以使用该特性替换传统的将服务实现存入Map的方式。
(6). 被@Autowired标注的属性/方法的类型是List,Spring则会把所有符合该类型的bean装载到List集合中。
(7). @Autowired与@Resource都可以用来装配bean,都可以写在字段上。@Autowired默认按类型装配,@Resource(这个注解属于J2EE的),默认安照名称进行装配,名称可以通过name属性进行指定。