Spring-定义、功能、Ioc/DI、Junit、作用域(singleson、prototype)、惰性初始化、依赖注入(@Bean、组件扫描)、Ioc和DI的解耦

0.对前面学习的总结

  前面初步使用了Spring MVC 和 Spring Boot,大概总结如下:

  • Spring MVC:

    Controller 和 java与页面的交互,都是Spring MVC的功能

    M: Model模型,狭义上指实体类, 广义上指除了控制器之外的其它java类

    V: View视图,html页面, 即显示给用户看到的界面

    C: Controller控制器,Controller类,视图与java进行数据交互的

    Spring MVC框架简化了V和C之间的数据交互过程

  • Spring Boot:

    boot : 启动

    支持Spring Boot框架的项目内置了很多配置,这些配置都是大部分框架的常规配置,当我们使用这些框架时,就免去了那些繁琐的配置。除了这些配置之外,还包含了一些约定:例如static文件夹存放静态资源等。

1.什么是Spring?

  Spring本身的意思是春天、泉水,Spring是一个基础框架,Spring MVC、Spring Boot、Spring Data、Spring Security、Spring Validation、Spring Cloud都是Spring框架衍生出来的以及众多以Spring开头的框架的基础。它的出现是现在java能够长盛不衰的主要原因。因为它提供了一个java开发的生态环境,几乎市面上任何通用的常用需求,Spring都有解决方案。

2.Spring框架的功能

  • Ioc\DI (主要讲解内容):Ioc是一种思想,DI是基于Ioc的操作。

  • Aop (最后讲解内容)

2.1 什么是Ioc?

  • Ioc(Inversion of Control) 翻译为:控制反转,正常的控制称之为主动控制

  • 主动控制: 我们编写的程序主动控制对象组件的产生,再编写对象组件互相调用,以完成程序功能

  • 控制反转: 我们编写的程序需要的对象组件保存在外部容器中,需要时从容器中获取,之后调用方法实现功能

2.2 Spring 实现Ioc

(1)首先创建一个Maven项目,默认配置即可

  注意:Maven项目不同于普通项目,因为里面包含pom.xml文件,可以添加组件依赖,方便管理依赖,而普通项目需要单独导入jar包,当jar包较多时,不方便管理,使用不便。Spring Boot内置Maven!Maven中内置Spring MVC!

(2)项目的pom.xml中添加依赖

 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
     <groupId>org.example</groupId>
     <artifactId>SpringDemo</artifactId>
     <version>1.0-SNAPSHOT</version>
     <dependencies>
         <!--Spring框架:添加Spring Context依赖-->
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-context</artifactId>
             <version>5.2.2.RELEASE</version>
         </dependency>
 
        <!--JUnit单元测试框架-->
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.13</version>
         </dependency>
     </dependencies>
 
 </project>

注意:先添加依赖(代码正确),再刷新Maven,等待下载完毕即可!

maven下载资源失败的解决方案:

1.检查setting.xml文件的位置是否设置了阿里\华为的镜像

2.退出所有防火墙或电脑管家等占用电脑资源的程序

3.更换网络再试(手机热点,手机共享网络给电脑)

4.安装maven(可以从苍老师网站下载)

5.删除.m2文件夹下的repository文件夹,再导入依赖,刷新Maven

(3)详细步骤

在src/main/java路径下创建一个包hello,包下创建类Stu,类中编写代码如下:

 package hello;
 
 /**
  * 1.实体类Stu
  */
 public class Stu {
     private String name;
     private Integer age;
 
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 
     public Integer getAge() {
         return age;
    }
 
     public void setAge(Integer age) {
         this.age = age;
    }
 
     @Override
     public String toString() {
         return "Stu{" +
                 "name='" + name + '\'' +
                 ", age=" + age +
                 '}';
    }
 }

包下编写配置类Config,常见的将对象保存到Spring容器中的方法有两个

我们先介绍第一个:@Bean方式,Bean是对象的意思。

 package hello;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 /**
  * 2.Spring配置类
  * 配置Spring容器中内容
  */
 //标准情况下,Spring配置类要添加下面的注解:@Configuration
 @Configuration
 public class Config {
     //编写注解@Bean,注解下写方法,这个方法返回的对象,就会保存到Spring容器中
     @Bean
     public Stu stu(){//注意:调用id时注意写这个方法名
         //实际上每个保存到Spring容器中的对象都要有一个唯一的名字
         //这个名字就是这个方法的方法名
         //简单来说,这个杨过对象在Spring容器中的id就是stu
         Stu stu = new Stu();
         stu.setName("杨过");
         stu.setAge(26);
         return stu;
    }
 }
 

包下新建测试类:获得Spring容器中的对象

 package hello;
 
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
 /**
  * 3.测试:获得Spring容器中的对象
  */
 public class Test {
     public static void main(String[] args) {
         //初始化Spring容器,参数为配置类的反射
         AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(Config.class);//快捷键ACAC,然后回车
 
         //从Spring容器中获取对象
         //getBean方法两个参数:
         //1.想获得的对象在Spring容器中的id(唯一的名字:@Bean时对应方法名)
         //2.获得对象类型的反射(不需要强转,直接返回这个类型的对象)
         Stu stu = acac.getBean("stu",Stu.class);
         System.out.println(stu);
         acac.close();//关闭资源
    }
 }
 

输出结果:Stu{name='杨过', age=26}

2.3 利用单元测试框架JUnit运行Spring

  我们之前使用过Junit来测试程序运行,Junit测试程序有很多好处,例如一个测试类可以编写多个可以直接运行的方法,来减少main方法和类的数量。

  在maven项目的pom.xml文件中添加junit的依赖,刷新Maven

 <!--JUnit单元测试框架-->
 <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.13</version>
 </dependency>

  在我们测试Spring的方法中,发现实例化ACAC对象和ACAC对象的关闭是每次都要编写执行的,我们可以利用Junit的@Before和@After这两个注解减少冗余代码,简化编写。

  src下面有两个路径:main和test,它们的子文件中都包含java文件夹,其中main下面的java主要存储我们日常编写的程序,test下面的java主要是对上面编写程序的测试,虽然名字相同,但是具有不同的功能。在编写测试类时,尽量与上面的包同名,以便测试类中可以直接调用编写好的类,不需要导包。同时,注意测试类的名字不能为Test,因为测试时需要用到@Test注解,会发生冲突。

在test/java路径下,新建包hello,编写HelloTest的代码如下:

 package hello;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
 public class HelloTest {
     //声明ACAC为成员变量,方便每个方法使用(Java中叫做成员变量,有的语言中也叫全局变量)
     AnnotationConfigApplicationContext acac;
 
     //测试类中的@Before注解,注解下的方法会在@Test标记的方法运行之前自动运行
     @Before
     public void init(){
         //初始化Spring容器,参数为配置类的反射
         acac = new AnnotationConfigApplicationContext(Config.class);
    }
 
     //测试类中的@After注解,注解下的方法会在@Test标记的方法运行之后自动运行
     @After
     public void destroy(){
         //关闭资源
         acac.close();
    }
 
     //测试类中的方法上加@Test注解,可以直接运行该方法
     @Test
     public void test(){//直接运行该test方法
         //从Spring容器中获取对象
         Stu stu = acac.getBean("stu",Stu.class);
         System.out.println(stu);
    }
 }
 

输出结果:Stu{name='杨过', age=26}

2.4 将对象保存到Spring容器中方法2:Spring 组件扫描(大部分使用此方式)

  除了上面章节中给大家介绍的@Bean方式可以将对象保存到Spring容器中,还有组件扫描方式可以将对象保存到Spring容器中。

  在main/java路径下,新建包ioc,包中创建类Hero

 package ioc;
 
 import org.springframework.context.annotation.Lazy;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 //@Component(组件):这个注解一旦标记,表示当前类要自动实例化对象,保存到Spring容器中
 //这个对象保存到Spring容器中的id就是当前类名,只是首字母要小写
 //此处直接设置对象属性,适用于对象属性不需要改变的情况
 @Component
 public class Hero {
     private String name= "关羽";
     private String job = "战士";
 
     public Hero(){
         System.out.println("Hero实例化");
    }
 
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 
     public String getJob() {
         return job;
    }
 
     public void setJob(String job) {
         this.job = job;
    }
 
     @Override
     public String toString() {
         return "Hero{" +
                 "name='" + name + '\'' +
                 ", job='" + job + '\'' +
                 '}';
    }
 }
 

包下编写配置类Config

 package ioc;
 
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
 //如果想启动组件扫描的注入方式(即:如果想让Hero类上的@Component生效),
 //则还要在配置类中声明扫描的包
 @ComponentScan("ioc")
 public class Config {
 
 }
 

同样按照上面的方法,转到测试类进行测试

 package ioc;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
 public class IocTest {
     AnnotationConfigApplicationContext acac;
     @Before
     public void init(){
         acac = new AnnotationConfigApplicationContext(Config.class);
    }
     @After
     public void destroy(){
         acac.close();
    }
     @Test
     public void run(){
         Hero hero = acac.getBean("hero",Hero.class);
         System.out.println(hero);
    }
 }
 

输出结果:Hero{name='关羽', job='战士'}

组件扫描流程:

1.实例化ACAC对象时,指定Config类的反射

2.这个Config类上如果有@ComponentScan注解,并指定了包

3.扫描这个包中的所有类(包括子孙包中的所有类)

4.如果某个类上带有支持组件扫描方式保存该类对象到Spring容器的注解(例如@Component)

5.实例化这个对象,并将这个对象保存到Spring容器中,注意:id是这个类名首字母小写

2.5 保存到Spring的细节

  • 除了@Component,还有以下常见注解有相同功能

    • @Controller \ RestController 控制器

    • @Service 业务逻辑

    • @Repository 数据访问

    • .....

  为什么功能一样要设计这么多的注解呢?原因是见到注解名称,就知道这个类的作用(见名知意)

  • 关于特殊的类名

    我们已经学习使用组件扫描时,当前类型对象的id是当前类名首字母小写,但是有特殊情况:如果有类名连续两个以上字母都是大写,例如:VIPStu,那么它保存到Spring容器中的id就是类名(原类名)。

  • 自定义组件ID

    如果有特殊原因不能或不希望类名做id,我们可以自定义组件id,例如:

     @Component("myHero")
     public class Hero {
        ...
     }
     //注意:此时测试类中的id要改变
     //Hero hero = acac.getBean("myHero",Hero.class);

    当然,其它支持组件扫描的注解,也可以用这个方法定义id,一般都是被动使用。

3.Spring中对象的作用域

3.1 作用域(Scope)概述

我们的飞机大战项目中,一共有6个类型,这6个类型中就涉及了我们要讲的两种作用域:

单例(singleton):从程序开始到程序结束,某个类型的对象始终只有一个

天空和英雄机就是单例

原型(prototype): 从程序开始到程序结束,某个类型不断出现新的对象,没有数量的限制(需要时就初始化创建新的对象)

小敌机,大敌机,小蜜蜂,子弹

3.2 单例(singleson)作用域

默认情况下,所有保存到Spring容器中的对象都是单例(singleton)(节省内存,原型要创建多个对象,占用内存)

 @Test
 public void singleton(){
     //从Spring容器中获得两次hero,虽然名字不同,但两次获得的对象完全相同,指同一对象
     Hero hero1 = acac.getBean("hero",Hero.class);
     Hero hero2 = acac.getBean("hero",Hero.class);
     //因为默认情况下,Spring容器中对象都是singleton
     //所以hero1和hero2是相同的引用,指向同一个对象
     hero1.setJob("法师");
     System.out.println(hero2);//Hero{name='关羽', job='法师'}   修改1影响2,因为是同一对象
     System.out.println(hero1==hero2);//true
 }

3.3 原型(prototype)作用域

修改默认scope,将对象修改为prototype(原型)的scope

@Bean保存方式中的设置:

 @Bean
 @Scope("prototype")
 public Stu stu(){
     //代码略
 }

组件扫描方式保存的设置:

 @Component
 @Scope("prototype")
 public class Hero {
     // 代码略
 }

测试代码:

 @Test
 public void prototype(){
         //在@Component注解下编写了@Scope("prototype")注解
         //修改了当前Hero类为原型Scope
         Hero hero1 = acac.getBean("hero",Hero.class);
         Hero hero2 = acac.getBean("hero",Hero.class);
         //原型模式下,每次获得Hero对象Spring都会实例化一个新的对象返回
         //这样就造成了上面两个对象是不同引用的两个不同对象
         hero1.setJob("刺客");
         System.out.println(hero1);//Hero{name='关羽', job='刺客'}
         System.out.println(hero2);//Hero{name='关羽', job='战士'} 不受hero1的影响
         System.out.println(hero1==hero2);//false
 }

今后使用Spring框架的时候,如果需要设置Scope,根据这两个特性来设置修改即可。

4.惰性初始化(懒惰初始化、延迟加载、懒加载)

  Spring容器中大部分对象都是单例的,单例对象会在实例化Spring容器时进行对象的实例化,也就是说实例化Spring容器时会有大量对象进行实例化。针对实例化时会消耗较多资源,但是运行过程中又不一定使用到的对象,我们可以设置为懒惰初始化。

使用@Lazy表示懒惰初始化

@Bean对象设置懒惰加载:

 @Bean
 @Lazy
 public Stu stu(){
     //代码略
 }

组件扫描的方式设置懒惰加载:

 @Component
 @Lazy
 public class Hero {
     // 代码略
 }

测试代码:

 package ioc;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
 public class IocTest {
     AnnotationConfigApplicationContext acac;
     @Before
     public void init(){
         acac = new AnnotationConfigApplicationContext(Config.class);
         System.out.println("init方法运行完毕");
    }
     @After
     public void destroy(){
         acac.close();
    }
     @Test
     public void run(){
         Hero hero = acac.getBean("hero",Hero.class);
         System.out.println(hero);
    }
     @Test
     public void singleton(){
         //从Spring容器中获得两次hero,虽然名字不同,但两次获得的对象完全相同,指同一对象
         Hero hero1 = acac.getBean("hero",Hero.class);
         Hero hero2 = acac.getBean("hero",Hero.class);
         //因为默认情况下,Spring容器中对象都是singleton
         //所以hero1和hero2是相同的引用,指向同一个对象
         hero1.setJob("法师");
         System.out.println(hero2);
         System.out.println(hero1==hero2);
    }
 
     @Test
     public void prototype(){
         //在@Component注解下编写了@Scope("prototype")注解
         //修改了当前Hero类为原型Scope
         Hero hero1 = acac.getBean("hero",Hero.class);
         Hero hero2 = acac.getBean("hero",Hero.class);
         //原型模式下,每次获得Hero对象Spring都会实例化一个新的对象返回
         //这样就造成了上面两个对象是不同引用的两个不同对象
         hero1.setJob("刺客");
         System.out.println(hero1);
         System.out.println(hero2);//不受hero1的影响
         System.out.println(hero1==hero2);
    }
 
     //通过测试验证实例化对象的运行顺序
     @Test
     public void lazy(){
         System.out.println("lazy方法运行");
         Hero hero = acac.getBean("hero",Hero.class);
         System.out.println(hero);
    }
 }
 

输出结果:

 init方法运行完毕
 lazy方法运行
 Hero实例化
 Hero{name='关羽', job='战士'}

非Lazy情况下输出结果:

 Hero实例化
 init方法运行完毕
 lazy方法运行
 Hero{name='关羽', job='战士'}

  正常情况下,是在init的时候实例化对象,而输出的结果证明我们的设置生效了,是在acac.getBean方法的位置才实例化的Hero对象。如果方法中没有获得Hero对象的代码,这个对象就不会实例化了。所以通过使用惰性初始化可以针对个别对象节省内存。

  什么时候需要使用这个对象,什么时候懒惰初始化的对象才会实例化.

  惰性初始化可以针对个别对象节省内存,不宜大范围使用,因为大范围使用会引起运行压力大\缓慢。由于prototype是在需要的时候产生对象,所以不能和Lazy一起使用。

5.DI

5.1 什么是DI?

  DI(Dependency Injection)依赖注入

  Ioc是控制反转,它是一种编程思想,DI是基于控制反转思想的一种操作,要明确依赖注入的原因和作用,我们要先了解"依赖的概念"。

5.2 什么是依赖?

  依赖指程序中A类需要使用到B类型对象的情况,那么就说A类依赖B类。

要在Spring中实现这个依赖过程,我们要创建如下类来演示:

青龙偃月刀类

 package san;
 
 public class DragonBlade {
     private String name = "青龙偃月刀";
 
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 
     @Override
     public String toString() {
         return name;
    }
 }
 

关羽类

 package san;
 
 public class GuanYu {
     private String name = "关云长";
     //在GunaYu类中声明了属性DragonBlade
     //这就是依赖关系,关羽依赖青龙偃月刀
     private DragonBlade dragonBlade;
 
     public void fight(){
         //dragonBlade自动调用toString输出名字
         System.out.println(name+"使用"+dragonBlade+"战斗");
    }
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 
     public DragonBlade getDragonBlade() {
         return dragonBlade;
    }
 
     public void setDragonBlade(DragonBlade dragonBlade) {
         this.dragonBlade = dragonBlade;
    }
 
 }
 

关羽依赖青龙偃月刀,在配置类中:

 package san;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
 public class Config {
     @Bean
     public DragonBlade blade(){
         return new DragonBlade();
    }
     @Bean
     public GuanYu guanYu(){
         return new GuanYu();;
    }
 }
 

测试类让关羽战斗:

 package san;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
 public class SanTest {
     AnnotationConfigApplicationContext acac;
     @Before
     public void init(){
         acac = new AnnotationConfigApplicationContext(Config.class);
    }
     @After
     public void destroy(){
         acac.close();
    }
     @Test
     public void diTest(){
         //先获得青龙偃月刀
         DragonBlade blade = acac.getBean("blade",DragonBlade.class);
         //再获得关羽
         GuanYu guanYu = acac.getBean("guanYu",GuanYu.class);
         //把刀赋给关羽
         guanYu.setDragonBlade(blade);
         //关羽拿刀战斗
         guanYu.fight();
    }
 }
 

我们经过配置看到测试代码,逻辑和之前主动控制没有什么多大区别,但是使用依赖注入可以简化这个流程。

实现依赖注入:修改Config配置

 package san;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
 public class Config {
     @Bean
     public DragonBlade blade(){
         return new DragonBlade();
    }
     //实现依赖注入
     //GuanYu方法的参数列表中声明DragonBlade
     //Spring会自动从Spring容器中找到匹配的对象赋值
     @Bean
     public GuanYu guanYu(DragonBlade blade){
         GuanYu guanYu = new GuanYu();
         guanYu.setDragonBlade(blade);
         return guanYu;
    }
 }
 

测试类修改代码运行:

 @Test
 public void diTest(){
     //直接获得关羽
     GuanYu guanYu=acac.getBean("guanYu",GuanYu.class);
     //关羽战斗,是带着刀的
     guanYu.fight();
 }

  所谓依赖注入,就是将两个或更多类的依赖关系实现在Spring容器内部,从Spring容器中获取时,是一个已经注入好依赖对象的对象。

5.3 @Bean方式实现依赖注入

  上次课完成了基本的依赖注入操作,下面我们研究一下,在Spring容器中有不同数量的DragonBlade类型对象时,运行的效果分别是什么?

  • 当Spring容器中没有任何DragonBlade类型对象时,运行会报错(IDEA也会在相应对象下面出现红色下划线,提示报错:Could not autowire. No beans of 'DragonBlade' type found.

    • Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'guanYu' defined in san.Config: Unsatisfied dependency expressed through method 'guanYu' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'san.DragonBlade' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

  • 当Spring容器中有唯一的DragonBlade类型对象时,运行成功,即使保存DragonBlade对象的方法名和关羽参数的参数名不同也可以.

    •  @Bean
       public DragonBlade blade1(){
          return new DragonBlade();
       }
       
       //实现依赖注入
       //GuanYu方法的参数列表中声明DragonBlade
       //Spring会自动从Spring容器中找到匹配的对象赋值
       @Bean
       public GuanYu guanYu(DragonBlade blade){
          GuanYu guanYu = new GuanYu();
          guanYu.setDragonBlade(blade);
          return guanYu;
       }
    • 输出结果:关云长使用青龙偃月刀战斗

  • 当Spring容器中有两个及以上的DragonBlade类型对象时,关羽方法参数名没有和任何一个方法名匹配时会报错;关羽方法参数名有和任何一个方法名匹配会成功。

    • Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'guanYu' defined in san.Config: Unsatisfied dependency expressed through method 'guanYu' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'san.DragonBlade' available: expected single matching bean but found 2: blade1,blade2

 @Configuration
 public class Config {
     @Bean
     public DragonBlade blade1(){
         return new DragonBlade();
    }
     @Bean
     public DragonBlade blade2(){
         return new DragonBlade();
    }
     @Bean
     public GuanYu guanYu(DragonBlade blade1){
     //public GuanYu guanYu(DragonBlade blade2){
         GuanYu guanYu=new GuanYu();
         guanYu.setDragonBlade(blade1);
         return guanYu;
    }
 }

5.4 组件扫描方式实现依赖注入

先创建新的包di,包中创建丈八蛇矛类SnakeLance

 package di;
 
 import org.springframework.stereotype.Component;
 
 @Component
 public class SnakeLance {
     private String name = "丈八蛇矛";
 
     @Override
     public String toString() {
         return name;
    }
 
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 }
 

创建张飞类:类中需要使用自动装配获得Spring容器中的丈八蛇矛,实现依赖注入

 package di;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 @Component
 public class ZhangFei {
     private String name = "张飞";
     //@Autowired注解翻译为自动装配,表示当前属性会自动从Spring容器中找到合适对象赋值
     //当然,没有合适对象或有多个合适对象但没有匹配id时也会报错
     //这里的匹配规则是属性名称和Spring中的id相匹配
     @Autowired
     private SnakeLance snakeLance;
 
     public void fight(){
         System.out.println(name+"使用"+snakeLance+"战斗");
    }
 
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 
     public SnakeLance getSnakeLance() {
         return snakeLance;
    }
 
     public void setSnakeLance(SnakeLance snakeLance) {
         this.snakeLance = snakeLance;
    }
 }
 

配置类实现组件扫描:

 package di;
 
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
 @ComponentScan("di")
 public class Config {
     // 组件扫描分别扫描了张飞类和丈八蛇矛类
     // 这两个类对象都保存到Spring容器了
 }
 

测试类运行:

 package di;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
 public class DiTest {
     //声明ACAC为成员变量,方便每个方法使用(Java中叫做成员变量,有的语言中也叫全局变量)
     AnnotationConfigApplicationContext acac;
 
     //测试类中的@Before注解,注解下的方法会在@Test标记的方法运行之前自动运行
     @Before
     public void init(){
         //初始化Spring容器,参数为配置类的反射
         acac = new AnnotationConfigApplicationContext(Config.class);
    }
 
     //测试类中的@After注解,注解下的方法会在@Test标记的方法运行之后自动运行
     @After
     public void destroy(){
         //关闭资源
         acac.close();
    }
 
     @Test
     public void run(){
         ZhangFei zhangFei = acac.getBean("zhangFei",ZhangFei.class);
         zhangFei.fight();
    }
 }
 

输出结果:张飞使用丈八蛇矛战斗

5.5 Ioc和DI的解耦

5.5.1 什么是耦合性?

所谓耦合性,就是一组依赖关系中组成依赖关系的类的依赖紧密度。

耦合性高:表示依赖关系紧,好处是整体性好,封装数据好;

耦合性低:表示依赖关系松,好处是易更换,方便维护和扩展。

我们的程序应该追求低耦合,原因是现代程序经常需要进行修改和扩展。

如何实现低耦合的程序呢?关键在一点:将原有的依赖类型,由具体的类更换为接口。

具体到我们课程中的例子:关羽类应该依赖武器接口,而不是具体的类,如青龙偃月刀

5.5.2 解耦测试案例

武器接口类:

 package ou;
 
 public interface Weapon {
     //接口中方法上public可以不写
     String getName();
 }
 

青龙偃月刀类实现武器接口:

 package ou;
 
 import org.springframework.stereotype.Component;
 
 @Component
 public class DragonBlade implements Weapon{
     private String name = "青龙偃月刀";
 
     @Override
     public String toString() {
         return name;
    }
 
     //重写了getName方法
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 }
 

丈八蛇矛类实现武器接口:

 package ou;
 
 import org.springframework.stereotype.Component;
 
 @Component
 public class SnakeLance implements Weapon{
     private String name = "丈八蛇矛";
 
     @Override
     public String toString() {
         return name;
    }
 
     //重写了getName方法
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 }
 

关羽类:

 package ou;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Component;
 
 @Component
 public class GuanYu {
     private String name = "关二爷";
     //解耦的依赖,声明的类型是接口或者抽象类,不声明具体可实例化的内容
     @Autowired
     private Weapon weapon;
 
     public void fight(){
         System.out.println(name+"使用"+weapon+"战斗");
    }
 
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 
     public Weapon getWeapon() {
         return weapon;
    }
 
     public void setWeapon(Weapon weapon) {
         this.weapon = weapon;
    }
 }
 
 

配置类:

 package ou;
 
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
 @ComponentScan("ou")
 public class Config {
 
 }
 

测试类:

 package ou;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
 public class OuTest {
     //声明ACAC为成员变量,方便每个方法使用(Java中叫做成员变量,有的语言中也叫全局变量)
     AnnotationConfigApplicationContext acac;
 
     //测试类中的@Before注解,注解下的方法会在@Test标记的方法运行之前自动运行
     @Before
     public void init(){
         //初始化Spring容器,参数为配置类的反射
         acac = new AnnotationConfigApplicationContext(Config.class);
    }
 
     //测试类中的@After注解,注解下的方法会在@Test标记的方法运行之后自动运行
     @After
     public void destroy(){
         //关闭资源
         acac.close();
    }
 
     @Test
     public void run(){
         GuanYu guanYu = acac.getBean("guanYu",GuanYu.class);
         guanYu.fight();
    }
 
 }
 

运行报错:

 Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'guanYu': Unsatisfied dependency expressed through field 'weapon'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'ou.Weapon' available: expected single matching bean but found 2: dragonBlade,snakeLance

因为Spring容器中有两个Weapon类型对象,Spring无法选择,匹配失败。所以使用@Qualifier注解指定要匹配的Spring容器中对象的id,关羽类中Weapon依赖的位置:

 private String name = "关二爷";
 //解耦的依赖,声明的类型是接口或者抽象类,不声明具体可实例化的内容
 @Autowired
 //@Qualifier注解表示自动装配的对象指定Spring容器中对象的id
 //@Qualifier("snakeLance")
 @Qualifier("dragonBlade")
 private Weapon weapon;

输出结果:关二爷使用青龙偃月刀战斗

作业:

(1)组件扫描方式实现

  1. 创建武器接口Weapon

     package xi;
     
     import org.springframework.stereotype.Component;
     
     @Component
     public interface Weapon {
     }
  1. 创建金箍棒GoldenCudgel 实现武器接口

     package xi;
     
     import org.springframework.stereotype.Component;
     
     @Component
     public class GoldenCudgel implements Weapon{
         private String name = "金箍棒";
     
         @Override
         public String toString() {
             return name;
        }
     
         public String getName() {
             return name;
        }
     
         public void setName(String name) {
             this.name = name;
        }
     }
  1. 创建乾坤圈UniverseCircle 实现武器接口

     package xi;
     
     import org.springframework.stereotype.Component;
     
     @Component
     public class UniverseCircle implements Weapon{
         private String name = "乾坤圈";
     
         @Override
         public String toString() {
             return name;
        }
     
         public String getName() {
             return name;
        }
     
         public void setName(String name) {
             this.name = name;
        }
     }
  1. 创建孙悟空类WuKong,悟空类依赖武器,编写打妖怪方法

     package xi;
     
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.beans.factory.annotation.Qualifier;
     import org.springframework.stereotype.Component;
     
     @Component
     public class WuKong {
         private String name = "孙悟空";
         @Autowired
         @Qualifier("goldenCudgel")
         //@Qualifier("universeCircle")
         private Weapon weapon;
     
         public void fight(){
             System.out.println(name+"使用"+weapon+"打妖怪");
        }
         public String getName() {
             return name;
        }
         public void setName(String name) {
             this.name = name;
        }
         public Weapon getWeapon() {
             return weapon;
        }
         public void setWeapon(Weapon weapon) {
             this.weapon = weapon;
        }
     }
  1. 创建配置类

     package xi;
     
     import org.springframework.context.annotation.ComponentScan;
     import org.springframework.context.annotation.Configuration;
     
     @Configuration
     @ComponentScan("xi")
     public class Config {
     }
  1. 创建测试类

     package xi;
     
     import org.junit.After;
     import org.junit.Before;
     import org.junit.Test;
     import org.springframework.context.annotation.AnnotationConfigApplicationContext;
     
     public class XiTest {
         AnnotationConfigApplicationContext acac;
         @Before
         public void init(){
             acac = new AnnotationConfigApplicationContext(Config.class);
        }
         @After
         public void destroy(){
             acac.close();
        }
         @Test
         public void run(){
             WuKong wuKong = acac.getBean("wuKong",WuKong.class);
             wuKong.fight();
        }
     }
  1. 使用依赖注入输出"孙悟空使用金箍棒打妖怪"

     package xi;
     
     import org.junit.After;
     import org.junit.Before;
     import org.junit.Test;
     import org.springframework.context.annotation.AnnotationConfigApplicationContext;
     
     public class XiTest {
         AnnotationConfigApplicationContext acac;
         @Before
         public void init(){
             acac = new AnnotationConfigApplicationContext(Config.class);
        }
         @After
         public void destroy(){
             acac.close();
        }
         @Test
         public void run(){
             WuKong wuKong = acac.getBean("wuKong",WuKong.class);
             wuKong.fight();
        }
     }

输出结果:孙悟空使用金箍棒打妖怪

(2)利用@Bean方式实现

利用@Bean解耦实现:

 package xi;
 
 public interface Weapon {
 }

 

 package xi;
 
 public class GoldenCudgel implements Weapon{
     private String name = "金箍棒";
 
     @Override
     public String toString() {
         return name;
    }
 
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 }

 package xi;
 
 public class UniverseCircle implements Weapon{
     private String name = "乾坤圈";
 
     @Override
     public String toString() {
         return name;
    }
 
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 }

 package xi;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 
 public class WuKong {
     private String name = "悟空";
     @Autowired
     @Qualifier("goldenCudgel")
     private Weapon weapon;
 
     public void fight(){
         System.out.println(name+"使用"+weapon+"战斗");
    }
 
     public String getName() {
         return name;
    }
 
     public void setName(String name) {
         this.name = name;
    }
 
     public Weapon getWeapon() {
         return weapon;
    }
 
     public void setWeapon(Weapon weapon) {
         this.weapon = weapon;
    }
 }

 package xi;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
 @Configuration
 public class Config {
     @Bean
     public GoldenCudgel goldenCudgel(){
         return new GoldenCudgel();
    }
     @Bean
     public UniverseCircle universeCircle(){
         return  new UniverseCircle();
    }
     @Bean
     public WuKong wuKong(){
         return new WuKong();
    }
 
 }

 package xi;
 
 import com.sun.deploy.panel.WinUpdatePanel;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
 public class WuKongTest {
     AnnotationConfigApplicationContext acac;
     @Before
     public void init(){
         acac = new AnnotationConfigApplicationContext(Config.class);
    }
     @After
     public void destroy(){
         acac.close();
    }
     @Test
     public void run(){
         WuKong wuKong = acac.getBean("wuKong",WuKong.class);
         wuKong.fight();
    }
 }

@Bean其他测试:

1.金箍棒类、乾坤圈类去掉@Component,悟空类不变

2.Config:

 @Configuration
 public class Config {
     @Bean
     public GoldenCudgel goldenCudgel(){
         return new GoldenCudgel();
    }
 
     @Bean
     public UniverseCircle universeCircle(){
         return new UniverseCircle();
    }
 
     @Bean
  public WuKong wuKong(){
    return new WuKong();
  }
 }
 
 

3.测试效果与原先一致

其它情况:

1.金箍棒类、乾坤圈类去掉@Component,悟空类去掉@Autowired,Config去掉@ComponentScan:孙悟空使用null打妖怪

2.金箍棒类、乾坤圈类去掉@Component,悟空类去掉@Autowired、@Qualifier,Config去掉@ComponentScan:孙悟空使用null打妖怪

3.金箍棒类、乾坤圈类去掉@Component,悟空类去掉@Autowired、@Qualifier,Config去掉@ComponentScan,Config添加:

 @Configuration
 public class Config {
     @Bean
     public GoldenCudgel goldenCudgel(){
         return new GoldenCudgel();
    }
 
     @Bean
     public UniverseCircle universeCircle(){
         return new UniverseCircle();
    }
 
     @Bean
     public WuKong wuKong(){
         WuKong wuKong = new WuKong();
         //wuKong.setWeapon(goldenCudgel());
         wuKong.setWeapon(universeCircle());
         return wuKong;
    }
 }

测试效果与原先一样。

 

 

posted @ 2021-08-20 21:35  Coder_Cui  阅读(127)  评论(0编辑  收藏  举报