zoukankan      html  css  js  c++  java
  • Spring5【五】Bean 的自动装配及注解开发

    7、bean 的自动装配

    • 自动装配是使用 Spring 满足 bean 依赖的一种方式

    • Spring 会在应用上下文中为某个 bean 寻找其依赖的 bean,即在上下文自动寻找并自动给 bean 装配属性

    由于在手动配置 xml 过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。采用自动装配将避免这些错误,并且使配置简单化。

    在 Spring 中有三种装配方式:

    1. 在 xml 中显式的配置

    2. 在 Java 中显式的配置

    3. 隐式的自动装配 bean【重要】

    Spring 的自动装配需要从两个角度来实现,或者说是两个操作:

    1. 组件扫描(component scanning):Spring 会自动发现应用上下文中所创建的 bean;
    2. 自动装配(autowiring):Spring 自动满足 bean 之间的依赖,也就是我们说的 IoC/DI;

    组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。

    推荐不使用自动装配xml配置,而使用注解。

    7.1 测试

    环境搭建:一个人有两个宠物

    public class Dog {
        public void shout(){
            System.out.println("汪~");
        }
    }
    
    public class Cat {
        public void shout(){
            System.out.println("喵~");
        }
    }
    
    public class People {
        private Cat cat;
        private Dog dog;
        private String name;
        
        set/get/toString...
    }
    
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="cat" class="com.song.pojo.Cat"/>    
        <bean id="dog" class="com.song.pojo.Dog"/>
        
        <bean id="people" class="com.song.pojo.People">
            <property name="name" value="张三"/>
            <property name="cat" ref="cat"/>
            <property name="dog" ref="dog"/>
        </bean>
    </beans>
    
    public class MyTest {
        @Test
        public void test(){
            ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
            People people = context.getBean("people", People.class);
            people.getCat().shout(); // 喵~
            people.getDog().shout(); // 汪~
        }
    }
    结果正常输出
    

    7.2 byName 自动装配

    autowire byName (按名称自动装配)

    修改bean配置,增加一个属性 autowire="byName",使用 ref 的 property 可以省略:

    <bean id="cat" class="com.song.pojo.Cat"/>
    <bean id="dog" class="com.song.pojo.Dog"/>
    
    <!--
        byName: 会自动在容器上下文中查找,和自己对象 set 方法后面的值对应的 beanid
    -->
    <bean id="people" class="com.song.pojo.People" autowire="byName">
        <property name="name" value="张三"/>
        <!--<property name="cat" ref="cat"/>-->
        <!--<property name="dog" ref="dog"/>-->
    </bean>
    

    结论:

    当一个bean节点带有 autowire byName 的属性时:

    1. 将查找其类中所有的 set 方法名,例如 setCat,获得将 set 去掉并且首字母小写的字符串,即 cat。
    2. 去 spring 容器中寻找是否有此字符串名称 id 的对象。
    3. 如果有,就取出注入;如果没有,就报空指针异常。

    即,byName 必须保证 bean 的 id 唯一,并且这个 bean 要和自动注入的属性的 setXxx 方法的名字 xxx(将 set 去掉并且首字母小写) 一致。

    7.3 byType 自动装配

    autowire byType (按类型自动装配)

    修改bean配置,增加一个属性 autowire="byType":

    <bean id="cat" class="com.song.pojo.Cat"/>
    <bean class="com.song.pojo.Dog"/>
    
    <!--
        byType:会自动在容器上下文中查找,和自己对象 属性类型相同的 bean
    -->
    <bean id="people" class="com.song.pojo.People" autowire="byType">
        <property name="name" value="张三"/>
        <!--<property name="cat" ref="cat"/>-->
        <!--<property name="dog" ref="dog"/>-->
    </bean>
    

    结论:

    • byType 必须保证所有 bean 的 class 唯一,并且这个 bean 需要和自动注入的属性的类型一致。

    • byType 必须保证类型全局唯一。即:同一类型的对象,在spring容器中唯一,如果不唯一,会报不唯一的异常 NoUniqueBeanDefinitionException

    注意:对于 byName 自动装配,自动注入的属性的 bean 的 id 是可以省略的。

    7.4 使用注解实现自动装配

    The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML.

    jdk1.5 开始支持注解,spring2.5 开始全面支持注解。

    使用注解的准备工作:

    1. 导入约束:context 约束

    2. 配置注解的支持:<context:annotation-config/>

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:context="http://www.springframework.org/schema/context"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
              https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context
              https://www.springframework.org/schema/context/spring-context.xsd">
      
          <context:annotation-config/>
      </beans>
      

    @Autowried

    直接在属性上使用即可,也可以在 set 方式上使用。

    使用 Autowired 可以不用编写 set 方法,前提是这个自动装配的属性在 IOC(Spring)容器中存在,且符合类型 byType。

    测试

    1. 在属性上使用 @Autowired 注解:

      public class People {
          @Autowired
          @Qualifier(value = "cat1")
          private Cat cat;
          @Autowired
          private Dog dog;
          private String name;
          
          getter方法……
      }
      
    2. 配置文件内容:

      <!--开启注解支持-->
      <context:annotation-config/>
      
      <bean id="cat" class="com.song.pojo.Cat"/>
      <bean id="dog" class="com.song.pojo.Dog"/>
      <bean id="people" class="com.song.pojo.People"/>
      
    3. 测试结果:成功输出

    总结

    @Autowired 首先按类型(byType)自动装配;如果同一类型有多个 bean 存在,再按照属性名(byName)进行装配,即 bean 的 id 与属性名一致;如果属性名装配不成功,可以使用 @Qualifier(value = “xxx”)装配与 id 一致的属性名。

    扩展

    Autowired 源码:

    public @interface Autowired {
     boolean required() default true;
    }
    

    关于对象为空的问题

    • Autowired 的 required 属性

      required 属性默认为 true,对象必须存对象,不能为 null;如果为 false,则这个对象可以为 null。

      // 如果显式定义了 Autowired 的 required 属性为false,说明这个对象可以为 null,否则不允许为空
      @Autowired(required = false)
      
    • @Nullable

      字段标记了这个注解,说明这个字段可以为 null

    @Qualifier

    如果 @Autowired 自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候,可以使用@Qualifier(value = "xxx") 去配置 @Autowired 的使用,指定一个唯一的 bean 对象注入(byName)。

    注意:@Qualifier(value = "xxx") 不能单独使用

    测试

    1. 配置文件修改内容,保证类型存在,但名字不为类的默认名字!

      <!--开启注解支持-->
      <context:annotation-config/>
      <bean id="cat1" class="com.song.pojo.Cat"/>
      <bean id="cat111" class="com.song.pojo.Cat"/>
      <bean id="dog2" class="com.song.pojo.Dog"/>
      <bean id="dog222" class="com.song.pojo.Dog"/>
      <bean id="people" class="com.song.pojo.People"/>
      
    2. 没有加 @Qualifier 测试,直接报错

    3. 在属性上添加 @Qualifier 注解

      public class People {
          @Autowired(required = false)
          @Qualifier(value = "cat111")
          private Cat cat;
          @Autowired
          @Qualifier(value = "dog222")
          private Dog dog;
          private String name;
      }
      
    4. 测试结果:成功输出

    @Resource

    @Resource 如果有指定的 name 属性,先按该属性进行 byName 方式查找装配;其次再进行默认的 byName 方式进行装配;如果以上都不成功,则按 byType 的方式自动装配;都不成功,则报异常。

    public class People {
        @Resource(name = "cat2")
        private Cat cat;
        @Resource
        private Dog dog;
        private String name;
    }
    

    小结:

    @Autowired 和 @Resource 异同:

    • 作用相同都是用注解方式注入对象,都是用来自动装配 bean 的,都可以放在属性字段,或写在 setter 方法上。

    • @Autowired (属于spring规范)默认通过 byType 的方式实现,而且必须要求这个对象存在(除非制定了 required 属性为 false),类型重复再通过 id 与属性名匹配(byName)找,都不合适则结合 @Qualifier 注解使用名字装配。

    • @Resource(属于 J2EE 复返) 默认通过 byName 的方式实现,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在 setter 方法上默认取属性名进行装配。如果找不到与名称匹配的bean时,则通过 byType 实现自动装配。如果两个都找不到的情况下就报错。注意,如果 name 属性一旦指定,就只会按照名称进行装配。

    • 执行顺序不同:@Autowired 先 byType 再 byName;@Resource 先 byName 再 byType。


    8、使用注解开发

    • 在 spring4 之后,想要使用注解形式,必须得要引入 aop 的包。

    • 使用注解需要导入 context 约束,增加注解的支持。

    • 必须指定要扫描的包,使这个包下的注解生效。扫描是为了类上的注解。

    Spring 的配置文件 applicationContext.xml :

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--指定要扫描的包,这个包下的注解就会生效-->
        <context:component-scan base-package="com.song"/>
        <context:annotation-config/>
    
    </beans>
    

    8.1 Bean 的实现(@Component)

    在指定包下编写类,增加注解

    // 等价于配置文件中 <bean id="user" class="com.song.pojo.User"/>
    @Component  // 组件
    public class User {
        public String name = "张三";
    }
    

    测试:

    public void test(){
       ApplicationContext Context =
           new ClassPathXmlApplicationContext("applicationContext.xml");
       User user = (User) Context.getBean("user");
       System.out.println(user.name); // 张三
    }
    

    注意:@Component("xxx") 可以指定 id

    • 如果是 @Component,即没有指定 id,那么这个 bean 的 id 就是当前类的首字母小写的字符串
    • 如果是 @Component("xxx"),即指定了 id,那么 getBean 时只能使用这个 id,即Context.getBean("xxx")

    8.2 属性注入(@Value)

    可以在属性名上添加注解,也可以在setter方法上添加。

    @Component
    public class User {
        // 相当于 <property name="name" value="张三"/>
        //@Value("张三")
        public String name;
    
        @Value("张三")
        public void setName(String name) {
            this.name = name;
        }
    }
    

    8.3 衍生的注解

    @Component 的三个衍生注解,在 web 开发中,会按照 mvc 三层架构分层。

    • dao 层【@Repository】
    • service 层【@Service】
    • web 层【@Controller】

    这四个注解功能都是一样的,都是代表将某个类注册到 Spring 中,装配 bean。

    8.4 自动装配

    前面已经学过。

    8.5 作用域(@Scope)

    • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
    • prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
    @Component
    @Scope("prototype")
    public class User {
        //@Value("张三")
        public String name;
    
        @Value("张三")
        public void setName(String name) {
            this.name = name;
        }
    }
    

    8.6 小结

    xml 和注解比较

    • xml 更加万能,适用于任何场合,结构清晰,维护简单方便
    • 注解,不是自己提供的类使用不了,维护相对复杂,开发简单方便

    xml 与注解整合开发

    • xml 用来管理 bean
    • 注解只负责完成属性的注入
    • 使用时需要注意:必须让注解生效,需要开启注解的支持 <context:annotation-config/>

    9、使用 java 的方式配置 Spring

    完全不使用 Spring 的 xml 配置,全权交给 java来做。

    javaConfig 是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 之后它成为了一个核心功能。这种纯 java 的配置方式,在 SpringBoot 中随处可见。

    测试:

    1. 实体类

      // 注解意思:这个类被 Spring 接管了,注册到了容器中
      @Component // 其实不加这个注解也可以测试成功
      public class User {
          private String name;
      
          @Value("zhangsan") //属性注入值
          public void setName(String name) {
              this.name = name;
          }
          
          getter/toString……
      }
      
    2. 新建一个 config 配置包,编写一个 javaConfig 配置类

      //这个也会被 Spring 容器托管,注册到容器中,因为它本来就是一个 @Component
      // @Configuration 代表这是一个配置类,和之前的 beans.xml 一样
      @Configuration
      @ComponentScan("com.song.pojo")
      @Import(MyConfig2.class) //导入合并其他配置类,类似于配置文件中的 import 标签
      public class MyConfig {
      
          // 通过方法注册一个 bean,相当于之前写的一个 bean 标签
          // 方法的名字,相当于 bean 标签中的 id 属性
          // 方法的返回值,相当于 bean 标签中的 class 属性
          @Bean
          public User getUser(){
              return new User(); // 返回要注入 bean 的对象
          }
      }
      
      @Configuration  //代表这是一个配置类
      public class MyConfig2 {
      }
      
    3. 测试类

      public class MyTest {
          public static void main(String[] args) {
              // 如果完全使用了配置类方式去做,就只能通过 AnnotationConfig 上下文来获取容器,通过配置类的class对象加载
              ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
              User getUser = (User) context.getBean("getUser"); // 参数为方法名       
              System.out.println(getUser.getName()); // zhangsan
              
              User user = (User) context.getBean("user"); // 参数为 类名的首字母小写
              System.out.println(user.getName()); // zhangsan
              
              System.out.println(getUser == user);//fasle
          }
      }
      

    思考:

    发现以下两种方式的注解操作都可以正确输出

    1. 只在 MyConfig 类中加 @Bean 注解,context.getBean("getUser") 参数需要是 @Bean 注解时的方法名;

    2. 不加 @Bean 注解,在 User 类中加 @Component 注解,在 MyConfig 类中加 @ComponentScan("xxx.xxx.xxx") 注解,context.getBean("user") 参数需要是类名的首字母小写字符串。

    3. 如上面的测试案例,将 @Bean 和 @Component 注解都加上,通过 getBean 得到的对象是两个不同的对象,也就是说,在 Spring 容器中存在着该类的两个对象。

  • 相关阅读:
    [DB] 数据库的连接
    JS leetcode 翻转字符串里的单词 题解分析
    JS leetcode 拥有最多糖果的孩子 题解分析,六一快乐。
    JS leetcode 搜索插入位置 题解分析
    JS leetcode 杨辉三角Ⅱ 题解分析
    JS leetcode 寻找数组的中心索引 题解分析
    JS leetcode 移除元素 题解分析
    JS leetcode 最大连续1的个数 题解分析
    JS leetcode 两数之和 II
    JS leetcode 反转字符串 题解分析
  • 原文地址:https://www.cnblogs.com/Songzw/p/13170118.html
Copyright © 2011-2022 走看看