【Spring】@Autowired、@Resource、@RequiredArgsConstructor 的使用、区别、最佳实践

elltor 2021年03月20日 90次浏览

Spring 的核心是Ioc容器和DI(Dependence Injection)思想,这些提供了 java 对象的管理以及他们之间依赖的管理。bean 的管理是Spring自动管理的,而我们只需要使用一些注解(Annotion)。

常用的是 @Resource 和 @Autowired 以及 lambok 的构造器进行注入。

使用

注入前对象必须由 Spring 管理。通过 @Component、@Service、@Repository 标识,Springboot会自动扫描并初始化 Bean。

@Autowired

    @Autowired
    private UserMapper userMapper;

如果Bean出现了重复,在项目中通常使用@Qualifier单独指定bean. 注意: @Qualifier不能单独使用只能配合@Autowired使用。

    @Autowired
    @Qualifier(value = "userMapper")
    private UserMapper userMapper;

@Autowired 可以放在成员变量(field),setter、类构造器。它只有一个属性 —— required,Boolean类型,取值为false时不依赖bean,也就是说被注入field可以为null,否则当依赖bean不存在时报错。

// Autowired 构造器注入
class UserServiceImpl{
    private UserMapper userMapper;
    private DeptMapper deptMapper;
    
    @Autowired
    public UserServiceImpl(UserMapper userMapper, DeptMapper deptMapper){
        this.userMapper = userMapper;
        this.deptMapper = deptMapper;
    }
}

// Autowired Setter 注入
class UserServiceImpl{
    private UserMapper userMapper;
    
    @Autowired
    public void setUserMapper(UserMapper userMapper){
        this.userMapper = userMapper;
    }
}

@Resource

@Resource 是java规范的一个注解,Spring也支持它,因此在spring项目中可以用它。

@Resource 是默认按照类型的名字注入bean,这个名字通常是类名(其第一个字母小写);它也可以通过类型匹配,通过哪种类型匹配取决于 name 和 type 两个属性,两个不同时出现,同时出现按默认走。

当@Resource匹配不到Bean时就不再注入,此时field为null。

    // 按名字注入bean
    @Resource(name = "userMapper")
    private UserMapper userMapper;

    // 按类型注入备案
    @Resource(type = UserMapper.class)
    private UserMapper userMapper;

在项目中 @Autowired 已经不推荐使用了, 通常推荐使用 JSR-250 @Resource,这样可以减少对Spring的依赖,让代码更加规范统一。

另外,和@Resource相关的还有 @PostConstruct以、@PreDestroy。

  • @PostConstruct 相当于init-method,使用在方法上,当Bean初始化时执行。
  • @PreDestroy 相当于destory-method,使用在方法上,当Bean销毁时执行。
@Autowired 和 @Resource 的区别
  • @Autowired 是 Spring 提供的注解,@Resource 是JDK提供的注解
  • @Autowired 默认通过被注入对象的类型注入,而@Resource默认通过类型注入
  • @Autowired与@Resource都可以用来装配bean. 都可以写在字段上或写在setter方法上

@RequiredArgsConstructor构造器注入

lambok 的构造器注入需要使用 @RequiredArgsConstructor 注解, 放在类上。

简单使用示例

@RequiredArgsConstructor
@Service
public class UserServiceImpl{
    private final UserMapper userMapper;
}

注意事项

  • 类需要注入的字段(fields)都需要 final 修饰
  • 如不用 final 修饰,或已经赋值常量,则 spring 不进行注入,这些字段通常是类中的定义的静态常量, 如下
@RequiredArgsConstructor
public class UserServiceImpl{
    // 注入
    private final UserMapper userMapper;
    // 不注入
    private static String USER_SIGN = "admin"
    // 不注入
    private final static String USER_SIGN2 = "user"
}
  • 使用构造器注入时,在使用对象就不能推荐new了,new对象时调用对象构造器,因为对象的field是通过构造器注入的,new时所有需要注入的对象都需要填在构造器里,当对象需要注入的bean多时是比较麻烦的一件事。

经常遇到的问题

常见Spring注入Bean为null

  1. 首先排查bean是否被扫描到,Springboot项目中与启动类同级及一下的bean才能被扫描到; 引入mybatis或mybatis plus所有模块的Mapper都需要使用@MapperScan进行扫描,否则需要使用xml配置扫描位置(不推荐这种方法)。
  2. 如果bean装配没有问题,那可能这个对象是new出来的,这时不对new的对象中的@Autowired @Resource标识的进行注入。
  3. bean出现重复,这种情况控制台会报错,设置并在注入的时候通过@Qualifier或@Resource(name = "xxx")指明具体的bean即可
  4. 另一种解决办法,通过SpringContext拿,方法如下
@Slf4j
@Configuration
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {

    private static ApplicationContext applicationContext = null;

    /**
     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        assertContextInjected();
        return (T) applicationContext.getBean(name);
    }

    /**
     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(Class<T> requiredType) {
        assertContextInjected();
        return applicationContext.getBean(requiredType);
    }

    @Override
    public void destroy() {
        SpringContextHolder.clearHolder();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringContextHolder.applicationContext != null) {
            log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);
        }
        SpringContextHolder.applicationContext = applicationContext;
    }
}

另一种情况注入对象为null的情况

在静态代码块/普通代码块使用被注入对象,被注入的对象为null。

这种情况代码:

@Component("functionExecutor")
public class FunctionExecutor {

    @Resource(name = "whereGreaterThenFunction")
    private  WhereGreaterThenFunction whereGreaterThenFunction;
    
    @Resource(name = "countFunction")
    private  CountFunction countFunction;
    
    // 静态/普通代码
   {
        AviatorEvaluator.addFunction(whereGreaterThenFunction);
        AviatorEvaluator.addFunction(countFunction);
    }
}

这里注入不成功是因为,在初始化FunctionExecutor时,成员变量还没有被注入,此时对象为null。

解决办法是使用 @PostConstruct ,使用这个注解的方法会在bean初始化完成后被调用,这时所有的成员变量都已经注入。

最佳实践

  1. 在使用@Component @Service @Controller @Repository通常不指定bean的名字, 除非有重名的
  2. 注入bean时推荐使用lombok的构造器注入,更加简洁
  3. 单独注入bean推荐使用@Resource而不是@Autowired