Spring进阶-依赖注入

什么是依赖

通常在我们的程序中,我们的一个组件通常需要依赖其他组件才能完成一件工作。例如在我们的程序中我们有一个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中提供了四种自动注入的方式,分别为nobyNamebyTypeconstructor。其实通过名字我们就可以知道是什么意思,默认情况下为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

从最后的打印结果都表明了,byNamebyType这两种自动注入生效了。对于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。简单的说,AutowiredAnnotationBeanPostProcessorpostProcessMergedBeanDefinition方法中将@Autowired注解解析成InjectionMetadata数据缓存起来,然后在postProcessProperties再将依赖注入到实例中。所以说,@Autowired并不是所谓的自动注入。

自动注入的缺陷

虽然Spring提供了自动注入的功能,但是Spring官方并不推荐使用自动注入的方式,它存在下面这些缺点。

  • propertyconstructor-arg会覆盖掉自动注入,同时对于基础类型例如String等类型无法做到自动注入。
  • 自动注入相比与手动注入缺少精确性。自动注入它是一种猜测性的方式来实现注入,有时候很可能会产生一些不确定的结果。
  • 如果在容器中有多个Bean与注入类型相同,这样会产生一些错误。

循环依赖

Spring可以帮我们进行依赖注入,可是依赖注入就不会有其他问题了吗?如果现在我有两个示例A和B,它们内部分别依赖对方实例,像这种依赖最后形成了一个环也就发生了循环依赖问题。Spring能不能解决这循环依赖问题呢?答案是可以也不可以。为什么这么说,这个需要分情况。有些情况Spring是可以解决循环依赖的问题,但有的条件下是无法解决循环依赖的问题的。关于循环依赖,后面我会单独写一篇文章来详细说明,限于篇幅我这里不细说了,有兴趣的可以先去了解一下。

小结

看完本文后我希望你能了解以下几点:

  • 什么是依赖以及过多的依赖导致的问题
  • 依赖注入的方式以及好处
  • Spring的自动注入

对于依赖注入时存在的循环依赖问题这里没有细说,欢迎订阅后续更新。


原文始发于微信公众号(一只菜鸟程序员):Spring进阶-依赖注入

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/72972.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!