使用 Spring 的项目中问题存在Bean之间的依赖,例如在某服务启动前要初始化各类词典,主要方式有显式声明Bean和隐式注解注入2种,本文着重介绍后一种。
1. 依赖注入简介
首先我们定义“依赖”是什么:如果在Class A中有Class B的实例,则称A依赖B。
现在我们来构造一个依赖:
public class UserDao {
DBConnection conn;
public UserDao() {
// 先初始化数据库连接
conn = new DBConnection();
}
}
上述代码中,UserDao即对DBConnection存在依赖。但这样构造依赖存在如下问题:
(1) 耦合度高。设想在未来我们为DBConnection的构造函数新增字段name,那么上述代码将修改为:conn = new DBConnection();conn = new DBConnection(String name);
(2) 如果DBConnection的初始化耗时很长,将影响构造函数的后续初始化工作。
上述UserDao在构造函数里直接初始化DBConnection的过程,属于主动初始化依赖对象,二者耦合度高,不方便测试。如果被依赖对象事先就初始化好了,直接供给本Class调用,则称之为依赖注入。例如上述代码可改写为如下:
public class UserDao {
@Autowired //依赖注入
DBConnection conn;
public UserDao() {
}
}
要使注入生效,我们还需要在DBConnection中添加Bean标识Component
@Component //将类标识为Bean
public class DBConnection {
public DBConnection() {
init(); //初始化工作
}
}
同时,为了让Bean的标识被探测到,我们还需要在xml配置文件中添加自动扫描:
<context:component-scan base-package="org.example.service"/>
到此处相信读者大致了解其注入原理:
- 将一些初始化词典、数据库连接的类添加诸如
@Component的标识,使之成为Bean。 - Spring做自动扫描记录下这些Bean。
- 调用方使用
@Autowired对Bean进行依赖注入。
此处我将这3步分别命名为:服务Bean化、自动扫描、依赖注入。接下来我将详细介绍这3部分。
2. 服务Bean化
事实上我们可以在任何情况下使用@Component标识需要被"Bean化"的服务。但不久我们会发现,其他更有水平的代码里会有更多富于变化的标识,例如:@Repository、@Service和@Controller。这是怎么回事?设想我们现在有加载本地词典的类A,本地词典监控类B,二者有明显区别:前者的功能视数据访问,后者则是监控服务,我们更愿意对标识做更精细化的区分,具体如下:
@Repository是为DAO(数据访问)特制的声明,将一个类声明为Repository意味着该类主要功能是数据读取、DB访问。
@Service则声明该类为服务性质的Bean,例如上文中的词典监控服务,还有许多无关数据操作的功能性初始化类,都应该被标识为Service。
@Controller声明标志着一个类是SpringWeb的MVC控制器,主要负责MVC之间的操纵。另一个类似的声明是@RequestMapping,将URL映射为一个方法。
以上3个新的声明均为@Component的子集,因此仅仅使用@Component也不影响后续的扫描和注入过程。但在现实生活中,其实只有很少的场合我们需要用到@Component,例如无法给类做清晰的定位时。
3. 注解扫描
<context:component-scan base-package="org.example.service"/>
注意到上文中的这段自动扫描,它的包名其实可以根据用户需求做出更改。例如:现在我有一个判断query是否为脏词的服务A,它仅依赖注入了自动加载脏词词典的类B。此外,项目中还存在访问数据库的类C,但我们没有为数据库配置访问信息。当我们将注解扫描范围设置为整个项目时,启动服务A将产生问题。
解决方案是缩小自动扫描范围,使得Spring仅扫描B而不扫描C。具体做法是增加扫描过滤器, Spring支持正则Regex和AspectJ两种表达式的方式进行扫描过滤。
例如:
<context:component-scan base-package="org.example.service">
<context:include-filter type="regex"
expression="org\.example\.service\.dictionary\..*"/>
<context:exclude-filter type="aspectj"
expression="org.example.service.db..*"/>
</context:component-scan>
上面的xml配置就成功的扫描名为com.example.service.dictionary的package而忽略了以org.example.service.db为前缀的package。
4. 依赖注入
@Autowired:负责自动装配被成功扫描的Bean。@Qualifier("xxxxx"): 有时候我们有参数不同的两个相同类的bean需要装配(具体为什么会存在这种现象?可想象要连接2个数据库。),那么如何单独指定我需要装配哪一个呢?@Qualifier配合@Autowired可用于消除这类歧义。
在方法内部,我们也有一些"注解":@PostConstruct和@PreDestroy是Bean内分别执行初始化和销毁bean的注解。b>注意!它并不属于Spring,而是属于J2ee里的jar包,因此使用时需要在xml配置:
<context:annotation-config />
@PostConstruct: 初始化bean@preDestroy: 结束前清理。
本文详细介绍了Spring中的依赖注入机制,包括服务Bean化、注解扫描和依赖注入三大部分。通过使用@Component及其子注解@Repository、@Service和@Controller对类进行标记,实现Bean的创建和管理。通过<context:component-scan>标签进行自动扫描,并通过正则表达式过滤扫描范围。最后,@Autowired注解用于依赖注入,@Qualifier用于解决相同类型Bean的选择问题。此外,还提到了@PostConstruct和@PreDestroy用于Bean的初始化和销毁。

2572

被折叠的 条评论
为什么被折叠?



