• Spring——@Autowired注解以及与@Resource区别


    本篇介绍Spring中注入bean的方式之一@Autowired注解的使用方法;同为注入bean的注解@Resource,比较@Autowired与其在使用上的区别及注意事项;这部分知识也是面试经常会问的知识点,需要掌握;

    1. @Autowired注解介绍及各个使用场景示例

    先看看@Autowired注解的定义:

    1. // org.springframework.beans.factory.annotation.Autowired
    2. @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
    3. @Retention(RetentionPolicy.RUNTIME)
    4. @Documented
    5. public @interface Autowired {
    6. /**
    7. * Declares whether the annotated dependency is required.
    8. * <p>Defaults to {@code true}.
    9. */
    10. boolean required() default true;
    11. }

    可以看出该注解能够使用在5种目标类型上:

    • 构造器
    • 方法
    • 方法参数
    • 成员属性
    • 注解

    我们自己开发过程中,用的最多是Field成员属性上;接下来,我们重点看看在其他地方该怎么用;

    1.1 成员变量

    在成员变量上使用Autowired注解,这种方式可能是平时用得最多的;

    1. @Service
    2. public class ServiceAImpl implement ServiceA {
    3. @Autowired
    4. private ServiceB serviceB;
    5. }

    1.2 构造器

    在构造器上使用Autowired注解,实际上还是使用了Autowired装配方式,并非构造器装配;

    1. @Service
    2. public class ServiceAImpl implement ServiceA {
    3. private ServiceB serviceB;
    4. @Autowired
    5. public ServiceAImpl(ServiceB serviceB) {
    6. this.serviceB = serviceB;
    7. System.out.println("serviceB:" + serviceB);
    8. }
    9. }

    1.3 方法

    可以在普通方法上加Autowired注解,spring会在项目启动的过程中,自动调用一次加了@Autowired注解的方法,我们可以在该方法做一些初始化的工作,这种写法一般用于框架中;

    1. @Service
    2. public class ServiceA {
    3. // @Autowired
    4. // private ServiceB serviceB;
    5. @Autowired
    6. public void init(ServiceB serviceB){
    7. final String init4ServiceA = serviceB.init4ServiceA();
    8. log.info(init4ServiceA);
    9. }
    10. }

    注意,这种写法一般是要求被加Autowired注解的方法包含入参的,且参数类型的bean是存在的;如果不加参数,也能启动成功但是打印提示"Autowired annotation should only be used on methods with parameters";如果方法参数对应的bean不存在,也会有提示"Could not autowire. No beans of 'Xx' type found.",这种情况下编译器提示error,是无法启动的;

    除了普通方法,当然也可以在setter方法上Autowired注解,该方式与属性注入一样;

    1. @Service
    2. public class ServiceA {
    3. private ServiceB serviceb;
    4. @Autowired
    5. public void setUser(ServiceB serviceb) {
    6. this.serviceb= serviceb;
    7. }
    8. }

    1.4 方法参数

    可以在构造器的入参上加Autowired注解;

    1. @Service
    2. public class ServiceA {
    3. private ServiceB serviceB;
    4. public ServiceA(@Autowired ServiceB serviceB) {
    5. this.serviceB= serviceB;
    6. }
    7. }

    也可以在非构造器方法的普通方法入参上加Autowired注解;

    1. @Service
    2. public class ServiceA {
    3. public void test(@Autowired ServiceB serviceB) {
    4. serviceB.doB();
    5. }
    6. }

    1.5 注解

    这种方式其实用得不多,就不介绍了;

    2.  使用@Autowired时可能遇到的问题

    2.1 自动装配的bean不存在

    依赖倒置原则DIP是Spring IOC的设计原理,依赖注入DI是IOC的实现方式;

    这一思想通俗的将就是:流程固定的条件下,我们有什么实现,程序执行就用什么;这也是依赖倒置原则(DIP)所表达的——程序不应依赖于实现,而应依赖于抽象;

    这种设计思想在一些框架内,包括Spring内部都有类似的实现;例如这样的一个场景:某个异步执行器代理,在准备异步执行方法时,先去IOC容器中去找一个类型为"TaskExecutor"且名字叫"taskExecutor"的bean,作为异步执行的线程池;如果找到了则用这个bean,如果没找到则使用自己事先根据默认参数创建的线程池;

    在对这个异步执行代理bean执行依赖注入的过程中,Spring会去扫描这个名为"taskExecutor"的bean,如果直接使用@Autowired注解注入(注解的属性的默认值是true),若这个名为"taskExecutor"的bean未找到,注入就会失败,程序报错;如下:

    启动时会报错:

    1. ***************************
    2. APPLICATION FAILED TO START
    3. ***************************
    4. Description:
    5. Field baseService in com.internet.demo.service.testautowire.MyService required a bean of type 'com.internet.demo.service.testautowire.BaseService' that could not be found.
    6. Action:
    7. Consider defining a bean of type 'com.internet.demo.service.testautowire.BaseService' in your configuration.

    而我们期望的结果是:有bean就注入,没有找到bean就不注入,改为使用默认线程池;因此解决办法就是改为使用@Autowired(required=false),表示我们不使用自动装配功能,即忽略当前要注入的bean,如果有则直接注入,如果没有则跳过,不会启动时报错;如下,代码不再提示错误,启动成功;

    2.2 同类型的多个bean的注入失败

    Spring中@Autowired注解是按照bean的类型装配的,也就是我们所说的byType方式;当相同type的bean存在多个时,我们在一个bean中注入这个type的bean,就会报错;

    先尝试一种操作,在项目的不同包目录下,分别建了一个同名的类ServiceA,然后启动工程,发现报错:

    Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'serviceA' for bean class [com.internet.demo.service.testautowire.ServiceA] conflicts with existing, non-compatible bean definition of same name and class [com.internet.demo.service.cycledependence.ServiceA]
    

    注意,这个错误不管是否有没有在其他bean中注入ServiceA,他都会报错;其原因并不是由于ServiceA的type下存在多个bean,而是因为Spring的@Service注解下不允许出现相同的类名,因为Spring会将类名的第一个字母转换成小写,作为bean的名称,如:serviceA,而默认情况下bean名称必须是唯一的;这一点非常容易产生混淆,需要注意;

    下面演示一种产生两个相同的类型bean的情况;定义接口BaseService,让A和B分别实现,然后将类型的bean注入;

    1. @Service
    2. public class BaseServiceImplA implements BaseService {
    3. @Override
    4. public void baseMethod() {}
    5. }
    6. @Service
    7. public class BaseServiceImplB implements BaseService {
    8. @Override
    9. public void baseMethod() {}
    10. }

    其实编译器已经会提示我们,同类型下出现了多个bean,启动会报错;

    1. ***************************
    2. APPLICATION FAILED TO START
    3. ***************************
    4. Description:
    5. Field baseService in com.internet.demo.service.testautowire.MyService required a single bean, but 2 were found:
    6. - baseServiceImplA: defined in file [C:\IDE_Apps\demo\target\classes\com\internet\demo\service\testautowire\BaseServiceImplA.class]
    7. - baseServiceImplB: defined in file [C:\IDE_Apps\demo\target\classes\com\internet\demo\service\testautowire\BaseServiceImplB.class]
    8. Action:
    9. Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

    还有一个情况会产生两个相同的类型bean,如定义了一个配置类,在其中手动注册多个同类型但不同名字的bean,也会报错,如下;

    1. @Configuration
    2. public class BeanConfig {
    3. @Bean
    4. public BaseService getBaseServiceA(){
    5. return new BaseServiceImplA();
    6. }
    7. @Bean
    8. public BaseService getBaseServiceB(){
    9. return new BaseServiceImplB();
    10. }
    11. }

    可以看到报错的提示里面已经提示了解决思路,出现了2个注解:@Primary和@Qualifier;显然在spring中,按照Autowired默认的装配方式:byType,是无法解决上面的问题的,这时可以改用按名称装配:byName,而@Qualifier注解就有这个作用;

    在注入BaseService的地方,在@Autowired注解上面,同时加上@Qualifier注解即可,它指定一个bean的名称,通过bean名称找到需要装配的bean;

    1. @Service
    2. public class MyService {
    3. @Autowired
    4. @Qualifier("baseServiceImplA")
    5. private BaseService baseService;
    6. }

    另一个注解@Primary注解也可以解决上面的问题,如在BaseService的其中一个实现类上面加上@Primary注解,当注入BaseService类型的bean存在多个时,被@Primary标记的bean会作为主bean被注入;

    1. @Primary
    2. @Service
    3. public class BaseServiceImplA implements BaseService {
    4. @Override
    5. public void baseMethod() {}
    6. }

    2.3 用集合去接收多个同类型的bean

    上面举的例子都是通过@Autowired自动装配单个实例,实际上它也能自动装配多个实例,这是一个比较巧妙的用法,并且@Resource注解也支持;

    1. @Slf4j
    2. @Service
    3. public class MyService {
    4. @Autowired
    5. private List<BaseService> baseServiceList;
    6. @Autowired
    7. private Map<String, BaseService> baseServiceMap;
    8. public void printDependence() {
    9. log.info("baseServiceList={}", baseServiceList);
    10. log.info("baseServiceMap={}", baseServiceMap);
    11. }
    12. }

    打印分别打印集合,发现多个bean都被放入了集合;对于Map类型,key存放的就是bean的name,对于Config形式注册的bean,name就是打了@Bean注解的方法名;

    1. baseServiceList=[
    2. com.internet.demo.service.testautowire.BaseServiceImplA@511f5b1d,
    3. com.internet.demo.service.testautowire.BaseServiceImplB@31f295b6,
    4. com.internet.demo.service.testautowire.BaseServiceImplA@1ba467c2,
    5. com.internet.demo.service.testautowire.BaseServiceImplB@63f6bed1]
    6. baseServiceMap={
    7. baseServiceImplA=com.internet.demo.service.testautowire.BaseServiceImplA@511f5b1d,
    8. baseServiceImplB=com.internet.demo.service.testautowire.BaseServiceImplB@31f295b6,
    9. getBaseServiceA=com.internet.demo.service.testautowire.BaseServiceImplA@1ba467c2,
    10. getBaseServiceB=com.internet.demo.service.testautowire.BaseServiceImplB@63f6bed1}

    这种写法往往在使用模板方法设计模式时,根据type取出对应的模板Service时会用到;

    2.4 当前类需要先注册为bean

    在使用@Autowired注入bean的类上,该类忘了加@Controller、@Service、@Component、@Repository等注解,Spring就无法完成自动装配的功能,这种情况应该是小白最常见的错误了,虽然是低级的错误,但很多人包括本人校招入职刚写代码时都干过~

    另外,bean未被scan扫描也是常见的问题,通常情况下,标识为bean的@Controller、@Service、@Component等注解,是需要被扫描的;但是,如果注解扫描的路径配的不对,或者路径范围太小,会导致有些类未被扫描到并注册为bean,导致无法使用@Autowired完成自动装配的功能;如springboot项目中使用@SpringBootApplication注解,可以通过注解的scanBasePackages属性配置bean扫描路径;

    1. @SpringBootApplication(scanBasePackages = "com.internet.demo.*")
    2. public class DemoApplication extends SpringBootServletInitializer {
    3. public static void main(String[] args) {
    4. setEnvironmentInDev();
    5. SpringApplication.run(DemoApplication.class, args);
    6. }
    7. }

    2.5 注入Filter或Listener时失败

    这个点很重要!在开发中,需要自定义一个Filter,并且在里面注入一个Service时,会发现tomcat启动不起来;配置Filter如下:

    1. import javax.servlet.*;
    2. import javax.servlet.FilterConfig;
    3. import java.io.IOException;
    4. public class UserFilter implements Filter {
    5. @Autowired
    6. private UserService userService;
    7. @Override
    8. public void init(FilterConfig filterConfig) throws ServletException {
    9. userService.initUserFilter();
    10. }
    11. @Override
    12. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //... }
    13. @Override
    14. public void destroy() {}
    15. }
    1. @Configuration
    2. public class FilterConfig {
    3. @Bean
    4. public FilterRegistrationBean filterRegistrationBean() {
    5. FilterRegistrationBean bean = new FilterRegistrationBean();
    6. bean.setFilter(new UserFilter());
    7. bean.addUrlPatterns("/*");
    8. return bean;
    9. }
    10. }

    启动后报错NPE:

    1. 2022-06-28 10:47:05.054 ERROR [] [localhost-startStop-1] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/] 182: Exception starting filter [userFilter]
    2. java.lang.NullPointerException: null
    3. at com.internet.demo.service.testautowire.UserFilter.init(UserFilter.java:29) ~[classes/:na]

    分析下过程,我们定义了一个UserFilter类,将其通过Config方式注册成bean,并且在UserFilter中注入了UserService的bean用来执行Filter的初始化,结果是注入的userService对象为null,执行init方法时报了空指针;接下来分析下原因;

    众所周知,web应用启动的顺序是:listener->filter->servlet;

    SpringMVC的启动是在DisptachServlet里面做的,而它是在listener和filter之后执行;如果我们想在listener和filter里面@Autowired某个bean,肯定是不行的,因为filter初始化的时候,此时bean还没有被加载,所以无法自动装配,导致Tomcat无法正常启动;

    实际编码中,如果真的需要在Filter中注入Service来协助执行doFilter逻辑,那有没有办法呢?

    ——方法有!使用WebApplicationContextUtils.getWebApplicationContext获取当前的ApplicationContext,再通过它根据beanName获取到bean实例;如下:

    1. @Slf4j
    2. public class UserFilter implements Filter {
    3. private UserService userService;
    4. @Override
    5. public void init(FilterConfig filterConfig) throws ServletException {
    6. // 手动执行getBean 从applicationContext中取
    7. ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
    8. this.userService = ((UserService) (applicationContext.getBean("userService")));
    9. userService.initUserFilter();
    10. }
    11. @Override
    12. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    13. log.info("UserFilter.doFilter");
    14. // ...
    15. chain.doFilter(request, response);
    16. }
    17. @Override
    18. public void destroy() {
    19. }
    20. }

    至此,tomcat启动成功,Filter#init方法中成功的调用了UserService的bean实例的方法;

    2.6 出现循环依赖

    Spring的bean默认是单例的,如果单例bean使用@Autowired自动装配,大多数情况,能解决循环依赖问题;

    但是如果bean是多例的,Spring解决不了此类循环依赖问题,会导致bean自动装配不了;

    引入AOP下,需要创建原始bean的代理对象,即使bean是单例的,依然可能会出现循环依赖问题;

    关于循环引用,我的上一篇文章《Spring循环依赖&三级缓存【建议收藏】》讲的非常细,个人感觉值得一看;

    3. @Autowired与@Resource的区别

    @Autowired功能虽说非常强大,但是也有些不足之处;比如:比如它是Spring框架下的注解,跟Spring框架强耦合了,如果换成了非Spring的其他框架,功能就会失效;而@Resource是javax包下的,是JSR-250提供的标准,绝大部分框架都支持;

    日常很多使用@Autowired的场景,使用@Resource几乎能平替;接下来,重点看看@Autowired和@Resource的区别;

    3.1 @Autowired与@Resource的区别

    @Autowired@Resource
    装配方式默认按byType自动装配默认byName自动装配
    注解参数注解属性只包含1个参数:required,默认是true,表示是否开启自动注入注解属性有7个参数,其中最重要的两个参数是:name和type
    使用方式如果要使用byName,需要使用@Qualifier一起配合如果指定了name,则用byName自动装配,如果指定了type,则用byType自动装配;也可以同时使用
    使用范围能够用在:构造器、方法、参数、成员变量和注解上能用在:类、成员变量和方法上
    注解来源@Autowired是Spring框架下定义的注解@Resource是JSR-250定义的注解

    接下来主要理一下二者在装配bean上的逻辑;

    3.2 @Autowired的装配顺序

    3.3 @Resource的装配顺序

    1. @Service
    2. public class MyService {
    3. @Resource(type = BaseService.class, name = "baseServiceImplB")
    4. private BaseService baseService;
    5. }

    参考:

    @Autowired和@Resource的区别

  • 相关阅读:
    Flutter高仿微信-第31篇-单聊-表情
    多线程-浅析线程安全
    Go/Golang语言学习实践[回顾]教程13--详解Go语言的词法解析单元
    LQ0266 巧排扑克牌【模拟】
    vue项目打包报错,jenkins发版前端时,提示内存溢出
    pb系统函数:其他函数
    tomcat的介绍与优化
    附录5-vscode常用配置
    Oracle Scheduler中日期表达式和PLSQL表达式的区别
    如何处理 Angular 单页面应用里的 a 标签,避免点击后重新加载整个应用
  • 原文地址:https://blog.csdn.net/minghao0508/article/details/125479542