本文是自己学习的一个总结
目录
1、基于XML装配
在Spring官网中搜索bean,找到相应的xml基本架构,复制到我们xml中后,开始装配bean。
Spring官网:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#spring-core。
1.1、构造器装配Bean和setter装配Bean
依赖注入的方式主要有三种,构造器注入,setter注入,接口注入。XML装配方式也有与这三者相对应的装配方式。不过因为接口注入很少见,所以这里就只记录前两种装配方式。
1.1.1 构造器装配Bean
先建立一个带有构造方法的类。
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
}
之后在XML文件中这样定义。我们在bean标签下使用<constructor-arg>标签,其中有两个属性
- index:构造器中的第几个参数,从0开始。
- value:该参数的值。
装配的例子如下
<!--该xml名为application-context.xml-->
<bean id="user1" class="org.company.think.in.spring.ioc.overview.domain.User">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="user"/>
</bean>
那么关于该bean的描述就存储在application-context.xml文件中,之后根据这个XML文件生成对应的容器,通过容器就可以取到我们描述的Bean实例。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-context.xml");
User user = (User) applicationContext.getBean("user1");
将这个user打印出来,确实与xml中定义的一致。
1.1.1.1、构造器参数和类型自动匹配
Java中构造器是可以有多个的。XML注入方式中,系统会根据参数的类型和顺序与类中的构造器进行匹配。尽管XML中参数的定义都是以字符串的形式(比如上述例子中的id是Long类型,但XML中是写成index=“0” value=“1”),但系统会为我们自动进行类型转换。
现在我们给User新增一个构造器,新的构造器和旧的一样,只是参数的位置调换了,代码如下。
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
//旧的构造器
public User(Long id, String name) {
this.id = id;
this.name = name;
}
//新的构造器
public User(String name, Long id) {
this.id = id;
this.name = name;
}
}
然后XML文件中也新增一个bean,对应着新的构造器。
<bean id="user1" class="org.company.think.in.spring.ioc.overview.domain.User">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="user1"/>
</bean>
<bean id="user2" class="org.company.think.in.spring.ioc.overview.domain.User">
<constructor-arg index="0" value="user2"/>
<constructor-arg index="1" value="2"/>
</bean>
然后在取出这两个bean并打印,我们能发现Java自动帮我们根据参数的类型和顺序找到了对应的构造器。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-context.xml");
User user1 = (User) applicationContext.getBean("user1");
User user2 = (User) applicationContext.getBean("user2");
System.out.println(user1);
System.out.println(user2);
输出结果如下
1.1.2、通过setter注入装配
将上面例子的User类写成下面这样,建立属性的getter和setter方法。
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
//省略getter和setter
}
对于setter注入,我们使用<property>标签,对需要赋值的属性赋值即可。
<bean id="role" class="com.ssm.chapter9.pojo.Role">
<property name="id" value="1"/>
<property name="name" value="用户1"/>
</bean>
1.2、引用型赋值和集合型赋值
上述的例子中我们赋值的属性都是基本类型,比如Long,Integer,String。但是如果属性的类型是引用型或者集合,那就要使用其他方式了。
1.2.1、引用型赋值
如果User的定义如下
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
private Address address;
//省略getter和setter
}
其中的Address是一个类。那这个属性的赋值就不能像之前一样"value = ···",我们要先保证容器中已有一个类型是Address的Bean,然后使用"ref = ···",其中ref是指类型是Address的Bean在容器中的id。
假设容器中已有一个类型是Address的Bean,id是address。引用型赋值的示例如下。
<bean id="role" class="com.ssm.chapter9.pojo.Role">
<property name="id" value="1"/>
<property name="name" value="总经理"/>
<property name="Address" ref="address"/>
</bean>
1.2.2、集合类赋值
如果属性的类型是集合,那要分好几种情况,比如Map,List。类型不同赋值方式也不同。
1.2.2.1、List类型
List类型的话,我们使用<list>标签代替value或者ref。在<list>下则正常使用value或ref对元素赋值。
<bean id="id" class="全限定性类名">
<property name="list">
<list>
<value>"1"</value>
<ref bean="myDataSource"/>
</list>
</property>
</bean>
1.2.2.2、Set类型
Set类型的话,我们使用<set>标签代替value或者ref。在<set>下则正常使用value或ref对元素赋值。
<bean id="id" class="全限定性类名">
<property name="list">
<set>
<value>"1"</value>
<ref bean="myDataSource"/>
</set>
</property>
</bean>
1.2.2.3、Map类型
Map类型我们使用<map>标签代替value或者ref。在<map>标签下使用<entry>子标签代表元素。<entry>下的key属性和value属性则是元素的key和value。如果key和value的类型是引用型,则使用key-ref和value-ref。
<bean id="id" class="全限定性类名">
<property name="map">
<map>
<!--key和value是基本类型-->
<entry key="0" value="value1"/>
<!--key和value是引用类型-->
<entry key-ref="1" value-ref="class1"/>
</map>
</property>
</bean>
假设User的定义如下
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
private Map<Integer, String> map;
//省略getter和setter
}
开始装配User。
<bean id="user1" class="org.company.think.in.spring.ioc.overview.domain.User">
<property name="map">
<map>
<entry key="0" value="map1"/>
<entry key="1" value="map2"/>
</map>
</property>
</bean>
然后初始化容器并输出User的map。
BeanFactory beanFactory = new ClassPathXmlApplicationContext("META-INF/test.xml");
User user1 = (User) beanFactory.getBean("user1");
System.out.println(user1.getMap());
输出结果如下
1.3、命名空间赋值
所谓的命名空间其实就是上面提到的构造器注入、setter注入,只是语法上更为简单简洁。
1.3.1、c命名空间
c命名空间就是构造器注入,我个人是将c看成contructor的简称。
要使用c命名空间,要先引入一些东西。我们在spring官方文档中可以直接搜索『 c: 』,直接复制过来。
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#spring-core
c命名空间是<bean>的一个属性,不是子标签。c命名空间有两种用法,一种是指明构造器参数的名称进行复制,另一种是指明构造器参数的位置顺序赋值。
1.3.1.1、指定参数名称(c:name)
假设User类的定义如下
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
}
使用c命名空间指定参数名称的方法如下
<!-- 如果name的类型是引用,则要写成c:name-ref="引用名称"-->
<bean id="user2" class="org.company.think.in.spring.ioc.overview.domain.User"
c:name="user1" c:id="1"/>
这样写就简介得多了。我们初始化容器后输出user2看看结果。
BeanFactory beanFactory = new ClassPathXmlApplicationContext("META-INF/test.xml");
User user2 = (User) beanFactory.getBean("user2");
System.out.println(user2);
输出如下
1.3.1.2、指定参数位置顺序
我们也可以指定参数顺序,还是上面的例子。
<!-- 同样的,如果参数的类型是引用,则要写成c:_0-ref="引用名称"-->
<bean id="user2" class="org.company.think.in.spring.ioc.overview.domain.User"
c:_0="1" c:_1="user"/>
1.3.2、p命名空间
p命名空间就是setter注入。基本的setter注入的写法中使用<property>来为成员属性赋值,我认为p应该就是property的缩写。
setter注入没有所谓的顺序,所以p命名空间只能指定成员属性名,用法与c命名空间相似。
当然,使用之前也要先引入p命名空间,在官方文档中搜索搜索『 p: 』,然后复制相关引用到自己的XML中。
使用方式如下。
<bean id="user2" class="org.company.think.in.spring.ioc.overview.domain.User"
p:name="user2" p:id="1"/>
1.3.3、util命名空间
上面的c命名空间和p命名空间都是只能对基本类型和引用进行赋值。集合类型就得使用util命名空间。
同样的,util命名空间使用之前要先引入,我们在官方文档中搜索『 util: 』,复制相关引用。
其实util命名空间和原来的写法相比并没有简介到哪里去,util命名空间还有其他用途,不过这里不过多介绍了。
下面看看使用方法。假设User的定义如下。
package org.company.think.in.spring.ioc.overview.domain;
import java.util.Map;
public class User {
private Long id;
private String name;
private Map<User, String> map;
//省略setter和getter
}
我们使用util命名空间对其中的map赋值。
<bean id="user1" class="org.company.think.in.spring.ioc.overview.domain.User">
<property name="map">
<util:map id="map">
<entry key="0" value="map1"/>
<entry key="1" value="map2"/>
<entry key="2" value="map3"/>
</util:map>
</property>
</bean>
对于list和set,就替换成util:list和util:set即可。
util还有一些子属性可以了解
1.3.3.1、指定成员类型
在util:map中,我们可以使用map-class属性指定map的具体类型。比如下面的例子,我们指定map的类型是Hashmap。
<bean id="user1" class="org.company.think.in.spring.ioc.overview.domain.User">
<property name="map" map-class="java.util.HashMap">
<util:map id="map">
<entry key="0" value="map1"/>
<entry key="1" value="map2"/>
<entry key="2" value="map3"/>
</util:map>
</property>
</bean>
同时也可以使用key-type和value-type指定key和value的类型。List和Set也有类似的属性。
1.4、通过静态方法
我们可以在类中定义一个静态方法,将该方法的返回对象作为Bean。
假设User的定义如下
package org.company.think.in.spring.ioc.overview.domain;
public class User {
private Long id;
private String name;
public static User createUser() {
User user = new User();
user.setId(1L);
user.setName("user1");
return user;
}
//省略setter和getter
}
类中定义了一个静态方法,返回一个User对象。在XML中我们可以获取这个静态方法的返回对象作为Bean。
XML中可以使用<beam>的bean-factory属性使用到这个静态方法,例子如下
<bean id="user" class="org.company.think.in.spring.ioc.overview.domain.User"
factory-method="createUser"/>
1.5、通过实例方法
我们可以指定一个类,然后调用该类的一个实例方法,并将该方法的返回值作为Bean。
User类定义如以往一样,成员变量是id和name。我们定义一个新的类,其中有一个实例方法返回值为User。
public class DefaultUserFactory implements UserFactory{
public User createUser() {
User user = new User();
user.setId(1L);
user.setName("user");
return user;
}
}
之后我们在XML声明DefaultUserFactory的一个Bean,然后利用这个Bean的实例方法实例化User类型的Bean。
<!-- 先要有工厂类的Bean-->
<bean id="userFactory" class="org.company.think.in.spring.bean.factory.DefaultUserFactory"/>
<!-- factory-bean指定工厂类,factory-method指定工厂的生产方法-->
<bean id="user-by-instance-method" factory-bean="userFactory" factory-method="createUser"/>
之后生成容器输出user-by-instance-method的bean信息。
BeanFactory beanFactory = new ClassPathXmlApplicationContext("META-INF/bean-creation-context.xml");
User user = beanFactory.getBean("user-by-instance-method", User.class);
System.out.println(user);
输出结果如下
1.6、通过FactoryBean
我们可以借助FactoryBean完成Bean的实例化。
定义一个类,使其实现FactoryBean接口,复写其中的getObject()和getObjectType()两个方法(其实还有一个isSingleton(),但是这个方法已经默认实现了,默认是单例,一般不用我们操心)。
其中,getObject()定义的是Bean的具体信息,返回值就是Bean。getObjectType()的返回值则是Bean的类型。
之后在XML中定义这个实现FactoryBean的类即可
例子如下
package org.company.think.in.spring.bean.factory;
import org.company.think.in.spring.ioc.overview.domain.User;
import org.springframework.beans.factory.FactoryBean;
public class UserFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
User user = new User();
user.setId(1L);
user.setName("user");
return user;
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
XML中的定义如下
<bean id="user-by-factory-bean" class="org.company.think.in.spring.bean.factory.UserFactoryBean"/>
我们就可以通过getBean(“user-by-factory-bean”)得到User类的bean。
1.7、通过接口回调
bean的初始化过程中有一个接口回调的过程。只要bean实现了一系列以Aware结尾的接口,那就可以将一些信息注入到这个bean之中。比如BeanFactoryAware和ApplicationContextAware,bean实现这两个接口以后,就可以获取到当前的BeanFactory和ApplicationContext信息(一般情况下,一个普通的bean只包含自身的信息,比如成员变量,是不会包含容器相关信息的。但只要实现这两个接口,bean中就包含容器相关信息)。
我们看看示例。
public class Test implements BeanFactoryAware, ApplicationContextAware {
public BeanFactory beanFactory;
public ApplicationContext applicationContext;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(Test.class);
applicationContext.refresh();
Test test = applicationContext.getBean(Test.class);
System.out.println(applicationContext == test.applicationContext);
System.out.println(applicationContext.getBeanFactory() == test.beanFactory);
applicationContext.close();
}
}
上述例子的Test实现了BeanFactoryAware, ApplicationContextAware两个接口,并且覆写的方法是将当前容器赋值给Test的成员变量beanFactory和applicationContext。那在main函数中启动容器后,判断容器applicationContext,beanFactory是否分别与test.applicationContext和test.beanFactory是同一对象时,输出的结果就应该为true。下面是输出结果。
2、基于注解装配Bean
使用注解是一个更为简洁的方式,它是直接在类加注解,比起xml方式更为直观。
编写注解装配一般要经历两个过程,一是在目标类上加入相关注解;二是指定扫描类,扫描类中会指定范围,该范围内被标注的类才会被装配成Bean。
2.1、使用@Component装配Bean
我们可以使用@Component标注类,表明这个类需要被装配成一个Bean。同时可以直接使用@Value对属性进行赋值。
示例如下
package org.company.think.in.spring.ioc.overview.domain;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class User {
@Value("1")
private Long id;
@Value("user1")
private String name;
}
这样一个Bean就标注好了,之后是需要定义扫描类,不过在这里要多讲一些关于@Component的事。
2.1.1、@Component的属性value
Component有一个属性value可以设置,它是表示该类被扫描成Bean以后,在容器中的名字,相当于是XML中的id。如果value没有设置,系统会默认该类在容器中的id为首字母小写后的类名。
2.1.2、与@Component相关的注解
为了更贴合业务,Sping还提供了@Controller,@Service和@Repository来标注控制层,服务层和持久层三层架构的Bean。似乎在目前的Spring框架中,这三个注解与@Component没有太多区别,但是文档还是建议不同层的的Bean使用对应的注解。
一方面在语义上能让人们更好区分被标注的Bean属于哪一层;另一方面在以后的版本(5.2.7以后)中,很可能会对这些不同层的注解专门实现一些定制化的需求。所以强烈建议不同层的Bean使用对应层的注解标注。
2.2、使用@ComponentScan标注扫描Bean
注解标注类以后,还需要定义扫描类。我们使用@ComponnetScan进行扫描。
首先我们先定义一个类,这个类可以是一个空类。然后我们在这个类上添加@ComponentScan注解
package org.company.think.in.spring.ioc.overview.domain;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class PojoConfig {
}
这个类很简单,但是有一点是要注意的。
注意第一行代码,这个类的包名必须和需要装配的类的包名是一样的。因为@ComponentScan扫描默认是扫描当前包的路径,所以包名必须和需要装配的类相同。
从这里就可以理解为什么需要装配的类分散在不同的包时,使用注解不容易管理,需要使用XML的原因。
2.2.1、@ComponentScan的属性
@ComponentScan的属性有两个,这两个都是用来指定扫描范围的。
2.2.1.1、basePackages
这个属性是用来设置扫描的包名。扫描范围是改包以及改包的子包下的所有类。要注意到basePackages是复数,所以我们可以指定多个包进行扫描。
package org.company.think.in.spring.ioc.overview.domain;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = {"org.company.think.in.spring.ioc.overview.domain", "org.company.think.in.spring.ioc.overview.dto")}
public class PojoConfig{
}
这段代码代表着Spring IoC会扫描包org.company.think.in.spring.ioc.overview.domain和org.company.think.in.spring.ioc.overview.dto下所有的类(包括它们子包的所有类)。
2.2.2.2、basePackageClasses
该属性指定的是,容器只对该属性指定的类,以及该类的子类进行进行扫描,如果这个类被注解标注要成为Bean,那该类就会被变成Bean放到容器中维护。
package org.company.think.in.spring.ioc.overview.domain;
import org.springframework.context.annotation.ComponentScan;
//Role和RoleImpl是定义好的类或接口
@ComponentScan(basePackageClasses = {User.class, Address.class})
public class PojoConfig{
}
2.3、@Autowired
使用@Componnet注解时,对于成员变量的赋值我们是使用@Value注解。但是@Value只能完成一般类型的赋值,对于引用类型的赋值,就要使用到@Autowired。
@Component
public class User {
@Value("1")
private Long id;
@Value("user1")
private String name;
@Autowired
private Address address;
}
我们使用@Autowired后,系统会在容器中已注册的Bean中去寻找与@Autowired标注类型一致的Bean,然后将这个Bean赋值给@Autowired标注的成员变量。
2.3.1、@Autowired的属性
@Autowired只有一个属性,required。该属性只能赋值true或false。true表示如果容器中没有找到想对应类型的Bean,那就报错,@Autowired标注的该成员变量必须指向一个实例;false表示即使没有找到相对应类型的Bean也不会报错。
2.3.2、@Autowired注解方法
@Autowired注解方法多半用于setter方法上,当然其他方法也可以,只要@Autowired标注方法的参数类型是引用即可。对于@Autowired标注的方法,这个方法的类在实例化成Bean时,会自动执行方法(即使方法不是构造方法,也不是静态方法),并且系统会在容器中寻找和方法参数类型相同的Bean传值到方法中执行。
下面是使用的例子。
@Component
public class User {
@Value("1")
private Long id;
@Value("user1")
private String name;
private Address address;
@Autowired
public void setter(Address address) {
this.address = address;
}
}
2.3.3、@Autowired的歧义性
@Autowired是根据类型赋值,但如果容器中符合条件的Bean不止一个的话,计算机会糊涂,会不知道应该使用哪个Bean进行赋值。
在这种情况下,我们可以使用@Primary和@Qualifier两个注解来解决这个问题
2.3.3.1、@Primary
被@Primary标注的类在@Autowired自动装配中具有优先权。比如有两个类继承同一个接口,这时候容器类就有两个RoleService类
@Component
@Primary
public class RoleService1 implements RoleService{
@Override
public void RoleService(Role role) {
System.out.println(role.sword + "1");
}
}
@Component
public class RoleService2 implements RoleService{
@Override
public void RoleService(Role role) {
System.out.println(role.sword + "2");
}
}
当需要自动装配RoleService时,因为RoleService1标注了@Primary,所以系统会有限选择RoleService1进行装配。
要注意,@Primary可以同时标注多个Bean的。上述例子中RoleService1标注@Primary,那RoleService2也可以同时标注@Primary。这时候RoleService1和RoleService2就具有同样的优先权,歧义性依然存在,系统依然会报错。
2.3.3.2、@Qualifier
@Primary只是解决了首要性的问题,容器依然是根据类型来自动装配。而@Qualifier是根据名称查找来进行装配,完美地消除了歧义性。
@Qualifier紧跟着@Autowired标注,并且指定名称,指定Bean在容器中的名称,指定要这个Bean,这样就不会再有歧义性的问题。使用方式如下
public class User {
@Value("1")
private Long id;
@Value("user1")
private String name;
@Autowired
@Qualifier("address")
private Address address;
}
2.4、 使用@Bean装配
对于Java而言,很多时候都需要引入第三方的包(jar文件),并且这些包都没有源码,或者源码是只读状态无法修改。这时候就无法使用注解将第三方的类变为开发环境的Bean。
这时候可以使用@Bean注解来解决这个问题。@Bean可以注解到方法上,并且将标注方法的返回值作为Bean放到容器中。比如我们要使用DBCP数据源,这时候就可以使用@Bean标注来装配数据源的Bean
@Bean
public DataSource getDataSource() {
Properites props = new Properties();
props.setProperty("drivers", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/mysql");
props.setProperty("username", "root");
porps.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDateSource(props);
} catch(Exception e) {
e.printStackTrace();
}
return dataSource;
}
2.4.1、注意事项
在使用@Bean注解方法的时候,要保证被注解方法的类被相关注解标注了。当扫描类扫描到这个类时,要让系统知道这个类需要被扫描。如果这个类没有被任何注解标注,只是一个普通的类,那系统扫描到这个类时,会认为这个类不需要被装配,那么其中被@Bean标注的方法也会被略过。@Bean就没有起到任何作用。
比如下面这个例子
public class Data {
@Bean(name = "sword1")
public Sword getSword(){
Sword sword = new Sword();
sword.setDamage(10);
sword.setLength(10);
return sword;
}
}
容器扫描到Data类时会直接略过,最终容器中不会有一个name为sword1的Bean。
要想要@Bean起作用,就可以用下面两种方式。
- 对Data加上@Configuration注解。
- 通过AnnotationConfigApplicationContext生成容器是,将Data类也放到方法参数中。
//PojoConfig.class是扫描类
ApplicationContext ac = new AnnotationConfigApplicationContext(PojoConfig.class, Data.class);
2.5、引入其他容器资源,@Import和@ImportResource
有时候我们已经有了一些容器资源来源,比如已经写好的XML文件,或者被@ComponentScan标注的定义好的扫描类,我们想在这些资源上再加上一些Bean的定义组成新的容器,同时又不想再重复定义这些容器,那我们就可以通过引入的方式。
对于XML而言,我们就只能引入XML文件资源,在beans标签下使用import标签,例子如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<import resource="dependency-lookup-context.xml"/>
<bean id="userRepository" class="org.company.think.in.spring.ioc.overview.repository.UserRepository">
<property name="users">
<util:list>
<ref bean="superUser"/>
<ref bean="user"/>
</util:list>
</property>
</bean>
</beans>
对于通过注解生成的容器,我们可以引入其他注解的资源,也可以引入XML定义的资源。
2.5.1、引入XML资源
使用@ImportResource即可。在扫描类中加入@ImportResource,该注解的属性就是XML的项目路径。加上这个注解之后,该注解中指定的XML文件中定义的Bean就会被引入到由该扫描类生成的容器中。
使用例子如下
@ComponentScan
@ImportResource({"bean1.xml", "bean2.xml"})
public class PojoConfig {
}
通过这样通过该扫描类生成的容器中,也会存在这bean1.xml和bean2.xml中定义的Bean。
2.5.2、引入注解资源
和@ImportResource一样,在相同的位置使用@Import即可。@Import中的属性是其他的扫描类
@ComponentScan
@Import({ApplicationConfig2.class, ApplicationConfig3.class})
public class PojoConfig {
}
@Import和@ImportResource是可以同时使用的
2.5.3、Bean重复定义的问题
引入资源时,很有可能会对同一个类对此扫描 ,或者XML中多次定义,这时就涉及到一个类被多次定义或者多次扫描时,容器中存在关于这个类的多个Bean还是一个Bean。
对于XML而言,无论重复与否,定义了多少次就产生多少个Bean,这点比较简单。
对于注解而言,同一个扫描类多次扫描了一个类,那关于这个类只会生成一个Bean;如果是多个扫描类扫描到了同一个类,那每个扫描类会分别产生一个关于这个类的Bean。
2.6、分组注入
2.6.1、基于@Qualifier分组注入
@Autowired可以标注在集合上,比如
@Autowired
private Collection<User> users;
容器会将所有User类的bean注入到集合users中。
在对集合进行@Autowired注入时,我们还只选择一部分User类注入到集合中,而不是将所有User类都注入到集合。只需要再加一个注解@Qualifier
@Autowired
@Qualifier
private Collection<User> users;
我们可以用下面代码验证一下,Test类中@Autowired四个User类,其中有两个额外加上了@Qualifier注解。
public class Test {
@Autowired
private Collection<User> users;
@Autowired
@Qualifier
private Collection<User> qualifierUsers;
@Bean
@Qualifier
public User user1() {
User user = new User();
user.setId(1L);
return user;
}
@Bean
@Qualifier
public User user2() {
User user = new User();
user.setId(2L);
return user;
}
@Bean
public User user3() {
User user = new User();
user.setId(3L);
return user;
}
@Bean
public User user4() {
User user = new User();
user.setId(4L);
return user;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(Test.class);
applicationContext.refresh();
Test test = applicationContext.getBean(Test.class);
System.out.println(test.users);
System.out.println(test.qualifierUsers);
applicationContext.close();
}
程序的执行结果如下
可以看出,只有@Autowired标注的users中包含了所有User类的bean。而加上了@Qualifier的qualifierUsers中则只有@Qualifier标注的bean。
2.6.2、继承@Qualifier实现分组注入
我们可以基于@Qualifier对集合进行更精细地注入。创建一个新的注解继承@Qualifier
@Target({ElementType.FIELD, ElementType.METHOD})
@Inherited
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface UserGroup {
}
与使用@Qualifier进行分组注入的方式一样,将@UserGroup标注在集合和bean上。
public class Test {
@Autowired
private Collection<User> users;
@Autowired
@Qualifier
private Collection<User> qualifierUsers;
@Autowired
@UserGroup
private Collection<User> userGroup;
@Bean
public User user1() {
User user = new User();
user.setId(1L);
return user;
}
@Bean
public User user2() {
User user = new User();
user.setId(2L);
return user;
}
@Bean
@Qualifier
public User user3() {
User user = new User();
user.setId(3L);
return user;
}
@Bean
@Qualifier
public User user4() {
User user = new User();
user.setId(4L);
return user;
}
@Bean
@UserGroup
public User user5() {
User user = new User();
user.setId(5L);
return user;
}
@Bean
@UserGroup
public User user6() {
User user = new User();
user.setId(6L);
return user;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(Test.class);
applicationContext.refresh();
Test test = applicationContext.getBean(Test.class);
System.out.println(test.users);
System.out.println(test.qualifierUsers);
System.out.println(test.userGroup);
applicationContext.close();
}
}
Test类中有6个类型为User的bean。user1和user2没有额外注解,user3和user4有@Qualifier注解,user5和user6有@UserGroup注解。
当我们输出user,qualifierUsers和userGroup时,可以预测user中包含所有的bean。qualifierUsers中包含被@Qualifier标注的bean,因为@UserGroup继承@Qualifier,所以qualifierUsers也包含@UserGroup标注的bean。而userGroup只包含@UserGroup标注的bean。
3、基于properties文件装配
基于properties文件装配bean只能在BeanDefinitionRegistry的实现类中注册,比较常见的实现类有AnnotationConfigApplicationContext和DefaultListableBeanFactory。主要是因为properties文件装配要使用到PropertiesBeanDefinitionReader,而这个类唯一构造器的参数就是BeanDefinitionRegistry
3.1、 properties文件装配规则
首先,我们先在资源目录下定义properties文件,定义的规则在PropertiesBeanDefinitionReader的官方文档中有写。
定义很简单,例子也浅显易懂。这里就不再去解释了。
3.2、封装properties文件成Java类并装入beanFactory
步骤可简单分为四步
- 使用Resource获取properties文件信息。
- 使用EncodedResource加工Resource,使系统知道以正确的编码方式解读properties文件信息(中文环境通常使用UTF-8,默认是ASCII)。
- 新建PropertiesBeanDefinitionReader对象,并在构建方法中和指定容器关联(容器类型必须是DefaultListableBeanFactory)。
- 使用刚刚新建的PropertiesBeanDefinitionReader对象通过beanDefinitionReader.loadBeanDefinitions方法加载存储着bean信息的EncodedResource。
做完以上四步以后,PropertiesBeanDefinitionReader关联的beanFactory中就有properties文件中定义的bean了。
这是示例。我们先定义properties文件
执行以下代码
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 实例化基于 Properties 资源 BeanDefinitionReader
String location = "META-INF/user.properties";
// 第一步:使用Resource基于 ClassPath 加载 properties 资源
Resource resource = new ClassPathResource(location);
// 第二步:指定字符编码 UTF-8
EncodedResource encodedResource = new EncodedResource(resource, "UTF-8");
// 第三步:实例化PropertiesBeanDefinitionReader并关联beanFactory
PropertiesBeanDefinitionReader beanDefinitionReader = new PropertiesBeanDefinitionReader(beanFactory);
// 第四步:将encodedResource中的信息放入beanFactory中。
int beanNumbers = beanDefinitionReader.loadBeanDefinitions(encodedResource);
System.out.println("已加载 BeanDefinition 数量:" + beanNumbers);
// 通过 Bean Id 和类型进行依赖查找
User user = beanFactory.getBean("user", User.class);
System.out.println(user);
下面是运行结果
4、基于API装配
基于API装配在实际开发中不常用,太过繁琐。不过Spring框架内注入内建bean的时候基本都是使用API的方式,所以想要阅读Spring源码的话的,这些API也有必要了解。
4.1、构建BeanDefinition然后手动注入
这方面的细节可以看这篇文章,https://blog.csdn.net/sinat_38393872/article/details/106820610。
4.2、使用BeanDefinitionReader系列
BeanDefinitionReader系列有三个,PropertiesBeanDefinitionReader、AnnotatedBeanDefinitionReader和XmlBeanDefinitionReader。这三个的用法都是类似的,PropertiesBeanDefinitionReader在基于properties文件装配中介绍过了,下面就只介绍AnnotatedBeanDefinitionReader。
AnnotatedBeanDefinitionReader可以直接将一个类直接注册到一个容器中。使用方式和基于properties装配的PropertiesBeanDefinitionReader类似,主要是以下两个步骤
- 新建AnnotatedBeanDefinitionReader对象,并在构建方法中和指定容器关联(容器类型必须是DefaultListableBeanFactory)。
- 使用刚刚新建的PropertiesBeanDefinitionReader对象使用register方法直接加载某个类到容器中。
示例如下。
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 基于 Java 注解的 AnnotatedBeanDefinitionReader 的实现
AnnotatedBeanDefinitionReader beanDefinitionReader = new AnnotatedBeanDefinitionReader(beanFactory);
int beanDefinitionCountBefore = beanFactory.getBeanDefinitionCount();
// 注册当前类(非 @Component class)
beanDefinitionReader.register(AnnotatedBeanDefinitionParsingDemo.class);
int beanDefinitionCountAfter = beanFactory.getBeanDefinitionCount();
int beanDefinitionCount = beanDefinitionCountAfter - beanDefinitionCountBefore;
System.out.println("已加载 BeanDefinition 数量:" + beanDefinitionCount);
// 普通的 Class 作为 Component 注册到 Spring IoC 容器后,通常 Bean 名称为 annotatedBeanDefinitionParsingDemo
// Bean 名称生成来自于 BeanNameGenerator,注解实现 AnnotationBeanNameGenerator
AnnotatedBeanDefinitionParsingDemo demo = beanFactory.getBean("annotatedBeanDefinitionParsingDemo",
AnnotatedBeanDefinitionParsingDemo.class);
System.out.println(demo);