【Spring基础系列】基于注解装配Bean

简介: 目前学习java也有一段时间,比较不适应的就是java的各种注解,因为它里面包含了太多的东西,然后使用的姿势也各不相同,今天就简单做个总结和记录,扫一次盲后,后续使用就畅通无阻。

image.png

本文主要讲解Spring通过注解装配Bean的常用方式,包括@Component、@Repository、@Service、@Controller、@Autowired、@Resource和@Qualifier。


前言


目前学习java也有一段时间,比较不适应的就是java的各种注解,因为它里面包含了太多的东西,然后使用的姿势也各不相同,今天就简单做个总结和记录,扫一次盲后,后续使用就畅通无阻。


什么是注解


传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事物,这么做有两个缺点:

  1. 如果所有的内容都配置在.xml文件中,那么.xml文件将会十分庞大;如果按需求分开.xml文件,那么.xml文件又会非常多,总之这将导致配置文件的可读性与可维护性变得很低。
  2. 在开发中在.java文件和.xml文件之间不断切换,是一件麻烦的事,同时这种思维上的不连贯也会降低开发的效率。

为了解决这两个问题,Spring引入了注解,通过"@XXX"的方式,让注解与Java Bean紧密结合,大大减少了配置文件的体积,又增加了Java Bean的可读性与内聚性。


常用注解


下面直接copy语言中文网的解释:

  • @Component:可以使用此注解描述 Spring 中的 Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),并且可以作用在任何层次。使用时只需将该注解标注在相应类上即可。
  • @Repository:用于将数据访问层(DAO层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
  • @Service:通常作用在业务层(Service层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
  • @Controller:通常作用在控制层(如 Struts2 的 Action),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
  • @Autowired:用于对 Bean 的属性变量、属性的 Set 方法及构造函数进行标注,配合对应的注解处理器完成 Bean 的自动配置工作。默认按照 Bean 的类型进行装配。
  • @Resource:其作用与 Autowired 一样。其区别在于 @Autowired 默认按照 Bean 类型装配,而 @Resource 默认按照 Bean 实例名称进行装配。
  • @Qualifier:与 @Autowired 注解配合使用,会将默认的按 Bean 类型装配修改为按 Bean 的实例名称装配,Bean 的实例名称由 @Qualifier 注解的参数指定。


不用注解


先看一个不使用注解的Spring示例,在这个示例的基础上,改成注解版本的,这样也能看出使用与不使用注解之间的区别,先定义猫和狗:

@ToString
public class Cat {
    private String catName = "罗小黑";
}
@ToString
public class Dog {
    private String dogName = "旺财";
}


再定义宠物:

@Data
public class Pets {
    private Cat cat;
    private Dog dog;
    public static void main(String args[]) {
        ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");
        Pets pets=context.getBean("pets",Pets.class);
        System.out.println(pets.toString());
    }
}
// 输出:
// Pets(cat=Cat(catName=罗小黑), dog=Dog(dogName=旺财))


注意这里我使用了@Data注解,主要是使用里面的getxxx()和setxxx()方法,因为这个Spring框架在进行注解注入时需要。由于没有使用注解,我们需要在xml文件进行如下配置:

<bean id="pets" class="com.java.annotation.spring.bean.test1.Pets" >
    <property name="cat" ref="cat" />
    <property name="dog" ref="dog" />
</bean>
<bean id="cat" class="com.java.annotation.spring.bean.test1.Cat" />
<bean id="dog" class="com.java.annotation.spring.bean.test1.Dog" />


成员变量使用注解


这里我们使用@Autowired注解,当然也可以使用@Resource,后面再讲他们的区别,引入注解可以简化xml配置文件,如下:

<bean id="pets" class="com.java.annotation.spring.bean.tes1.Pets" />
<bean id="cat" class="com.java.annotation.spring.bean.test1.Cat" />
<bean id="dog" class="com.java.annotation.spring.bean.test1.Dog" />


这里我们只对Cat和Dog加上@Autowired注解,就可以根据变量的类型,自动找到相应的类,然后进行加载:

@Data
public class Pets {
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    public static void main(String args[]) {
      // main()省略同上
      // 输出:Pets(cat=Cat(catName=罗小黑), dog=Dog(dogName=旺财))
    }
}


类使用注解


我们可以给Dog和Cat类加上@Service注解,就可以将xml配置简化如下:

<bean id="pets" class="com.java.annotation.spring.bean.tes1.Pets" />


下面是给类加@Service注解的正确姿势,当然你也可以使用@Component、@Repository、@Service、@Controller,区别不大,只是使用不太规范。

@Data
@Service
public class Cat {
    private String catName = "罗小黑";
}
@Data
@Service
public class Dog {
    private String dogName = "旺财";
}


然后Pets和上面示例保持不变,当然你可以把Pets在xml的配置给去掉,只需要在Pets使用@Service注解,原理同Cat和Dog,类似于:

@Data
@Service
public class Pets {
// 内部代码省略
}

这里有个巨大的坑,坑了我一个多小时,就是需要在配置文件中配置注解扫描的代码路径,否则程序运行时,会提示找不到该类的注解。


比如我的项目目录是放在"com.java.annotation"下面,所以你需要将该路径配置进去:

<!--使用context命名空间,通知spring扫描指定目录,进行注解的解析-->
<context:component-scan base-package="com.java.annotation"/>


接口注入


上面的示例比较简单,我们来个稍微复杂一点的,我们可以给Dog和Cat抽象一个接口Animal(接口不是我这样使用的,我只是给个示例),如下:

public interface Animal {
    public String food = null;
}


下面是Cat和Dog:

@Data
@Service
public class Cat implements Animal {
    private String catName = "罗小黑";
}
@Data
@Service
public class Dog implements Animal {
    private String dogName = "旺财";
}

这个有个重点需要关注的地方,就是通过@Service进行注入,主要做了2件事情:

  • 声明Pets.java是一个bean,这点很重要,因为Pets.java是一个bean,其他的类才可以使用@Autowired将Pets作为一个成员变量自动注入;
  • Pets.java在bean中的id是"pets",即类名且首字母小写。


用接口名定义变量

这里使用姿势其实很多,我们的成员变量定义可以使用接口Animal,但是变量名必须为dog和cat,因为Spring对Dog和Cat的注入名为dog和cat:

@Data
public class Pets {
    @Autowired
    private Animal dog;
    @Autowired
    private Animal cat;
    // main()省略同上
    // 输出:Pets(cat=Cat(catName=罗小黑), dog=Dog(dogName=旺财))
}


可能有同学会说“我才不信”,这个是我到真实环境跑过,如果我将变量名dog改为dog1,如下:

@Data
public class Pets {
    @Autowired
    private Animal dog1; // 错误使用,会报错!!!
    @Autowired 
    private Animal cat;
}


会输入如下错误,也就是提示找不到dog1对应注入的类:

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'pets': Unsatisfied dependency expressed through field 'dog1'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.java.annotation.spring.bean.test4.Animal' available: expected single matching bean but found 2: cat,dog
 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
 at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
 at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
 at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
 at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
 at com.java.annotation.spring.bean.test4.Pets.main(Pets.java:17)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.java.annotation.spring.bean.test4.Animal' available: expected single matching bean but found 2: cat,dog
 at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:173)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1116)
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
 at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)

不过上述方式不建议使用,一方面不容易理解,另一方面容易给别人留坑!如果一定需要用接口定义变量,可以使用@Qualifier注解。


引入@Qualifier注解

@Qualifier与@Autowired注解配合使用,会将默认的按Bean类型装配修改为按Bean的实例名称装配,Bean的实例名称由@Qualifier注解的参数指定。

@Data
public class Pets {
    @Autowired
    @Qualifier("dog")
    private Animal dog1;
    @Autowired
    private Animal cat;
    // main()省略同上
    // 输出:Pets(cat=Cat(catName=罗小黑), dog=Dog(dogName=旺财))
}

对于这种方式,我感觉用起来也别扭,最好的方式,可以直接用类名定义变量。


用类名定义变量

@Data
public class Pets {
    @Autowired
    private Dog dog1;
    @Autowired
    private Cat cat1;
    // main()省略同上
    // 输出:Pets(cat=Cat(catName=罗小黑), dog=Dog(dogName=旺财))
}

这个就很直观了,我用Dog指定dog1,用Cat指定cat1,就不用担心变量名不对,是不是很方便。


自定义注入名

如果你想自己指定注入名,那么就需要使用@Resource注解,只需要在Dog和Cat类作如下修改:

@Data
@Service("miniCat")
public class Cat implements Animal {
    private String catName = "罗小黑";
}


对于Cat我们的注入名为miniCat,所以我们可以指定miniCat,如下:

@Data
public class Pets {
    @Resource
    private Dog dog1;
    @Resource(name = "miniCat") // 只能是miniCat,如果为其它名字会报错
    //@Resource(type = Cat.class)  // 这种方式也可以
    //@Resource  // 这种方式也可以
    private Cat cat1;
    // main()省略同上
    // 输出:Pets(cat=Cat(catName=罗小黑), dog=Dog(dogName=旺财))
}

这块使用的姿势应该还有,我就不列举,无论如何变化,只需要知道@Resource的装配顺序,以及@Resource和@Autowired的区别,就很好理解了。


@Resource vs @Autowired

@Resource的装配顺序如下:

  1. @Resource后面没有任何内容,默认通过name属性去匹配bean,找不到再按type去匹配;
  2. 指定了name或者type则根据指定的类型去匹配bean;
  3. 指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都将报错。

然后,区分一下@Autowired和@Resource两个注解的区别:

  1. @Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配
  2. @Autowired是Spring的注解,@Resource是J2EE的注解,这个看一下导入注解的时候这两个注解的包名就一清二楚了

Spring属于第三方的,J2EE是Java自己的东西,因此,建议使用@Resource注解,以减少代码和Spring之间的耦合。

总结一下:

@Resource根据name和type,是先Name后Type,@Autowired是Type,一般情况下我们最好使用@Resource。


总结


文中详细讲解了@Service、@Autowired、@Resource和@Qualifier的用法,其中重点讲述了@Autowired、@Resource的区别,那么对于@Component、@Repository、@Controller这3个注解,文中也就开头提到,这3个注解其实和@Service一个含义,只是我们在写代码时,会进行分层,比如DAO层、Service层、Action层,分别可以用@Repository、@Service、@Controller表示,其实也就字面含义不一样,效果其实是一样的,然后@Component可以作用在任何层次。所以看起来有7个注解,其实你可以理解只有4个。

相关文章
|
3月前
|
缓存 监控 Java
SpringBoot @Scheduled 注解详解
使用`@Scheduled`注解实现方法周期性执行,支持固定间隔、延迟或Cron表达式触发,基于Spring Task,适用于日志清理、数据同步等定时任务场景。需启用`@EnableScheduling`,注意线程阻塞与分布式重复问题,推荐结合`@Async`异步处理,提升任务调度效率。
570 128
|
3月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
436 0
|
3月前
|
Java 测试技术 API
将 Spring 的 @Embedded 和 @Embeddable 注解与 JPA 结合使用的指南
Spring的@Embedded和@Embeddable注解简化了JPA中复杂对象的管理,允许将对象直接嵌入实体,减少冗余表与连接操作,提升数据库设计效率。本文详解其用法、优势及适用场景。
305 126
|
4月前
|
XML JSON Java
Spring框架中常见注解的使用规则与最佳实践
本文介绍了Spring框架中常见注解的使用规则与最佳实践,重点对比了URL参数与表单参数的区别,并详细说明了@RequestParam、@PathVariable、@RequestBody等注解的应用场景。同时通过表格和案例分析,帮助开发者正确选择参数绑定方式,避免常见误区,提升代码的可读性与安全性。
|
2月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
401 2
|
3月前
|
Java 测试技术 数据库
使用Spring的@Retryable注解进行自动重试
在现代软件开发中,容错性和弹性至关重要。Spring框架提供的`@Retryable`注解为处理瞬时故障提供了一种声明式、可配置的重试机制,使开发者能够以简洁的方式增强应用的自我恢复能力。本文深入解析了`@Retryable`的使用方法及其参数配置,并结合`@Recover`实现失败回退策略,帮助构建更健壮、可靠的应用程序。
391 1
使用Spring的@Retryable注解进行自动重试
|
3月前
|
XML Java 数据格式
常用SpringBoot注解汇总与用法说明
这些注解的使用和组合是Spring Boot快速开发和微服务实现的基础,通过它们,可以有效地指导Spring容器进行类发现、自动装配、配置、代理和管理等核心功能。开发者应当根据项目实际需求,运用这些注解来优化代码结构和服务逻辑。
302 12
|
3月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
191 0
探索Spring Boot的@Conditional注解的上下文配置
|
3月前
|
智能设计 Java 测试技术
Spring中最大化@Lazy注解,实现资源高效利用
本文深入探讨了 Spring 框架中的 `@Lazy` 注解,介绍了其在资源管理和性能优化中的作用。通过延迟初始化 Bean,`@Lazy` 可显著提升应用启动速度,合理利用系统资源,并增强对 Bean 生命周期的控制。文章还分析了 `@Lazy` 的工作机制、使用场景、最佳实践以及常见陷阱与解决方案,帮助开发者更高效地构建可扩展、高性能的 Spring 应用程序。
142 0
Spring中最大化@Lazy注解,实现资源高效利用