
Spring框架被广泛应用于Java程序程序开发,而最近对公司的code review校验,有个硬性规定是:不允许使用@Autowired进行字段注入(field injection),推荐改为构造器注入(constructor injection)或设值注入(setter injection)。因此记录原因,以便后续开发借鉴。
字段注入是指直接在类的字段(成员变量)上使用@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进行字段注入。
这种方式把控制权全给Spring的IOC,强依赖于依赖注入框架,其他类想重新设置下该类的某个注入属性,必须通过反射处理,得到的结果再次与Spring类注入器耦合,就失去通过自动装配类字段而实现的对类的解耦,从而使类在Spring容器之外无效.,即对于IOC容器以外的环境,除使用反射来提供它需要的依赖之外,无法复用该实现类。
由于字段注入是在对象实例化之后进行的,字段不能用final修饰。这会导致以下问题:
使用Spring的IOC,要求被注入的类用public修饰方法(构造方法/setter类型方法)来向外界表达需要什么依赖。但字段注入将依赖关系隐藏在类的内部,基本都是private修饰的,把属性都封印到class当中。
在程序启动的时无法获取这个类,只有在真正业务接口调用时才会获取,若注入的是null,因为Spring不会对依赖的bean是否为null进行判断,不调用接口将一直无法发现NullPointException的存在,想在属性注入的时候,增加验证措施,也无法办到。
若使用构造函数的依赖注入,代码会臃肿,如下:
@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 } } 优点:
这就是目前 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 } } 优势:
缺点:
解决方案:
@Lazy private final UserMapper userMapper; 通过采用构造器注入或设值注入,可以显著提高代码的健壮性和可测试性,避免字段注入带来的种种弊端。