• Spring系列文章3:基于注解方式依赖注入


    和XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测 到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作,本质上所有操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行

    一、负责声明bean的注解

    1、5个声明bean的注解

    Spring中使用以下5个注解声明一个bean

    @Controller、@Service、@Repository、@Component、@Configuration

    查看@Controller、@Service、@Repository三个注解源码

    1. @Target({ElementType.TYPE})
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Documented
    4. @Component
    5. public @interface Service {
    6. String value() default "";
    7. }

    可以看到只是在@Component注解的基础上起了三个新的名字,对于Spring使用IOC容器管理这些组件来说没有区别。Controller、Service、Repository三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。

    虽然本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。 

    • @Controller:将类标识为控制层组件即用于对 Controller 实现类进行注解
    • @Service:将类标 识为业务层组件即对service层进行注解
    • @Repository:将类标识为持久层组件即用于对 DAO 实现类进行注解
    • @Component:将类标识为普通组件,当前对象不是持久层、业务层、控制层的时候最好使用component
    • @Configuration:如果一个Bean不在我们自己的package管理之内,例如ZoneId,如何创建它?使用@Configuration注解定义一个类,并在方法上标记一个@Bean注解

    在我们使用XML方式管理bean的时候,每个bean都有一个唯一标识便于在其他地方引用。

    使用注解后每个组件仍然应该有一个唯一标识

    默认情况类名首字母小写就是bean的id。例如:UserController类对应的bean的id就是userController。

    可通过标识组件的注解的value属性设置自定义的bean的id @Service("userService")


    2、声明注解如何使用 

    加⼊aop的依

    我们可以看到当加⼊spring-context依赖之后会关联加⼊aop的依赖

    第⼆步在配置⽂件中context命名空间

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans.xsd
    7. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    8. beans>

    在配置⽂件中定要描的包 

     扫描指定包下所有被注解标注的组件

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans.xsd
    7. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    8. <context:component-scan base-package="com.springcode.example.controller">context:component-scan>
    9. <context:component-scan base-package="com.springcode.example.controller,com.springcode.example.entity">context:component-scan>
    10. <context:component-scan base-package="com.springcode.example">context:component-scan>
    11. beans>

     排除指定组件

    1. <context:component-scan base-package="com.demo">
    2. <context:exclude-filter type="annotation"
    3. expression="org.springframework.stereotype.Controller"/>
    4. context:component-scan>

     仅扫描指定组件

    1. <context:component-scan base-package="com.demo" use-default-filters="false">
    2. <context:include-filter type="annotation"
    3. expression="org.springframework.stereotype.Controller"/>
    4. context:component-scan>

    Bean使⽤注解 

    1. @Controller
    2. public class UserController {
    3. }

     编写试程序

    1. public class SpringTest {
    2. @Test
    3. public void testFirst() {
    4. ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
    5. UserController user = applicationContext.getBean("userController", UserController.class);
    6. System.out.println(user);
    7. }
    8. }

    3、方法注解

     可以通过上面注解声明一个类作为一个bean,也可以声明一个方法作为一个bean,如下

    1. @Configuration //也可以用@Component
    2. public class MyBean {
    3. //使用bean注解声明一个方法返回值作为一个bean,bean的名字是方法名
    4. @Bean
    5. public User userInfo(){
    6. return new User();
    7. }
    8. }

    测试

    1. public class SpringTest {
    2. @Test
    3. public void testFirst() {
    4. ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
    5. User user = applicationContext.getBean("userInfo", User.class);
    6. System.out.println(user);
    7. }
    8. }

    4、 @Configuration和@Component区别

    如果一个Bean不在我们自己的package管理之内,例如ZoneId,如何创建它?使用@Configuration注解定义一个类,并在方法上标记一个@Bean注解:

    1. @Configuration
    2. public class AppConfig {
    3. // 创建一个Bean:
    4. @Bean
    5. ZoneId createZoneId() {
    6. return ZoneId.of("Z");
    7. }
    8. }

    查看@Configuration源码本质是使用@Component进行元注解,因此  或者 @ComponentScan 都能处理@Configuration 注解的类。

    Spring @Configuration 和 @Component 区别,一句话概括就是 @Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。

    1. @Configuration
    2. public class MyBeanConfig {
    3. @Bean
    4. public Country country(){
    5. return new Country();
    6. }
    7. @Bean
    8. public UserInfo userInfo(){
    9. return new UserInfo(country());
    10. }
    11. }

    相信大多数人第一次看到上面 userInfo() 中调用 country() 时,会认为这里的 Country 和上面 @Bean 方法返回的 Country 可能不是同一个对象,因此可能会通过下面的方式来替代这种方式:

    @Autowired
    private Country country;

    实际上不需要这么做(后面会给出需要这样做的场景),直接调用 country() 方法返回的是同一个实例。
    但是@Component 注解并没有通过 cglib 来代理@Bean 方法的调用,因此像下面这样配置时,就是两个不同的 country。

    1. @Component
    2. public class MyBeanConfig {
    3. @Bean
    4. public Country country(){
    5. return new Country();
    6. }
    7. @Bean
    8. public UserInfo userInfo(){
    9. return new UserInfo(country());
    10. }
    11. }

    二、负责注入的注解

    上面注解声明后,如何给Bean的属性赋值?Bean属性赋需要⽤到

    @Value@Autowired@Qualifier@Resource

    1、@value

    对于简单属性注入可以使用@value赋值

    1. @Component
    2. public class User {
    3. @Value(value = "tom")
    4. private String name;
    5. @Value("18")
    6. private int age;
    7. public User(){
    8. System.out.println("无参构造被调用");
    9. }
    10. @Override
    11. public String toString() {
    12. return "User{" +
    13. "name='" + name + '\'' +
    14. ", age=" + age +
    15. '}';
    16. }
    17. }

    测试

    1. public class SpringTest {
    2. @Test
    3. public void testFirst() {
    4. ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
    5. User user = applicationContext.getBean("user", User.class);
    6. System.out.println(user);//User{name='tom', age=18}
    7. }
    8. }

     通过代码可以发现我们并有给属性提供setter但仍然可以完成属性赋值。 如果提供settersetter@Value可以完成⼊吗

    1. @Component
    2. public class User {
    3. private String name;
    4. private int age;
    5. @Value(value = "tom")
    6. public void setName(String name) {
    7. this.name = name;
    8. }
    9. @Value("18")
    10. public void setAge(int age) {
    11. this.age = age;
    12. }
    13. public User(){
    14. System.out.println("无参构造被调用");
    15. }
    16. @Override
    17. public String toString() {
    18. return "User{" +
    19. "name='" + name + '\'' +
    20. ", age=" + age +
    21. '}';
    22. }
    23. }

    通过测试可以得知@Value解可以直接使⽤属性上,也可以使⽤setter是可以的。都可以完成属性的赋值。

    为了简化代码以后我们提供setter直接属性使⽤@Value解完成属性赋值。

    是否能够通过完成 

    1. @Component
    2. public class User {
    3. private String name;
    4. private int age;
    5. public User(@Value("tome") String name,@Value("18") int age){
    6. this.name = name;
    7. this.age = age;
    8. }
    9. @Override
    10. public String toString() {
    11. return "User{" +
    12. "name='" + name + '\'' +
    13. ", age=" + age +
    14. '}';
    15. }
    16. }

    通过测试得知@Value解可以出现属性setter以及构的形参可⻅Spring给我们提供了多样化的 

    2、@Autowired与@Qualifier

    使用@Autowired完成引用类型装配,单独使⽤@Autowired注解,默认根据类型装配。【默认是byType】

    源码如下

    1. @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETE
    2. R, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
    3. @Retention(RetentionPolicy.RUNTIME)
    4. @Documented
    5. public @interface Autowired {
    6. boolean required() default true;
    7. }

     源码理解1、该注解可以标注在哪⾥?

    • 构造⽅法上
    • ⽅法上
    • 形参上
    • 属性上
    • 注解上

    源码理解2、该注解有⼀个required属性,默认值是true,表示在注⼊的时候要求被注⼊的Bean必须是 存在的,如果不存在则报错。如果required属性设置为false,表示注⼊的Bean存在或者不存在都没 关系,存在的话就注⼊,不存在的话,也不报错

    1、在属性上使⽤@Autowired注解

    在成员变量上直接标记@Autowired注解即可完成自动装配,如在controller中装配service层bean

    1. @Controller
    2. public class UserController {
    3. @Autowired
    4. private UserService userService;
    5. public void saveUser(){
    6. userService.saveUser();
    7. }
    8. }

    2、可以标记在set方法上

    1. @Service
    2. public class UserService {
    3. private UserDao userDao;
    4. @Autowired
    5. public void setUserDao(UserDao userDao) {
    6. this.userDao = userDao;
    7. }
    8. public void save(){
    9. userDao.insert();
    10. }
    11. }

     

    3、@Autowired注解还可以标记在构造器

    1. @Controller
    2. public class UserController {
    3. private UserService userService;
    4. @Autowired
    5. public UserController(UserService userService){
    6. this.userService = userService;
    7. }
    8. public void saveUser(){
    9. userService.saveUser();
    10. }
    11. }

    4、标注在构造⽅法的形参上 

    1. @Service
    2. public class UserService {
    3. private UserDao userDao;
    4. public UserService(@Autowired UserDao userDao) {
    5. this.userDao = userDao;
    6. }
    7. public void save(){
    8. userDao.insert();
    9. }
    10. }

    并且当有参数的构造⽅法只有⼀个时,@Autowired注解可以省略

    1. @Service
    2. public class UserService {
    3. private UserDao userDao;
    4. public UserService(UserDao userDao) {
    5. this.userDao = userDao;
    6. }
    7. public void save(){
    8. userDao.insert();
    9. }
    10. }

     如果有多个构造⽅法,@Autowired肯定是不能省略的。如下测试会报错

    1. @Service
    2. public class UserService {
    3. private UserDao userDao;
    4. public UserService(UserDao userDao) {
    5. this.userDao = userDao;
    6. }
    7. public UserService(){
    8. }
    9. public void save(){
    10. userDao.insert();
    11. }
    12. }

    @Autowired注解默认是byType进⾏注⼊的,也就是说根据类型注⼊的,如果以上程序中,UserDao接⼝ 还有另外⼀个实现类,会出现问题吗?

    可以测试是不能装配的,提示UserDao这个Bean的数量⼤于1. 怎么解决这个问题呢?当然要byName,根据名称进⾏装配了。 @Autowired注解和@Qualifier注解联合起来才可以根据名称进⾏装配,在@Qualifier注解中指定Bean名 称。

    1. @Service
    2. public class UserService {
    3. private UserDao userDao;
    4. @Autowired
    5. @Qualifier("userDaoTwo") // 这个是bean的名字。
    6. public void setUserDao(UserDao userDao) {
    7. this.userDao = userDao;
    8. }
    9. public void save(){
    10. userDao.insert();
    11. }
    12. }

     @Autowired工作流程

    首先根据所需要的组件类型到IOC容器中查找,唯一直接返回,存在多个

            没有@Qualifier注解:根据@Autowired标记位置成员变量的变量名作为bean的id进行 匹配 能够找到:执行装配 找不到:装配失败

            使用@Qualifier注解:根据@Qualifier注解中指定的名称作为bean的id进行匹配 能够找到:执行装配 找不到:装配失败 

    @Autowired中有属性required,默认值为true,因此在自动装配无法找到相应的bean时,会装 配失败 可以将属性required的值设置为true,则表示能装就装,装不上就不装,此时自动装配的属性为 默认值

    3、@Resource

    @Resource注解也可以完成⾮简单类型注⼊。那它和@Autowired注解有什么区别?

    @Resource注解是JDK扩展包中的,也就是说属于JDK的⼀部分。所以该注解是标准注解,更加具 有通⽤性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)

    • @Autowired注解是Spring框架⾃⼰的。
    • @Resource注解默认根据名称装配byName,未指定name时,使⽤属性名作为name。通过name 找不到的话会⾃动启动通过类型byType装配。
    • @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解⼀起 ⽤。
    • @Resource注解⽤在属性上、setter⽅法上。
    • @Autowired注解⽤在属性上、setter⽅法上、构造⽅法上、构造⽅法参数上。
    • @Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引⼊以下依赖:【如果是JDK8的话不需 要额外引⼊依赖。⾼于JDK11或低于JDK8需要引⼊以下依赖。】
    1. jakarta.annotation
    2. jakarta.annotation-api
    3. 2.1.1

    注意:如果你⽤Spring6,要知道Spring6不再⽀持JavaEE,它⽀持的是JakartaEE9。(Oracle 把JavaEE贡献给Apache了,Apache把JavaEE的名字改成JakartaEE了,⼤家之前所接触的所有的 javax.* 包名统⼀修改为 jakarta.*包名了。)

    如果你是spring5-版本请使⽤这个依赖

    1. javax.annotation
    2. javax.annotation-api
    3. 1.3.2

     @Resource注解的源码如下:

    使用

    1. @Service
    2. public class UserService {
    3. @Resource
    4. private UserDao userDao;
    5. public void save(){
    6. userDao.insert();
    7. }
    8. }

     @Resource注解也可以使⽤在setter⽅法上

    1. @Service
    2. public class UserService {
    3. private UserDao userDao;
    4. @Resource
    5. public void setUserDao(UserDao userDao) {
    6. this.userDao = userDao;
    7. }
    8. public void save(){
    9. userDao.insert();
    10. }
    11. }

    注意这个setter⽅法的⽅法名,setUserDao去掉set之后,将⾸字⺟变⼩写userDao,userDao就是name

    当然,也可以指定name:

    1. @Service
    2. public class UserService {
    3. private UserDao userDao;
    4. @Resource(name = "userDaoForMySQL")
    5. public void setUserDao(UserDao userDao) {
    6. this.userDao = userDao;
    7. }
    8. public void save(){
    9. userDao.insert();
    10. }
    11. }

     ⼀句话总结@Resource注解:默认byName注⼊,没有指定name时把属性名当做name,根据name找不 到时,才会byType注⼊。byType注⼊时,某种类型的Bean只能有⼀个。

    三、全注解开发

    所谓的全注解开发就是不再使⽤spring配置⽂件了。写⼀个配置类来代替配置⽂件。

    1. @Configuration
    2. @ComponentScan({"com.spring6demo.spring6.dao", "com.spring6demo.service"})
    3. public class Spring6Configuration {
    4. }

    编写测试程序:不再new ClassPathXmlApplicationContext()对象了。

    1. @Test
    2. public void testNoXml(){
    3. ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
    4. UserService userService = applicationContext.getBean("userService", Use
    5. rService.class);
    6. userService.save();
    7. }

    四、注解和xml的对比

    实际开发中以注解为主, xml为辅

    五、IOC(依赖注入)使用总结

    IOC解决的是业务逻辑对象之间的耦合关系,也就是service和dao之家的解耦合

    spring容器适合管理对象

    • service层、dao层、controller对象、工具类对象

    不适合管理管理对象

    • 实体类对象
    • servle、listener、filter等web中的对象,他们是tomcat创建和管理的
  • 相关阅读:
    人工智能:科技魔法赋予生活新意
    java.lang.IllegalStateException: Failed to load ApplicationContext
    阿里最新秋招面经,腾讯 / 美团 / 字节 1 万道 Java 中高级面试题
    Vue模板语法下集(03)
    如何在React中监听键盘事件
    数据结构之初始泛型
    记一次python 正则表达式
    2.如何创建线程
    【暑期每日一题】洛谷 P2026 求一次函数解析式
    11+ chrome高级调试技巧,学会效率直接提升666%
  • 原文地址:https://blog.csdn.net/qq_34491508/article/details/132688526