项目实战--不推荐使用@Autowired实现注入

一、前言

在这里插入图片描述

Spring框架被广泛应用于Java程序程序开发,而最近对公司的code review校验,有个硬性规定是:不允许使用@Autowired进行字段注入(field injection),推荐改为构造器注入(constructor injection)或设值注入(setter injection)。因此记录原因,以便后续开发借鉴。

二、字段注入

2.1 字段注入的使用

字段注入是指直接在类的字段(成员变量)上使用@Autowired注解,以实现依赖的注入。如:

@Service
public class EmployeeServiceImpl implements EmployeeService{
	@Autowired
	private EmployeeMapper employeeMapper;
 	
 	// 业务实现......
}

使用@Autowired注解基于字段的依赖注入编码时,IDEA也会出现弱警告:

Always use constructor based dependency injectionin your beans.
Always use assertions for mandatorydependencies

始终在 bean 中使用基于构造函数的依赖注入。始终对强制依赖项使用断言

且官方从Spring 4.0开始不推荐使用@Autowired进行字段注入。

2.2 字段注入的弊端

2.2.1 与Spring的IOC机制紧密耦合

这种方式把控制权全给Spring的IOC,强依赖于依赖注入框架,其他类想重新设置下该类的某个注入属性,必须通过反射处理,得到的结果再次与Spring类注入器耦合,就失去通过自动装配类字段而实现的对类的解耦,从而使类在Spring容器之外无效.,即对于IOC容器以外的环境,除使用反射来提供它需要的依赖之外,无法复用该实现类。

2.2.2 无法使用final修饰符

由于字段注入是在对象实例化之后进行的,字段不能用final修饰。这会导致以下问题:

  • 不变性(immutability)问题:无法确保依赖关系在对象生命周期内保持不变,从而可能引发难以调试的bug。
  • 设计上的局限:无法利用Java语言的特性来设计出更稳固和安全的代码结构。

2.2.3 隐藏依赖性

使用Spring的IOC,要求被注入的类用public修饰方法(构造方法/setter类型方法)来向外界表达需要什么依赖。但字段注入将依赖关系隐藏在类的内部,基本都是private修饰的,把属性都封印到class当中。

2.2.4 无法对注入的属性进行安全检查

在程序启动的时无法获取这个类,只有在真正业务接口调用时才会获取,若注入的是null,因为Spring不会对依赖的bean是否为null进行判断,不调用接口将一直无法发现NullPointException的存在,想在属性注入的时候,增加验证措施,也无法办到。

2.2.5 掩饰单一职责的设计思想

若使用构造函数的依赖注入,代码会臃肿,如下:

@Service
public class VerifyServiceImpl implents VerifyService{
  private AccountService accountService;
  private UserService userService;
  private IDService idService;
  private RoleService roleService;
  private PermissionService permissionService;
  private EnterpriseService enterpriseService;
  private EmployeeService employService;
  private TaskService taskService;
  private RedisService redisService;
  private MQService mqService;
 
  public SystemLogDto(AccountService accountService, 
                      UserService userService, 
                      IDService idService, 
                      RoleService roleService, 
                      PermissionService permissionService, 
                      EnterpriseService enterpriseService, 
                      EmployeeService employService, 
                      TaskService taskService, 
                      RedisService redisService, 
                      MQService mqService) {
      this.accountService = accountService;
      this.userService = userService;
      this.idService = idService;
      this.roleService = roleService;
      this.permissionService = permissionService;
      this.enterpriseService = enterpriseService;
      this.employService = employService;
      this.taskService = taskService;
      this.redisService = redisService;
      this.mqService = mqService;
  }
}

自然而然的会思考这个类是否违反单一职责思想,而使用字段注入就不会察觉到,甚至会很沉浸在@Autowire当中。

ps:

而现实情况是绝大多数项目都是使用@Autowired实现字段注入,虽然使用方式是最简单,但是也是最不推荐的,若非要使用也是推荐使用 @Resource

三、设值注入

设值注入是通过类的setter方法来注入依赖关系。示例:

@Controller
@RequestMapping("/employee")
public class EmployeeController {
    
    private EmployeeMapper employeeMapper;
    
    @Autowired
    public void setEmployeeMapper(EmployeeMapper employeeMapper) {
        this.employeeMapper = employeeMapper;
    }
}

或者:

@Service
public class MyServiceImpl implents MyService {

    private EmployeeService employeeService;
    
    @Autowired
    public void setEmployeeService(EmployeeService employeeService) {
        this.employeeService= employeeService;
    }
    // class implementation
}

调用:

public class MyServiceImplTest {
    private EmployeeService empService = Mockito.mock(EmployeeService.class);
    private MyServiceImpl myService = new MyServiceImpl();
    @Before
    public void setUp() {
        myService.setEmployeeService(empService);
    }
    @Test
    public void testServiceMethod() {
        // test implementation
    }
}

优点:

  • 相比构造器注入,set注入类似于选择性注入。
  • 允许在类构造完成后重新注入。
    缺点:
    这种方法是 Spring3.X 版本比较推荐的,但是基本上没见到有人用过。

四、构造器注入

这就是目前 Spring 最推荐的注入方式,直接通过带参构造方法来注入。
例如:

// 部分代码
@Component
public class RedisIdWorker {

    private StringRedisTemplate stringRedisTemplate;

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
}

或者:

public class UserServiceImpl implements UserService {
    private final BCryptPasswordEncoder passwordEncoder;
    private final UserMapper userMapper;

    @Autowired
    public UserServiceImpl(BCryptPasswordEncoder passwordEncoder,
                           UserMapper userMapper) {
        this.passwordEncoder = passwordEncoder;
        this.userMapper = userMapper;
    }
}

测试:

public class MyServiceTest {
    private UserMapper userMapper = Mockito.mock(UserMapper.class);
    private UserServiceImpl myService = new UserServiceImpl(userMapper);
    @Test
    public void testServiceMethod() {
        // test implementation
    }
}

优势:

  • 清晰的依赖关系:所有依赖关系在类实例化时就明确了,代码可读性和可维护性大大提高。
  • 不变性:可以使用final修饰符,确保依赖关系在对象生命周期内保持不变。
  • 便于测试:测试类只需通过构造函数注入模拟对象,简化单元测试的编写。

缺点:

  • 当注入参数较多时,代码臃肿。
  • 可能存在循环依赖问题。

解决方案:

  • 重构代码:消除循环依赖,可能成本较高。
  • 使用@Lazy注解:延迟初始化bean,解决循环依赖。
@Lazy
private final UserMapper userMapper;

五、总结

  • Spring提供三种注入方式:字段注入、setter方法注入、构造器注入。推荐使用基于构造器注入的方式。
  • 基于字段的依赖注入方式有很多缺点,主要原因是容易引发NPE,且无法在编译时期发现,应当避免使用基于字段的依赖注入。
  • 对于必需的依赖项,建议使用基于构造函数的注入,以使类成为不可变的,并防止类为null。
  • 对于可选的依赖项,建议使用基于Setter的注入。

通过采用构造器注入或设值注入,可以显著提高代码的健壮性和可测试性,避免字段注入带来的种种弊端。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

容若只如初见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值