暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring进阶-依赖注入

一个java菜鸟程序员 2021-08-18
237

什么是依赖

通常在我们的程序中,我们的一个组件通常需要依赖其他组件才能完成一件工作。例如在我们的程序中我们有一个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的自动注入

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


文章转载自一个java菜鸟程序员,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论