注解
1. @Bean 注册bean
1.简介
Bean可以说是Spring当中最为重要的概念之一了。
简单来说,Bean就是一个对象,只不过这个对象是由Spring容器来初始化,装配,管理的,因此也可以叫做Spring Bean
。
@Bean放在方法上面,用处就是告诉spring容器在初始化的时候,使用这个方法生成一个Bean对象
要做好Java, 需要将Bean(豆子)注入Container(咖啡壶)
2.使用
当注解中只传入value一个属性的时候,value属性的名称可以省略;在源码中,我们可以看见,name是value的别名,意味着name和value这两个属性是一个含义。
即:
@Bean("myBean")
等同于@Bean(value="myBean")
,也等同于@Bean(name="myBean")
。
通常,@Bean
方法在@Configuration
类中声明。
java
@Configuration
public class AppConfig {
@Bean
public FooService fooService() {
return new FooService(fooRepository());
}
@Bean
public FooRepository fooRepository() {
return new JdbcFooRepository(datasource());
}
// ...
}
在上面的代码中,我们向Spring容器
中注入了两个Bean,当没有显式命名时,自动注册的名称为方法名。
使用name属性显式命名如下:
java
@Bean({"b1", "b2"}) // Bean可以用'b1'或者'b2'取到,而非'myBean'
public MyBean myBean() {
// 此处初始化和配置MyBean对象
return obj;
}
3.补充说明
-
Bean默认为单例模式,即对于一个Bean而言,每次取出的都是同一个对象。
-
一些注解可以和@Bean组合使用:
@Profile
允许选择性包含某些bean
@Scope
将bean的范围从单例更改为指定的范围
@Lazy
在使用是才创建bean
@DependsOn
会在创建此bean前创建特定的其他bean,以及该bean通过直接引用表示的任何依赖关系
@Primary
用于在注入点级别解决歧义性
2. @Configuration 配置
1.简介
@Bean
注解往往和@Configuration
配合,前者标注在方法上,后者标注在类上;两者搭配,将Bean注册到容器中。事实上,正如该注解字面上的意思一样,该注解不仅可以注册Bean,基本上配置相关的内容它都有涉及。
而我们可以从@Configuration的源码中看到,它实质上也是一个@Component
注解,所以该注解标记的类本身也会被注册成一个Bean
2.使用
java
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
// instance, configure and return bean
}
}
它指示类生成一个或者多个@Bean
方法,可以由Spring容器处理,在运行时为这些Bean生成BeanDefination和服务请求。
3. @Component 注册bean
1.简介
从前面我们知道,所谓的Bean
其实就是一个个对象;但是@Bean
注解是标注在方法上的,意思就是通过方法返回一个对象,那么有没有直接通过类获取对象的呢?当然有,那就是@Component
,被该注解标注的类会被注册到当前容器,bean的id就是类名转换为小驼峰。
2.使用
直接标注在类上,Spring扫描时会将其加入容器。
java
@Component // or @Component("myBean")
public class MyClass {
// write your bean here...
}
Spring常用注册Bean注解:
- @Component, @Service, @Repository, @Controller注解作用于类上,实质上是一样的,注册类到当前容器,value属性就是BeanName
- @Configuration注解也作用于类上,该注解通常与**@Bean**配合使用,注册方法返回类型作为对象,用作配置。
- @Bean用于方法上,该方法需要在**@Configutation**标注的类里面,且方法必须为public
3.补充说明
@Component注解的效果与@Bean的效果类似,也是单例模式。可以搭配的注解也类似,例如@Scope
, @Profile
, @Primary
等等。
4. @Autowired 注入bean
1.简介
我们创建Bean的目的,就是是为了在需要的时候使用,@Autowired注解可以将bean自动注入到类的属性中。@Autowired注解可以直接标注在属性上,也可以标注在构造器,方法,甚至是传入参数上,本质都是调用了setter方法注入
。
当自动注入的Bean不存在时,Spring会报错;如果希望Bean存在时才注入,可以使用**@Autowired(required=false)**。
2.使用
标注在属性上:
@Autowired
private MyBean myBean;
标注在方法上:
@Autowired
public void setMyBean(MyBean myBean) {
this.myBean = myBean;
}
标注在构造函数上:
@Autowired
public MyClass(MyBean myBean) {
this.myBean = myBean;
}
标注在方法参数上:
public void setMyBean(@Autowired MyBean myBean) {
this.myBean = myBean;
}
3.补充说明
- @Autowired 和 @Resource 注解都是作为bean对象注入时使用的,@Autowired是Spring提供的注解,而@Resource是J2EE本身提供的。
- @Autowired 首先根据类型去寻找注入的对象,如果有多个再根据名字匹配。
- 当名字也无法区分时可以通过
@Qulifier
通过Bean的名字显式指定,如:
@Autowired
@Qualifier("userServiceImpl1")
private UserService userService;
5. @Qualifier bean区分
1.简介
当同一个类型的Bean创建了多个时,我们可以通过搭配 @Autowired 和 @Qualifier 来确定需要注入的Bean解决混淆。除此以外,@Qualifier 还可以标注在Bean上实现逻辑分组。
2.使用
- 标注在单个属性上,是根据name去获取Bean:
@Autowired
@Qualifier("bean2")
private MyBean mybean;
@Bean
public MyBean bean1() {
return new MyBean();
}
@Bean
public MyBean bean2() {
return new MyBean();
}
- 标注在集合上,则可以筛选出被 @Qualifier 标注的Bean
@Autowired
private List<User> users; // user1, user2, user3
@Autowired
@Qualifier
private List<User> usersQualifier; // user2, user3
@Bean
public User user1() {
return new User();
}
@Bean
@Qualifier
public User user2() {
return new User();
}
@Bean
@Qualifier
public User user3() {
return new User();
}
6. @Primary 优先注入
1.简介
当一个接口有多个实现时,我们可以通过给@Autowired
注解搭配@Qualifier
来注入我们想要的Bean。这里还有另一种情况:Bean之前分优先级顺序,一般情况下我们只会注入默认实现;这个时候可以采用@Primary
注解,该注解标注于Bean上,指示了优先注入的类。
2.使用
使用时直接标注在返回Bean的方法上
@Autowired
private MyBean myBean(); // 注入myBean1
@Primary
@Bean
public MyBean myBean1() {
return new MyBean();
}
@Bean
public MyBean myBean2() {
return new MyBean();
}
也可以标注在@Component的类上(@Controller, @Service, @Repository也是一样的)
@Primary
@Component
public class MyBean {
//...
}
7. @Scope bean作用域
1.简介
Spring中的Bean默认是单例模式,在代码各处注入的一个Bean都是同一个实例;我们将在Spring IoC创建的Bean对象的请求可见范围称为作用域
。注解@Scope
可用于更改Bean的作用域。
2.属性
我们在@Scope 注解的源码中可以看到,它其共有三个属性,分别为
value、scopeName、proxyMode
(其中value和scopeName两个方法作用是一样的)
对于value和scopeName,指的是bean生成的模式。
在给他们赋值的时候,可以直接传入字符串,
例如:@Scope("prototype")
但这种方法字符串拼写错误不容易发现。所以Spring提供了默认的枚举类型,避免我们写错
Spring提供了默认的参数:
传入作用域类型时,可以直接传入字符串,例如:@Scope("prototype"),但这种方法字符串拼写错误不容易发现;Spring提供了默认的参数:
类型 | 作用 |
---|---|
ConfigurableBeanFactory.SCOPE_PROTOTYPE | 原型模式,每次调用时创建新对象 |
ConfigurableBeanFactory.SCOPE_SINGLETON | 单例模式,每次调用返回同一个对象 |
WebApplicationContext.SCOPE_REQUEST | 每个请求返回一个新对象 |
WebApplicationContext.SCOPE_SESSION | 每个会话使用一个新对 |
对于proxyMode,指的是代理类型。
Spring也提供了一个枚举类型
类型 | 作用 |
---|---|
ScopedProxyMode.INTERFACES | 使用JDK的动态代理来创建代理对象 |
ScopedProxyMode.TARGET_CLASS | 使用CGLIB来创建代理对象 |
ScopedProxyMode.NONE | 不代理 |
ScopedProxyMode. DEFAULT | 不代理,同上 |
3.使用
作用域分为:
基本作用域(singleton, prototype)
Web作用域(request, session, globalssion)
由于默认为单例模式,所以需要标注时一般都是使用prototype
@Bean
@Scope("prototype") // @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyBean myBean() {
return new MyBean();
}
prototype是原型模式,具体可以看设计模式中的"原型模式",每次通过容器的getBean方法获取Bean时,都将产生一个新的Bean实例。
4.补充说明
常见使用误区,单例调用多例:
// SingletonBean是单例的
@Component
public class SingletonBean {
@Autowired
private PrototypeBean bean;
}
@Component
public class XXX {
// PrototypeBean是多例的原型模式
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public PrototypeBean getBean(){
return new PrototypeBean();
}
}
上面的代码中,外面的SingletonBean默认单例模式,里面的PrototypeBean设置成原型模式(多例)。
这里并不能达到我们每次创建获得新PrototypeBean是多例的实例的效果。
因为@Autowired
只会在单例SingletonBean初始化的时候注入一次,再次调用SingletonBean的时候,PrototypeBean不会再创建了(注入时创建,而注入只有一次)。
解决方法1:不使用@Autowired
,每次调用多例的时候,直接调用Bean;
解决方法2:Spring的解决办法:设置proxyMode, 每次请求的时候实例化。
两种代理模式的区别:
ScopedProxyMode.INTERFACES: 创建一个JDK代理模式
ScopedProxyMode.TARGET_CLASS: 基于类的代理模式
前者只能将其注入一个接口,后者可以将其注入类。
8. @Lazy 懒加载
1.简介
Spring Boot中Bean默认的作用域为单例模式,而Spring IoC容器会在启动的时候实例化所有单例Bean。熟悉单例模式的朋友也许立刻想到了饿汉模式和懒汉模式。
在Spring Boot中,@Lazy用于指定Bean是否取消预初始化,该注解可以用于直接或者间接用了@Component
注解的类,或使用了@Bean
的方法。
PS: Spring使用的是单例注册表实现的单例模式。
2.使用
@Lazy注解使用在**@Bean**方法上:
@Bean
@Lazy
public MyBean myBean() {
return new MyBean();
}
也可以在**@Comfiguration类上使用,该类中所有的@Bean**方法都会延迟初始化
@Configuration
@Lazy
public class MyConfig() {
@Bean
public MyBean myBean1() {
return new MyBean();
}
@Bean
public MyBean myBean2() {
return new MyBean();
}
}
在标注了**@Lazy的@Configuration类内,在某个@Bean方法上标注@Lazy(false)**,表明这个bean立即初始化。
@Configuration
@Lazy
public class MyConfig() {
@Bean
@Lazy(false)
public MyBean myBean1() {
return new MyBean();
}
}
9. @Profile bean分组
1.前言
- Profile单词本身的意思是"轮廓","侧写",在编程的其他领域,这个词有针对每个用户的数据存储的意思。在Spring Boot当中,这个词用于区分不同的环境,如开发环境,测试环境,生产环境。
- 该注解可以传入多个字符串,@Profile("prod"), **@Profile({"test","master"})**都是合法的使用,如果有多个条件,需要同时满足
2.使用
一般在**@Configuration**下使用,标注在能返回Bean的类或者方法上,标注的时候填入一个字符串,作为一个场景或者一个区分。
@Configuration
@Profile("dev")
public class MyConfig {
// your beans here
}
当Profile为dev时,上面的代码才会在Spring中注册MyConfig这个类。
在Profile的名字前加"!",即Profile不激活才注册相应的Bean。
@Bean
@Profile("!test") // test不激活才创建该Bean
public MyBean myBean() {
return new MyBean();
}
3.激活环境
- 配置计算机的环境变量:
export SPRING_PROFILES_ACTIVE=prod
-
设置启动参数 -Dspring.profiles.active=test,
允许同时激活多个环境,如:-Dspring.profiles.active=test,master
-
在application.yml中配置
-
使用**@ActiveProfiles**注解,一般仅用于测试,放在测试类上面
prod, dev, test等都是常用的缩写, 属于自己定义的内容而非Spring提供的定义
10. @ConfigurationProperties
1.简介
Springboot的配置文件,指的是在resources文件夹的application.properties文件,或application.yml文件。
而在类上使用 @ConfigurationProperties 注解,可以获取配置文件中的数据,将其注入类,为类内的成员属性赋值。成员属性的名字需要和配置文件对应。
2.使用
向注解中传入配置文件中的前缀名,如果配置文件如下:
myConfigs:
config1:
field1: f1
field2: f2
field3: f3
那么代码中的配置类应该这样写:
@Component
@ConfigurationProperties("myConfigs.config1")
public class MyConfig1 {
String field1;
String field2;
String field3;
}
如上所示,field1, field2, field3三个属性就被绑定到了对象上。
注意到我们使用了@Component,实际上我们使用配置类都是将其注入到其他类中,所以我们往往将其注册为Bean。
ignoreInvalidFields默认为false,不合法的属性的属性会默认抛出异常;
ignoreUnknownFields默认为true, 未能识别的属性会被忽略(所以打错了名字就会被忽略了)
@ConfigurationProperties(prefix="config.prefix", ignoreInvalidFields=true, ignoreUnknownFields=false)
public class MyConfig {
// fields
}
Spring Boot的绑定规则相当宽松,myField, my-field, my_field等都能识别绑定到myField上。
可以给字段设定默认值,这样配置中没有传入时会使用默认值。
@ConfigurationProperties("your.prefix")
public class YourConfig {
private String field = "Default"
// setter
}
类的字段必须要有public访问权限的setter方法。
在很多情况下public的setter方法时必须的,使用IDEA的话,这里推荐Alt+Insert(Windows, Mac使用Alt+n)生成;当然,想使用Lombok也可以
11. @Value 赋值
1.前言
使用**@ConfigurationProperties能够将配置注入到整个类中,而@Value**注解放在成员变量上,能够将配置注入到具体的成员变量中,进行更为细粒度的控制。
注意:注解修饰的字段不能为static或者final。
2.使用
使用上有两种形式:
- @Value("\${}"),用来加载外部文件中的值;
- @Value("#{}"),用于执行SpEl表达式,并将内容赋值给属性。
我们可以获取Spring Boot配置文件(.yml或.properties)中的属性值并将其赋值给指定变量。
@Value("${my.config.field}")
private String value;
SpEL是一种表达式语言,能够动态的运行语句。
@Value("#{1 + 1}")
private Integer value; // 2
拓展内容
1. @Configuration 和 @Component 区别
1.1. 前置:@Bean注解
在解释这两个注解之前,还是要先了解一下@Bean
注解,而关于这个注解,我们需要知道以下几点:
- @Bean需要放在方法上,并且@Bean主要用在被@Configuration注解的类里。也可以用在@Component注解的类里。
- Spring的@Bean注解用于告诉方法,通过这个方法来产生一个Bean对象,然后
把这个Bean对象交给Spring管理
。因为在Spring中,bean默认是单例的,所以在单例的情况下,这个bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中; - 当我们需要使用@Bean生成的对象的时候,只需要使用类似@Autowired这样的注释取出即可。
- 添加的bean的时候,默认的id为这个方法的方法名。可以通过设置@Bean的name属性来手动设置bean的名字;因为spring中bean默认是单例的,如果重名会因为无法识别具体调用哪个bean而报错。
- 特殊的,@Bean注解的方法的返回值也可以是void,这样可以在spring容器初始化进行扫描的时候,执行这个方法内的逻辑。可以通过这种方式,对当前类的成员变量进行初始化。
简单来讲,一般情况下,被@Bean注释的方法的用处就是生成一个bean对象,再把这个对象交给Spring容器管理,以便在需要的时候取出使用。
1.2. @Configuration 和 @Component
在spring中,诸如@Component
、@Configuration
、@Bean
、@Import
等注解 ,虽然负责的功能不同,但是都被作为配置注解
进行处理
而spring的配置注解又可以分为两类,一类称为 LITE模式, 一类称为 FULL模式。
@Component是LITE模式,而@Configuration 默认是FULL模式。
LITE模式的配置注解,被其注解的类不会被Spring所代理
,就是一个标准类。如果在这个类中有@Bean标注的方法,而在其他地方,某个方法调用了这个bean方法,那就只是普通Java类的方法的调用。我们知道bean方法都是用来创建对象的,那普通的方法调用,最后就会正常的执行这个bean方法,然后返回一个新的对象。
FULL模式的配置注解,被其注解的类会被Spring所代理
。如果在这个类中有@Bean标注的方法,而在其他地方,某个方法试图调用这个bean方法,则会被动态代理所拦截,并返回spring容器中的单例对象。
下面的例子能更生动的描述上述情况:
我们创建一个AppConfig类,并在其中创建两个方法,foo和eoo,其中,让eoo调用foo。
@Component
public class AppConfig {
@Bean
public Foo foo() {
System.out.println("foo() invoked...");
Foo foo = new Foo();
System.out.println("foo() 方法的 foo hashcode: " + foo.hashCode());
return foo;
}
@Bean
public Eoo eoo() {
System.out.println("eoo() invoked...");
Foo foo = foo();
System.out.println("eoo() 方法的 foo hashcode: "+ foo.hashCode());
return new Eoo();
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
}
}
执行结果如下
foo() invoked...
foo() 方法的 foo hashcode: 815992954
eoo() invoked...
foo() invoked...
foo() 方法的 foo hashcode: 868737467
eoo() 方法的 foo hashcode: 868737467
而如果我们将上述例子中,类上的 @Component 换成 @Configuration ,就会有下面不同的结果
foo() invoked...
foo() 方法的 foo hashcode: 849373393
eoo() invoked...
eoo() 方法的 foo hashcode: 849373393
可以看到,在第二种方式中,虽然eoo方法试图直接调用foo方法,但是实际上并没有直接执行foo方法,而是被代理类拦截后,直接返回了容器中早就生成好的单例Foo对象。
而在第一种方式中,eoo调用foo就像是调用普通方法,正常的执行,然后返回了一个新的Foo对象。
- 需要再次强调的是,如果在其他类中,通过@Autowired的方式获取Foo对象或Eoo对象,那不管获取几次,得到的都是同一个Foo对象或Eoo对象。因为使用@Autowired方式是直接从spring容器中获取对象,而@Bean默认是单例的。在上面的例子中不是通过@Autowired方式,而是通过直接执行方法的方式获取,所以才会出现不同。
Comments NOTHING