Spring三种DI形式详解以及为什么Spring和IDEA都不推荐使用@Autowired注解?


先来看看常用的几种DI方式

  • 构造器注入:利用构造方法的参数进行注入
  • Setter注入:使用Setter方法注入依赖
  • 字段注入:使用@Autowired或者@Resource

我们先来设定一个情景:在一个UserServiceImpl中利用构造方法注入一个UserDaoImpl对象,以供控制层调用使用,这是一个非常常规的控制层获取持久层数据的方案。

先来看构造器注入:

构造器注入又分为xml方式和注解方式:

xml方式注入:

首先需要一个UserService接口和UserDao接口,以及他们两个的实现类UserServiceImpl和UserDaoImpl实现类。

/**
UserService接口
**/
public interface UserService {
    void save();
}

/**
UserDao接口
**/
public interface UserDao {
    void save();
}

/**
UserServiceImpl实现类
**/
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    public UserServiceImpl() {
    }

    @Override
    public void save() {
        System.out.println("我是userServiceImpl:" + userDao);
        userDao.save();
    }
}

/**
UserDaoImpl实现类
**/
public class UserDaoImpl implements UserDao {
    public UserDaoImpl() {
        System.out.println("UserDaoImpl现在被创建啦");
    }
    

    public void init() {
        System.out.println("UserDaoImpl初始化成功");
    }

    public void destroy() {
        System.out.println("UserDaoImpl正在被销毁");
    }

    @Override
    public void save() {
        System.out.println("我是userServiceImpl:" + userDao + "   我将要执行userDao中的sava方法了!!!");
        userDao.save();
    }
}

然后再applicationContext.xml中进行配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton" init-method="init"/>
    
    <!--    构造方法注入-->
    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl" scope="singleton">
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>
</beans>

测试代码:

    @Test
    //利用了构造方法 依赖注入
    public void test05() throws Exception {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) app.getBean("userService");
        userService.save();
    }

执行结果:

UserDaoImpl现在被创建啦
UserDaoImpl初始化成功
我是userServiceImpl:com.itheima.dao.impl.UserDaoImpl@302552ec   我将要执行userDao中的sava方法了!!!
sava is running......

注解方式:

上面我们是通过配置xml方式来实现构造器注入的,还是比较繁琐的,现在很少使用xml配置来进行注入了,直接使用注解更方便,如下:

我们既然要再Spring中使用注解,可能想的很简单,在UserDaoImpl上加一个@Repository注解,再UserServiceImpl加一个@Service注解,表示这两个类的对象创建交给Spring容器管理

@Repository
public class UserDaoImpl implements UserDao {
    public UserDaoImpl() {
        System.out.println("UserDaoImpl现在被创建啦");
    }
	/**
	这个@PostConstruct注解就相当于上面xml中的  init-method="init"
	**/
    @PostConstruct
    public void init() {
        System.out.println("UserDaoImpl初始化成功");
    }

    public void destroy() {
        System.out.println("UserDaoImpl正在被销毁");
    }

    @Override
    public void save() {
        System.out.println("sava is running......");
    }
}

@Service
public class UserServiceImpl02 implements UserService {
    private UserDao userDao;

    public UserServiceImpl02(UserDao userDao) {
        this.userDao = userDao;
    }
	/**
	这个无参构造不能删
	**/
    public UserServiceImpl02() {
    }

    @Override
    public void save() {
        System.out.println("我是userServiceImpl" + userDao);
        userDao.save();
    }
}

但这样还不够,想想,我们是要在UserServiceImpl中使用userDao的,也就是UserDaoImpl,怎么用呢, 之前xml中,我们是写了一个构造器,然后xml配置文件进行配置,声明这个类里面使用了UserDaoImpl,现在用注解注入了,就用注解的方式来做,具体如下:

@Service
public class UserServiceImpl implements UserService {
    private UserDao userDao;
	//这里就不能使用@Resource注解了
    @Autowired
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    public UserServiceImpl02() {
    }

    @Override
    public void save() {
        System.out.println("我是userServiceImpl" + userDao + "   我将要执行userDao中的sava方法了!!!");
        userDao.save();
    }
}

我们既然使用注解了,那applicationContext.xml中的内容是不是也要换一换了

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
    
    <!--    构造方法注入-->
    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl" scope="singleton">
        <!-- <constructor-arg name="userDao" ref="userDao"/>-->
    </bean>
    
    <!--注解的组件扫描-->
    <context:component-scan base-package="com.itheima"></context:component-scan>
</beans>

之前的< bean id=“userDao”>现在用不着了,因为我们使用了注解,但是要注意的是,要想使用@Resource注解,得写一个依赖

        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>

然后,还得再applicationContext.xml中写一个注解扫描

    <!--注解的组件扫描-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

这样就可以在Spring中使用注解了。测试如下:

    @Test
    //利用了构造方法 依赖注入
    public void test05() throws Exception {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) app.getBean("userService");
        userService.save();
    }
UserDaoImpl现在被创建啦
UserDaoImpl初始化成功
我是userServiceImpl:com.itheima.dao.impl.UserDaoImpl@65c7a252   我将要执行userDao中的sava方法了!!!
sava is running......

再来看Setter注入:

Setter注入又分为xml方式和注解方式:

xml方式注入:

首先需要一个UserService接口和UserDao接口,以及他们两个的实现类UserServiceImpl和UserDaoImpl实现类,这里的UserServiceImpl实现类使用的是setter注入方式,注意。

/**
UserService接口
**/
public interface UserService {
    void save();
}

/**
UserDao接口
**/
public interface UserDao {
    void save();
}

/**
UserServiceImpl实现类
**/
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        System.out.println("我是userServiceImpl" + userDao + "  我正在被创建");
        userDao.save();
    }
}


/**
UserDaoImpl实现类
**/
public class UserDaoImpl implements UserDao {
    public UserDaoImpl() {
        System.out.println("UserDaoImpl现在被创建啦");
    }
    

    public void init() {
        System.out.println("UserDaoImpl初始化成功");
    }

    public void destroy() {
        System.out.println("UserDaoImpl正在被销毁");
    }

    @Override
    public void save() {
        System.out.println("我是userServiceImpl:" + userDao + "   我将要执行userDao中的sava方法了!!!");
        userDao.save();
    }
}

然后再applicationContext.xml中进行配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton" init-method="init"/>

    <!--    set注入方法 使用依赖注入但是没有使用p标签简化-->
    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
    
</beans>

测试代码:

    @Test
    //利用了setter方法 依赖注入
    public void test05() throws Exception {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) app.getBean("userService");
        userService.save();
    }

执行结果:

UserDaoImpl现在被创建啦
UserDaoImpl初始化成功
我是userServiceImplcom.itheima.dao.impl.UserDaoImpl@408d971b  我正在被创建
sava is running......

注解方式:

大概写法和构造器中的注解方式差不多,就直接上代码了

@Repository
public class UserDaoImpl implements UserDao {
    public UserDaoImpl() {
        System.out.println("UserDaoImpl现在被创建啦");
    }
	/**
	这个@PostConstruct注解就相当于上面xml中的  init-method="init"
	**/
    @PostConstruct
    public void init() {
        System.out.println("UserDaoImpl初始化成功");
    }

    public void destroy() {
        System.out.println("UserDaoImpl正在被销毁");
    }

    @Override
    public void save() {
        System.out.println("sava is running......");
    }
}

@Service
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    @Autowired
    //@Resource 注解也可以,区别下面会说
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void save() {
        System.out.println("我是userServiceImpl" + userDao + "  我正在被创建");
        userDao.save();
    }
}

最后看看字段注入:

就直接放UserServiceImpl的代码了,前面看明白了这里看一个UserServiceImpl就够了

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public void save() {
        System.out.println("我是userServiceImpl" + userDao);
        userDao.save();
    }
}

下面就是为什么Spring和IDEA都不推荐使用@Autowired注解的原因了

平常我们在代码中注入一个字段的时候,会用到@Autowired注解,但是这个注解有个问题,

image-20221007104424260

@Autowired VS @Resource

他们的基本功能都是通过注解实现依赖注入,只不过@Autowired是Spring定义的,而@Resource是JSR-250定义的。大致功能基本相同,但是还有一些细节不同:

  • 依赖识别方式:@Autowired默认是byType可以使用@Qualifier指定Name,@Resource默认ByName如果找不到则ByType
  • 适用对象:@Autowired可以对构造器、方法、参数字段使用,@Resource只能对方法字段使用
  • 提供方:@Autowired是Spring提供的,@Resource是JSR-250提供的

各种DI方式的优缺点:

  • 构造器注入:强依赖性(即必须使用此依赖),不变性(各依赖不会经常变动)
  • Setter注入:可选(没有此依赖也可以工作),可变(依赖会经常变动)
  • Field注入:大多数情况下尽量少使用字段注入,一定要使用的话, @Resource相对@Autowired对IoC容器的耦合更低

Field注入的缺点:

会导致组件与IoC容器紧耦合(这是最重要的原因,离开了IoC容器去使用组件,在注入依赖时就会十分困难)

为什么IDEA只对@Autowired警告

Field注入虽然有很多缺点,但它的好处也不可忽略:那就是太方便了。使用构造器或者setter注入需要写更多业务无关的代码,十分麻烦,而字段注入大幅简化了它们。并且绝大多数情况下业务代码和框架就是强绑定的,完全松耦合只是一件理想上的事,牺牲了敏捷度去过度追求松耦合反而得不偿失。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值