装配 Bean 的三种方式
一个程序中,许多功能模块都是由多个为了实现相同业务而相互协作的组件构成的。而代码之间的相互联系又势必会带来耦合。耦合是个具有两面性的概念,高度的耦合会导致代码难以复用,难以测试,难以理解;但同时耦合又是必须的,不耦合的代码什么也做不了。
在 Spring 中,容器负责了把需要相互协作的对象引用赋予各个对象,对象无需自己查找或创建与其相关联的对象。而创建应用对象之间的协作称之为装配(wiring),也就是依赖注入(DI)的本质。
在 Spring 中装配 Bean 有以下三种常见的方式:
- 在 XML 中进行显示配置;
- 在 Java 代码中显示配置;
- 隐式的 Bean 发现机制和自动装配;
自动化装配 Bean
三种装配方式中,最常用最高效的就是自动化装配了,Spring 从两个角度来实现自动化装配:
- 组件扫描( Component scanning):Spring 会自动发现应用上下文中所创建的 Bean。
- 自动装配(autowiring):Spring 自动满足 Bean 之间的依赖。
以我们平时代码中的三层架构举例,首先创建一个 Dao 层的接口类和实现类
接口类
@Repository public interface UserDao { /** * 根据 Username 和 password 查找 User 对象 * @param usernmae 账号名称 * @param password 账户密码 * @return User */ public User getUser(String Username, String password); }
实现类
public class UserDaoImpl implements UserDao { public User getUser(String email, String password) { // do something... return user; } }
然后当我们在 Service 层需要调用到上面 Dao 层的实例化对象时,我们只需要简单声明一个对应的类,并上标注解即可
public class UserServiceImpl implements UserService { @Autowired UserDao userDao; @Override public User login(String email, String password) { return userDao.getUser(email, password); } }
之后,你还需要在配置文件中启动组件扫描。在 spring-context.xml 文件中(或者这个文件在你项目中叫其他名字,无所谓)配置 <context:component-scen> 元素。
<context:component-scan base-package="yourpackage" />
当然,这里你也不一定只能通过 xml 文件形式来配置,也可以通过基于 Java 的配置。
@ Configuration
@CompomentScan(basePackages = "yourpackage")
public class BeanConfig {}
现在这段代码应该就能发挥它的实际用途了。
回顾刚刚的代码,可以发现有 @Repository 、 @Autowired 这两个注解。当你声明一个 Bean 的时候,你需要在类上使用相应的注解来让 Spring 的上下文找到这个类,类似于 Repository, Spring 中还提供了 3 个功能基本等效的注解:
- @Repository:用于对 DAO 的实现类进行注解;
- @Service:用于对 Service 的实现类进行注解;
- @Controller:用于对 Controller 的实现类进行注解;
- @Component:用于注解一些工具类或其他不归与上述类别的组件。
而当你需要引入一个 Bean 时,@AutoWired 就可以将 Bean 注入到代码中。
不过 @AutoWired 注解不仅能够用在构造器上,还能用在属性的 Setter 方法上,如下面代码所示
@Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; }
不过实际上 Setter 方法没有任何特殊之处,@Autowried 注解可以用在类的任何方法上。
但是不管是构造器,Setter 方法还是其他的方法,Spring 都会尝试满足方法参数上所声明的依赖。假如 有且只有一个 Bean 匹配依赖需求的话,那么这个 Bean 将会被装配进来。
如果没有匹配的 Bean,那么在应用上下文创建的时候,Spring 就会抛出异常,为了避免异常的出现,你可以将 @Autowired 的 required 属性设置为 false。
@Autowired(required = false) UserDao userDao;
将 required 属性设置为 false 时, Spring 依旧会尝试进行自动装配,但是如果没有匹配到 Bean 的话,Spring 会将这个处于未装配状态。不过我们在实际开发中,毕竟我们不大可能引入一个后面代码没有引用到的对象,而这可能让你的程序报空指针异常。
@Auowried 注解是通过 byType 方式注入的,如果环境中不止匹配到两个 Bean 时,或许你该给你的 Bean 进行命名,然后在 @Qualifier 的属性中指向该 Bean 的名称。
@Component("beanName") public class Bean { // write something } @Autowried @Qualifier("beanName") Bean bean;
通过 Java 代码配置 Bean
通过 Java 代码配置 Bean 的方式在理解起来要简单明了许多,而且这种方式也有其特定的应用场景,比如说你要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加 @Component 或者其他注解的,因此就不能使用自动化装配的方案了。所以在这种情况下,显示装配就成了新的选择,下面我们先来了解下如何通过 Java 代码配置 Bean。
创建配置类
@Configuration public class BeanConfig { }
@Configuration 注解表明这个类是一个配置类,之后我们将在该类中配置 Spring 应用上下文中如何创建类的细节。
声明 Bean
然后在配置类中声明要配置 Bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加 @Bean 注解,同时也可以通过 name 属性指定一个不同的名字,当然不写也没问题。例如下面代码
@Bean(name = "beanName") public Bean bean { return new Bean(); }
假如你需要另外一个 Bean 作为你创建这个 Bean 的参数的话,你也可以通过下面的方式进行配置
public Bean bean(AnotherBean anotherBean) { return new Bean(anotherBean); }
之后的事情就交给 Spring 去完成吧,你依旧可以像前面提及的方式去进行依赖注入。
通过 XML 装配 Bean
在我们项目开发中,我们可以发现,通过注解和 Java 配置文件装配 Bean,最大的好处就是配置方便,直观等等,但是其弊端也显而易见,以硬编码的方式写入到 Java 代码中,当修改其中的代码时,是需要重新编译的。
XML 相对起前面两种方式,也有其相应的利弊。最大的好处莫过于对其做修改,无需编译代码,只需要重启程序即可加载新的配置。下面就来介绍下通过 XML 配置装配 Bean。
创建 XML 配置规范
如代码所示
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="" xmlns:xsi="" xmlns:context="" xsi:schemaLocation=" "> <!-- congifuration here ... --> </beans>
声明 <bean>
<bean id="beanName" class="package.Bean">
这里声明了一个很简单的 Bean,创建这个 Bean 的类通过 class 属性来指定的,并且要使用权限定的类名。同时也要给 bean 声明一个 ID,当然你在这里不声明也没什么问题,bean 本身会自动得到一个 ID 名 package.Bean#0。不过你之后也可能会引用到这个 Bean,所以就加个 ID 咯。
到这里,其实 XML 的配置已经完成了,你可以在你的代码中进行依赖注入了。但是我们的对象初始化的时候,我们可能还要注入一些属性。在 Spring 中,当发现到 <bean> 元素时,会调用这个元素中指向的类的默认构造函数来创建 bean。在 XML 的配置中,没法像 Java Config 那样灵活地配置。接下来就介绍如何在初始化 Bean 时注入属性
借助构造器注入初始化 Bean
当你采用构造器注入时,有两种基本的配置方案可供选择:
- <constructor-arg>元素
- 使用 c- 命名空间(只适用 Spring 3.0 后的版本)
下面举例采用 <constructor-arg> 元素:
<bean id="beanName" class="package.Bean"> <constructor-arg ref="beanName2" /> </bean>
这样,当 Spring 遇到这个 元素时,他会创建一个 Bean 实例。<constructor-arg> 元素会告知 Spring 将一个 ID 为 beanName2 的 bean 引用传递到 Bean 的构造器中。
装配 字面量 以及集合
我们也可以将字面量注入构造器中,我们先创建一个 Bean3 类
public class Bean3 { String str1; String str2; public Bean3(String str1, String str2) { this.str1 = str1; this.str2 = str2; } public void log() { sout(str1 + " " + str2); } }
然后再在 xml 文件中进行以下配置
<bean id="beanName3" class="package.Bean3"> <constructor-arg value="hello" /> <constructor-arg value="world" /> </bean>
接下来介绍下如何装配集合,其实只需要简单在 <constructor-arg> 中声明集合即可,如下所示:
<constructor-arg> <list> <value>str1</value> <value>str2</value> </list> </constructor-arg> ----------------------------------------------- <constructor-arg> <list> <ref bean="beanName" /> <ref bean="beanName" /> <ref bean="beanName" /> </list> </constructor-arg>
设置属性值
假设我们现在有这么一个类,没有任何的构造器(除了隐式的构造器),它没有任何的强依赖。
public void TestBean { private String str1; private List<String> strList; // set()... public void log() { sout(str1); fore strList { sout(strList + " "); } } }
就如上面类所示,它并不强制我们装配任何的属性,但是当你不设置属性属性去调用 log() 方法的时候,毫无疑问会报空指针异常。所以,我们要设置这些属性,可以借助 property 元素的 value 属性实现该功能
<bean id="testBean" class="package.TestBean"> <property name="str1" value="hello" /> <property name="strList"> <list> <value>world</value> <value>and</value> <value>spring</value> </list> </property> </bean>
这样就可以实现将量注入 bean 中了。
小结
Spring 有多种方案来配置 Bean,这给我们提供了许多解决问题的思路,Spring 的配置也是可以相互搭配的,就像我们热衷与使用自动装配,但是有时 xml 装配或者 Java Config 装配才是最优的选择,这取决我们工作中遇见的情况。
顺带一提,若注解与 XML 同时使用,XML 的优先级要高于注解。这样做的好处是,需要对某个 Bean 做修改,只需要修改配置文件即可。
作者:周二
原文: