Bean配置的三种方式
- 基于XML的配置方式
- 基于注解的配置方式
- 基于Java类的配置方式
一、基于XML的配置
配置多个资源文件
- 在配置文件中使用import来导入所需的配置文件。
- 将多个配置文件构造为一个数组,然后传递给ApplicationContext实现加载多个配置文件。
这两种方式都是通过调用BeanDefinitionReader来读取定义文件的,在内部实现上没有任何的区别。
在xml配置文件中引入:<import resource="applicationContext2.xml"/>
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]
{"applicationContext1.xml","applicationContext2.xml","applicationContext3.xml"});
bean的属性注入方式
1、setter方式(要求bean提供一个默认的构造函数,并且得为需要注入的属性提供set方法。)
setter注入是Spring中最主流的注入方式,它利用JavaBean规范所定义的setter方法来完成注入,灵活且可读性高。它消除了使用构造器注入时出现多个参数的可能性,首先可以把构造方法声明为无参数的,然后使用setter注入为其设置对应的值,其实也是通过Java反射技术得以现实的。
注入属性name
类中的定义
private String name;
public void setName(String name) {
this.name = name;
}
xml中的定义
<bean id="user" class="com.review.bean.User" >
<property name="name" value="hello" />
</bean>
2、通过构造器来注入
private String name;
public User(String name) {
this.name = name;
}
①.构造函数参数类型匹配
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
②构造函数参数索引(从0开始)
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
③构造函数参数名称
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
bean属性注入集合类型
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
p和c名称空间
p名称空间
p-namespace允许您使用bean
元素的属性(而不是嵌套 <property/>
元素)来描述属性值协作bean,或两者。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
p名称空间没有标准的XML格式定义灵活,比如说,bean的属性名是以Ref
结尾的,那么采用p名称空间定义就会导致冲突
c名称空间
Spring 3.1中引入的c-namespace允许使用内联属性来配置构造函数参数,而不是嵌套constructor-arg
元素。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
翻译:
由于XML语法,索引表示法要求存在前导_
,因为XML属性名称不能以数字开头(即使某些IDE允许)。对于<constructor-arg>
元素也可以使用相应的索引符号,但不常用,因为通常的声明顺序通常就足够了。
SpEl
Spring Expression Language(简称“SpEL”)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于Unified EL,但提供了其他功能,最值得注意的是方法调用和基本字符串模板功能。
SpEL使用#{…}作为定界符,所有在大框号中的字符都将被认为是SpEL表达式。
使用字面量
整数:<property name="count" value="#{5}"/>
小数:<property name="frequency" value="#{89.7}"/>
科学计数法:<property name="capacity" value="#{1e4}"/>
String类型的字面量可以使用单引号或者双引号作为字符串的定界符号
<property name=”name” value="#{'Chuck'}"/>
<property name='name' value='#{"Chuck"}'/>
Boolean:<property name="enabled" value="#{false}"/>
引用其他bean
<bean id="emp" class="com.review.bean.Employee">
<property name="empId" value="1003"/>
<property name="empName" value="Kate"/>
<property name="age" value="21"/>
<property name="detp" value="#{dept}"/>
</bean>
引用其他bean的属性值作为自己某个属性的值
<bean id="emp2" class="com.review.bean.Employee">
<property name="empId" value="1003"/>
<property name="empName" value="Kate"/>
<property name="age" value="21"/>
<property name="deptName" value="#{dept.deptName}"/>
</bean>
Spring Expression Language (SpEL)
小结
1.基于constructor的注入,会固定依赖注入的顺序;该方式不允许我们创建bean对象之间的循环依赖关系,这种限制其实是一种利用构造器来注入的益处 - 当你甚至没有注意到使用setter注入的时候,Spring能解决循环依赖的问题;
2.基于setter的注入,只有当对象是需要被注入的时候它才会帮助我们注入依赖,而不是在初始化的时候就注入;另一方面如果你使用基于constructor注入,CGLIB不能创建一个代理,迫使你使用基于接口的代理或虚拟的无参数构造函数。
注意点:
1.若字面量中包含特殊字符, 特殊字符注入 <![CDATA[ 带有特殊字符的值 ]]>
2.设置null值
<property name="name"><null/></property>
内部bean
当bean实例仅仅给一个特定的属性使用时,可以将其声明为内部bean。内部bean声明直接包含在<property>或<constructor-arg>元素里,不需要设置任何id或name属性 内部bean不能使用在任何其他地方
<bean id="shop2" class="com.review.bean.Shop" >
<property name= "book">
<!--内部bean-->
<bean class= "com.review.bean.Book" >
<property name= "bookId" value ="1000"/>
<property name= "bookName" value="innerBook" />
<property name= "author" value="innerAuthor" />
<property name= "price" value ="50"/>
</bean>
</property>
</bean >
Spring 级联属性
Spring 级联属性是当两个bean 关联时 从一个bean 给 另一个bean 赋值。即当一个类引用另一个类时也可以通过在Bean中分别为引用的类注入值。为级联属性赋值,属性需要先初始化才可以为级联属性赋值,否则会有异常
例如person内部引用car,然后将car的价格改变了。
注意:使用构造器注入时,除了需要重写构造方法,Person中需要有getCar方法,Car中需要有setPrice()方法
<bean id="person" class="com.review.beans.Person">
<constructor-arg type="java.lang.String" value="Tom"></constructor-arg>
<constructor-arg type="java.lang.String" value="11"></constructor-arg>
<constructor-arg ref="car"></constructor-arg>
<property name="car.price" value="1111"></property>
</bean>
二、基于注解的配置方式
1.定义bean
相对于XML方式而言,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化开发的理念十分契合,是开发中常用的使用方式。
@Component注解
@Component
public class UserDao {
...
}
等价于
<bean id="userDao " class="com.review.Dao ">
除了 @Component 注解外, Spring 还提供了 3 个功能与 @Component 等效的注解:
注解 | 说明 |
---|---|
@Repository | 标注 DAO 实现类。 |
@Service | 标注 Service 实现类。 |
@Controller | 标注 Controller 实现类。(SpringMVC) |
- @Component:一个泛化的概念,表示一个组件(Bean),可作用在任何层次
官方文档翻译:Spring提供进一步典型化注解:@Component
,@Service
,和 @Controller
。@Component
是任何Spring管理组件的通用构造型。@Repository
,@Service
和,@Controller
是@Component
更具体的用例的专业化(分别在持久性,服务和表示层)。因此,您可以来注解你的组件类有 @Component
,但是,通过与注解它们@Repository
,@Service
或者@Controller
,你的类能更好地被工具处理,或与切面进行关联。例如,这些刻板印象注释成为切入点的理想目标。@Repository
,@Service
并且@Controller
还可以在Spring Framework的未来版本中携带其他语义。因此,如果您在使用之间进行选择@Component
或者@Service
对于您的服务层,@Service
显然是更好的选择。同样,如前所述,@Repository
已经支持将其作为持久层中自动异常转换的标记。
2.扫描组件
组件被上述注解标识后还需要通过Spring进行扫描才能够侦测到
①指定被扫描的package
<context:component-scan base-package="com.review"></context:component-scan>
[1]base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其子包中的所有类。
[2]当需要扫描多个包时可以使用逗号分隔。
[3]如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类,示例:
<context:component-scan
base-package="com.review.component"
resource-pattern="autowire/*.class"/>
<context:component-scan>
提供了 include-filter 与 exclude-filter 子元素,它们可以对需要过滤的包进行更精细的控制。
●<context:include-filter>子节点表示要包含的目标类
注意:通常需要与use-default-filters属性配合使用才能够达到“仅包含某些组件”这样的效果。即:通过将use-default-filters属性设置为false,禁用默认过滤器,然后扫描的就只是include-filter中的规则指定的组件了。
●<context:exclude-filter>子节点表示要排除在外的目标类
●component-scan下可以拥有若干个include-filter和exclude-filter子节点
官网Filter Types
Filter Type | Example Expression | Description |
---|---|---|
annotation (default) |
| An annotation to be present at the type level in target components. |
assignable |
| A class (or interface) that the target components are assignable to (extend or implement). |
aspectj |
| An AspectJ type expression to be matched by the target components. |
regex |
| A regex expression to be matched by the target components class names. |
custom |
| A custom implementation of the |
说明:
类别 | 示例 | 说明 |
annotation | com.example.XxxAnnotation | 过滤所有标注了XxxAnnotation的类。这个规则根据目标组件是否标注了指定类型的注解进行过滤。 |
assignable | com.example.BaseXxx | 过滤所有BaseXxx类的子类。这个规则根据目标组件是否是指定类型的子类的方式进行过滤。 |
aspectj | com.example.*Service+ | 所有类名是以Service结束的,或这样的类的子类。这个规则根据AspectJ表达式进行过滤。 |
regex | com\.example\.anno\.* | 所有com.atguigu.anno包下的类。这个规则根据正则表达式匹配到的类名进行过滤。 |
custom | com.example.XxxTypeFilter | 使用XxxTypeFilter类通过编码的方式自定义过滤规则。该类必须实现org.springframework.core.type.filter.TypeFilter接口 |
3.自动装载 Bean
在指定要扫描的包时,<context:component-scan> 元素会自动注册一个bean的后置处理器:AutowiredAnnotationBeanPostProcessor的实例。该后置处理器可以自动装配标记了@Autowired、@Resource或@Inject注解的属性。
@Value赋值
如果bean被添加到IOC容器,对其属性不做任何初始化,那么相关属性是null或者默认值(基本类型)。
可以通过@Value赋值,赋值方式有
- 基本字符
- spring EL表达式#{}
- 加载外部属性${配置文件中参数名}
//@ComponentScan("com.review") 已经在xml文件自动扫描了。
@Configuration
@PropertySource("classpath:jdbc.properties")//配合value注解使用,加载配置文件。
public class JavaAnnoBean {
@Value("${jdbc.url}")
String url;
@Value("${jdbc.driverClassName}")
String driverClassName;
@Value("${jdbc.username}")
String username;
@Value("${jdbc.password}")
String password;
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driverClassName);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
Spring4有: @PropertySources可以配置多个属性文件
@PropertySources({
@PropertySource("classpath:config.properties"),
@PropertySource("classpath:db.properties")
})
@Autowired注解
[1]根据类型实现自动装配。
[2]构造器、普通字段(即使是非public)、一切具有参数的方法都可以应用@Autowired注解
[3]默认情况下,所有使用@Autowired注解的属性都需要被设置。当Spring找不到匹配的bean装配属性时,会抛出异常。
[4]若某一属性允许不被设置,可以设置@Autowired注解的required属性为 false
[5]默认情况下,当IOC容器里存在多个类型兼容的bean时,Spring会尝试匹配bean的id值是否与变量名相同,如果相同则进行装配。如果bean的id值不相同,通过类型的自动装配将无法工作。此时可以在@Qualifier注解里提供bean的名称。Spring甚至允许在方法的形参上标注@Qualifiter注解以指定注入bean的名称。
[6]@Autowired注解也可以应用在数组类型的属性上,此时Spring将会把所有匹配的bean进行自动装配。
[7]@Autowired注解也可以应用在集合属性上,此时Spring读取该集合的类型信息,然后自动装配所有与之兼容的bean。
[8]@Autowired注解用在java.util.Map上时,若该Map的键值为String,那么 Spring将自动装配与值类型兼容的bean作为值,并以bean的id值作为键。
三.基于Java类的配置
用@Configuration注解该类,等价 与XML中配置beans;用@Bean标注方法等价于XML中配置bean。
使用bean注解的方法不能是private、final的(static经过测试可以...)
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
上面的AppConfig
类等效于以下Spring <beans/>
XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
要启用组件扫描,您可以@Configuration
按如下方式注释您的类:
@Configuration
@ComponentScan(basePackages = "com.acme") //不加参数默认会扫描该类所在的包下所有的配置类
public class AppConfig {
...
}
可以看看这篇文章:IOC之基于Java类的配置Bean
总结:不同配置方式比较
基于XML的配置主要使用场景:
第三方类库,如DataSource、JdbcTemplate等;
命名空间,如aop、context等;
基于注解的配置主要使用场景:
Bean的实现类是当前项目开发的,可直接在Java类中使用注解配置
基于Java类的配置主要使用场景:
对于实例化Bean的逻辑比较复杂,则比较适合用基于Java类配置的方式
在日常的开发中我们主要是使用XML配置和注解配置方式向结合的开发方式,一般不推荐使用基于Java类的配置方式。
自动装配
注解 | 相同点 | 注解提供 | 是否支持required参数 | 是否支持@Primary的Bean优先注入 | 是否支持指定beanId注入 |
---|---|---|---|---|---|
@Autowire | 可实现bean的依赖注入 | Spring的专有注解 | 支持 | 支持 | 通过@Qualifier指定注入特定bean |
@Resource | 可实现bean的依赖注入 | JSR250规范 | 不支持 | 不支持 | 通过参数name指定注入bean |
@Inject | 可实现bean的依赖注 | JSR330规范 | 不支持 | 支持 | 通过@Named注解指定注入bean |
@AutoWried按by type自动注入,而@Resource默认按byName自动注入。
@Resources是javaEE提供的
加载资源
Spring 允许通过 <import> 将多个配置文件引入到一个文件中,进行配置文件的集成。这样在启动 Spring 容器时,仅需要指定这个合并好的配置文件就可以。
import 元素的 resource 属性支持 Spring 的标准的路径资源
<import resource="classpath:annotation.xml"/>
<import resource="classpath*::annotation.xml"/>
<import resource="file::annotation.xml"/>
<import resource="http::annotation.xml"/>
引用外部属性文件
当bean的配置信息逐渐增多时,查找和修改一些bean的配置信息就变得愈加困难。这时可以将一部分信息提取到bean配置文件的外部,以properties格式的属性文件保存起来,同时在bean的配置文件中引用properties属性文件中的内容,从而实现一部分属性值在发生变化时仅修改properties属性文件即可。这种技术多用于连接数据库的基本信息的配置
<!-- 直接配置 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"/>
<property name="password" value="root"/>
<property name="jdbcUrl" value="jdbc:mysql:///test"/>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
</bean>
<!-- 指定properties属性文件的位置 -->
<!-- classpath:xxx 表示属性文件位于类路径下 -->
<context:property-placeholder location="classpath*:jdbc.properties"/>
<context:property-placeholder location="file:*:jdbc.properties"/>
<context:property-placeholder location="classpath::jdbc.properties"/>
<context:property-placeholder location="http::jdbc.properties"/>
spring classpath:和classpath*:区别和实际应用
关于<context:property-placeholder>的一个有趣现象
推荐:
Bean配置的三种方式(XML、注解、Java类)介绍与对比