什么是依赖
通常在我们的程序中,我们的一个组件通常需要依赖其他组件才能完成一件工作。例如在我们的程序中我们有一个UserService
类,它提供了一个通过用户名称查询用户的方法,而UserServuce
查询用户的实现需要通过UserDao
实例来实现。
public class App1 {
public static void main(String[] args) {
UserDao userDao = new UserDao();
UserService userService = new UserService(userDao);
User user = userService.queryUserByName("mac");
}
}
class UserService{
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public User queryUserByName(String userName){
return userDao.getByName(userName);
}
}
class UserDao{
User getByName(String name){
User user = new User();
user.setUserName(name);
return user;
}
}
在上面这个示例中,UserService
它的创建或者说它的功能实现需要UserDao
才能完成。这里面的UserDao
就是我们所说的依赖
。
过多依赖存在的问题
在上面的示例中,UserService
只有一个依赖UserDao
,但是在实际的业务场景中并非如此,这样会存在什么问题呢?
-
创建组件变得困难。如果组件依赖过多,在创建组件时我们需要先创建出组件的所有依赖项,这样才能完成整个组件的创建。 -
业务开发效率低。创建组件时还需要了解组件的依赖项如何创建,这无疑是降低了开发效率。例如我们不仅需要知道 UserService
有哪些组件,甚至我们还需要了解组件依赖项UserDao
是如何创建的。 -
错综复杂的依赖关系处理起来很闹心。
上面列举的只是依赖过多时的几种常见问题,对于实际开发中还存在许多其他问题。
依赖注入
对于过多的依赖存在的问题,难道就没有解决的办法了吗?作为最流行的IOC容器框架,Spring提供了「依赖注入」和「依赖查找」(实际开发中使用较少)来解决上面依赖过多时存在的问题。那么什么是依赖注入呢?简单的说就是你的组件依赖什么,对于这些依赖不再需要你自己手动去创建,而是容器在创建组件时就将你需要的依赖注入到你的组件。例如在上面的示例中,UserService
依赖UserDao
,那么Spring在创建UserService
时会创建UserDao
实例,然后再将UserDao
实例注入到UserService
中。
通常来说,Spring的依赖注入的方式有两种:基于构造函数的依赖项注入和基于Setter方法的依赖项注入。对于其他方式的依赖注入,都是基于这两种的一种变形实现。
基于构造函数的依赖注入
从名称就能看出来这种依赖注入是通过构造方法的参数注入的。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.buydeem.share.di.UserDao"/>
<bean id="userService" class="com.buydeem.share.di.UserService">
<constructor-arg name="userDao" ref="userDao"/>
</bean>
</beans>
public class App2 {
public static void main(String[] args) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions("spring-di-1.xml");
UserDao userDao = factory.getBean("userDao", UserDao.class);
UserService userService = factory.getBean("userService", UserService.class);
System.out.println("userService.getUserDao() == userDao : " + (userService.getUserDao() == userDao));
}
}
还是之前的示例,我们通过构造方法将UserDao
示例注入到了UserService
实例中,通过最后的比较结果我们也可以看出,UserService
中的UserDao
与Spring容器创建的UserDao
注入到了UserService
中。
基于Setter方法注入
通过名字同样可以看出,这种自动注入的方式就是通过Setter方法来完成依赖的注入。Java示例代码不变,Spring配置文件的修改如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.buydeem.share.di.UserDao"/>
<bean id="userService" class="com.buydeem.share.di.UserService">
<property name="userDao" ref="userDao"/>
</bean>
</beans>
上面代码的运行结果与上面相同。
构造函数依赖注入和Setter方法依赖注入
从上面的内容我们知道,我们可以通过构造函数或者Setter方法的方式来完成依赖注入,但是我们应该选哪一种呢?spring官方有给出一些建议:
-
对于构造函数和Setter方法依赖注入这两种方式是可以共存的。对于可依赖项我们通常建议使用Setter依赖,而对于必要依赖项我们通常建议使用构造函数的方式。 -
Spring团队推荐的是构造函数依赖注入,这种方式的好处是当组件被创建出来后可以让组件变成不可变对象。实际开发中如果我们的依赖项太多,这会导致构造函数的参数过多,这个时候我们应该考虑我们的组件是否承担了过多的职责。 -
Setter注入应该用在可选依赖项,这些依赖项可以在构造函数中设置默认值,否则我们在使用时应该就行判空处理。
自动注入
从前面我们了解依赖注入的两种方式,这两种方式都需要我们在XML中配置依赖关系。如果现在我们组件依赖很多其他组件,我需要在XML中将这些组件一一配置清楚。那有没有什么办法可以简化这种操作呢?答案就是自动注入
。在Spring中提供了四种自动注入的方式,分别为no
、byName
、byType
和constructor
。其实通过名字我们就可以知道是什么意思,默认情况下为no
,也就是没有开启自动注入功能。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="demo" class="com.buydeem.share.di.Demo"/>
<bean id="test1" class="com.buydeem.share.di.Test" autowire="byType" primary="true"/>
<bean id="test2" class="com.buydeem.share.di.Test"/>
<bean id="testService" class="com.buydeem.share.di.TestServer" autowire="byName"/>
</beans
public class App3 {
public static void main(String[] args) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions("spring-di-3.xml");
Demo demo = factory.getBean("demo", Demo.class);
Test test1 = factory.getBean("test1", Test.class);
Test test2 = factory.getBean("test2", Test.class);
TestServer testServer = factory.getBean("testService", TestServer.class);
System.out.println("test.getX() == demo : " + (test1.getX() == demo));
System.out.println("testServer.getTest1() == test : " + (testServer.getTest1() == test1));
System.out.println("testServer.getTest1() == test : " + (testServer.getTest1() == test2));
}
}
class TestServer{
private Test test;
public Test getTest1() {
return test;
}
public void setTest1(Test test) {
this.test = test;
}
}
class Test{
private Demo x;
public Demo getX() {
return x;
}
public void setX(Demo x) {
this.x = x;
}
}
class Demo{}
在上面的示例中,我们并没有手动的配置依赖,但是运行代码最后的打印结果如下:
test.getX() == demo : true
testServer.getTest1() == test : true
testServer.getTest1() == test : false
从最后的打印结果都表明了,byName
和byType
这两种自动注入生效了。对于constructor
这种方式,你可以理解为byType
的一种变形,这里就不细说了。
@Autowired算不算自动注入
在实际开发中我们可以使用XML注解这种方式越来越少了,特别是在SpringBoot流行的今天。在我们的项目中我们经常使用Autowired
来进行依赖注入,例如下面的示例:
@Autowired
public UserDao userDao;
那么我现在要问的是@Autowired算不算自动注入呢?网上都说这是自动注入的一种,但是个人认为他并不算是自动注入。在Spring官方文档中,从来没有说过@Autowired是自动注入,Spring官方文档在Dependency Injection
下面只说过这么一段话:
DI exists in two major variants: Constructor-based dependency injection and Setter-based dependency injection.
简单的说就是自动注入主要的两种方式就是:基于构造函数和Setter方法的注入。而对于自动注入在AutowireCapableBeanFactory
中只定义了如下几种:
public interface AutowireCapableBeanFactory extends BeanFactory {
int AUTOWIRE_NO = 0;
int AUTOWIRE_BY_NAME = 1;
int AUTOWIRE_BY_TYPE = 2;
int AUTOWIRE_CONSTRUCTOR = 3;
@Deprecated
int AUTOWIRE_AUTODETECT = 4;
}
这几种就是我们前面所说的几种,其中有一种已经在Spring中废弃了。我们使用示例来说明,示例代码如下:
public class App4 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.buydeem.share.di");
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
BeanDefinition definition = context.getBeanDefinition(name);
if (definition instanceof GenericBeanDefinition){
int autowireMode = ((GenericBeanDefinition) definition).getAutowireMode();
System.out.println(definition.getBeanClassName() + " => " + autowireMode);
}
}
}
}
@Component
@Data
class A{
@Autowired
private B b;
}
@Component
class B{
}
在上面的示例中,示例A
依赖示例B
,我们通过打印这两个Bean的BeanDefinition查看它的autowireMode
看它属于自动注入的哪一种。最后的打印结果如下:
com.buydeem.share.di.A => 0
com.buydeem.share.di.B => 0
从最后的结果可以看出,这两个Bean的BeanDefinition中的autowireMode
都为0,根据AutowireCapableBeanFactory
中的定义可知它们都不属于自动注入。如果你看过我之前写的关于《Spring进阶-BeanPostProcessor》
一文就知道,@Autowired的实现是通过AutowiredAnnotationBeanPostProcessor
这个类实现的。而AutowiredAnnotationBeanPostProcessor
则是我们常说的BeanPostProcessor
。简单的说,AutowiredAnnotationBeanPostProcessor
在postProcessMergedBeanDefinition
方法中将@Autowired
注解解析成InjectionMetadata
数据缓存起来,然后在postProcessProperties
再将依赖注入到实例中。所以说,@Autowired
并不是所谓的自动注入。
自动注入的缺陷
虽然Spring提供了自动注入的功能,但是Spring官方并不推荐使用自动注入的方式,它存在下面这些缺点。
-
property
和constructor-arg
会覆盖掉自动注入,同时对于基础类型例如String
等类型无法做到自动注入。 -
自动注入相比与手动注入缺少精确性。自动注入它是一种猜测性的方式来实现注入,有时候很可能会产生一些不确定的结果。 -
如果在容器中有多个Bean与注入类型相同,这样会产生一些错误。
循环依赖
Spring可以帮我们进行依赖注入,可是依赖注入就不会有其他问题了吗?如果现在我有两个示例A和B,它们内部分别依赖对方实例,像这种依赖最后形成了一个环也就发生了循环依赖
问题。Spring能不能解决这循环依赖问题呢?答案是可以也不可以。为什么这么说,这个需要分情况。有些情况Spring是可以解决循环依赖的问题,但有的条件下是无法解决循环依赖的问题的。关于循环依赖,后面我会单独写一篇文章来详细说明,限于篇幅我这里不细说了,有兴趣的可以先去了解一下。
小结
看完本文后我希望你能了解以下几点:
-
什么是依赖以及过多的依赖导致的问题 -
依赖注入的方式以及好处 -
Spring的自动注入
对于依赖注入时存在的循环依赖问题这里没有细说,欢迎订阅后续更新。
原文始发于微信公众号(一只菜鸟程序员):Spring进阶-依赖注入
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/72972.html