三天掌握Spring系列第一讲 IOC 解决耦合就是这么简单

1、Spring概述

首先要了解到一个小知识,Spring和Spring Framework的关系。没错,他们是包含的关系。我们常说Spring框架就是Spring Framework框架。其实,Spring项目是一个集合,Spring Framework是其中的一个子项目,其他的还有Spring Boot、Spring Cloud等。只不过我们把Spring Framework简称为Spring了。自己理解即可!

Spring 与其他方便集成,自身Spring 提供一些集成的组件,实际就是按照其他厂商提供的接口(使用文档)进行了封装,方便使用Spring框架的开发人员。给Spring点赞!

1.1、Spring是什么

Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。(详细请参考官方文档或者百度百科)

1.2、Spring体系结构

当你学习一个新东西的时候,首先要了解它里面包含什么,哪些是在项目中可能被使用到的。当然,很多公司选择它,它里面肯定会有一些被大家认可和常常使用的组件。

image-20200810100338057

  1. 数据访问/集成

  2. Web(Spring MVC)

  3. 核心

  4. 测试

  5. 其他组件

1.3、Spring的俩大核心

  • 依赖注入(DI):

Spring 最认同的技术是控制反转的依赖注入(DI)模式。控制反转(IoC)是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。 当编写一个复杂的 Java 应用程序时,应用程序类应该尽可能的独立于其他的 Java 类来增加这些类可重用可能性,当进行单元测试时,可以使它们独立于其他类进行测试。依赖注入(或者有时被称为配线)有助于将这些类粘合在一起,并且在同一时间让它们保持独立。 到底什么是依赖注入?让我们将这两个词分开来看一看。这里将依赖关系部分转化为两个类之间的关联。例如,类 A 依赖于类 B。现在,让我们看一看第二部分,注入。所有这一切都意味着类 B 将通过 IoC 被注入到类 A 中。 依赖注入可以以向构造函数传递参数的方式发生,或者通过使用 setter 方法 post-construction。由于依赖注入是 Spring 框架的核心部分,所以我将在一个单独的章节中利用很好的例子去解释这一概念。

  • 面向切面的程序设计(AOP):

Spring 框架的一个关键组件是面向方面的程序设计(AOP)框架。一个程序中跨越多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。有各种各样常见的很好的关于方面的例子,比如日志记录、声明性事务、安全性,和缓存等等。 在 OOP 中模块化的关键单元是类,而在 AOP 中模块化的关键单元是方面。AOP 帮助你将横切关注点从它们所影响的对象中分离出来,然而依赖注入帮助你将你的应用程序对象从彼此中分离出来。 Spring 框架的 AOP 模块提 供了面向方面的程序设计实现,可以定义诸如方法拦截器和切入点等,从而使实现功能的代码彻底的解耦出来。使用源码级的元数据,可以用类似于.Net属性的方式合并行为信息到代码中。我将在一个独立的章节中讨论更多关于 Spring AOP 的概念。

1.4、Spring的好处

  • 方便解耦,简化开发

    通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

  • AOP编程的支持

    通过 Spring的 AOP 功能,方便进行面向切面的编程,许多不容易用传统OOP 实现的功能可以通过 AOP 轻松应付。

  • 声明式事务的支持

    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。

  • 方便程序的测试

    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

  • 方便集成各种优秀框架

    Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。

2、IOC容器

2.1、为什么需要IOC

我们项目中的数据基本都使用到数据库,那么连接数据库是非常重要的,例如:

 public class JdbcDemo {
  public static void main(String[] args) throws Exception{
  //1.注册驱动
  //DriverManager.registerDriver(new com.mysql.jdbc.Driver());
  Class.forName("com.mysql.jdbc.Driver");
  //2.获取连接
  //3.获取预处理 sql 语句对象
  //4.获取结果集
  //5.遍历结果集
  }
 }

我们的类依赖了数据库的具体驱动类(MySQL),如果这时候更换了数据库品牌(比如 Oracle),需要修改源码来重新数据库驱动。这显然不是我们想要的。


当然你可以说,我写个工具类不就好了!卧槽??

同时,也产生了一个新的问题,mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码。

解决这个问题也很简单,使用配置文件配置。


这个例子是用来介绍耦合的,IOC在一定程度上解决了,代码中极大多数出现的异常为空指针,类型异常等。

2.2、基于 XML 的配置(入门案例)[掌握]

项目准备

image-20200810175619270

 public interface UserDao {
     User saveUser(User user);
 }
 public class UserDaoImpl implements UserDao {
     @Override
     public User saveUser(User user) {
         return user;
    }
 }
 public interface UserService {
     User saveUser(User user);
 }
 public class UserServiceImpl implements UserService {
     private UserDao userDao=new UserDaoImpl();
 
     @Override
     public User saveUser(User user) {
         return userDao.saveUser(user);
    }
 }
 @Data
 public class User {
     private Integer id;
     private String name;
     private int age;
 }
 

1、在Maven工程的resource下创建bean.xml

给配置文件导入约束:

 <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
         http://www.springframework.org/schema/beans/spring-beans.xsd">
  <!-- more bean definitions go here -->
 </beans>

image-20200810111429581

2、让spring管理资源,在配置文件中配置 service 和 dao

 <!-- bean 标签:用于配置让 spring 创建对象,并且存入 ioc 容器之中 
  id 属性:对象的唯一标识。
  class 属性:指定要创建对象的全限定类名
 -->
 <!-- 配置 service -->    
 <bean id="userService" class="cn.fage.spring.impl.UserServiceImpl"></bean>
 <!-- 配置 dao -->
 <bean id="userDao" class="cn.fage.spring.impl.UserDaoImpl"></bean>

内部执行了一个new 对象(); 的一个操作

3、单元测试

 public class TestSpring {
     ApplicationContext context;
 
     @Before
     public void before() {
         context = new ClassPathXmlApplicationContext("spring_xml/bean.xml");
    }
 
     @Test
     public void test01() {
         System.out.println(context);
         System.out.println(context.getBean("userDao"));
         System.out.println(context.getBean("userService"));
    }
 }
 结果:
 org.springframework.context.support.ClassPathXmlApplicationContext@351d0846, started on Mon Aug 10 14:56:06 CST 2020
 cn.fage.spring.impl.UserDaoImpl@4b8ee4de
 cn.fage.spring.impl.UserServiceImpl@27f981c6    

2.3、ApplicationContext继承关系

1、 Spring 中 工厂的类结构图

image-20200810152018447

 

image-202008101520364152、 BeanFactory和 ApplicationContext 的区别

BeanFactory 才是 Spring 容器中的顶层接口。

ApplicationContext 是它的子接口。

BeanFactory 和 ApplicationContext 的区别:

  • 创建对象的时间点不一样。

    • ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。

    • BeanFactory:什么使用什么时候创建对象。

3、 ApplicationContext 接口的实现类

  • ClassPathXmlApplicationContext: 它是从类的根路径下加载配置文件 推荐使用这种

  • FileSystemXmlApplicationContext: 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

  • AnnotationConfigApplicationContext: 当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。

2.4、Bean

1、bean标签

作用:

  • 用于配置对象让 spring 来创建的。

  • 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。

属性:

  • id: 给对象在容器中提供一个唯一标识。用于获取对象。

  • class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。

  • scope:指定对象的作用范围。

    • singleton :默认值,单例的。

    • prototype :多例的。

    • request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中。

    • session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中。

    • global session :WEB 项目中,应用在 Portlet 环境。如果没有 Portlet 环境那么 globalSession 相当于 session。

2、bean 的作用范围和生命周期

单例对象:scope="singleton"

一个应用只有一个对象的实例。它的作用范围就是整个引用。

生命周期:

  • 对象出生:当应用加载,创建容器时,对象就被创建了。

  • 对象活着:只要容器在,对象一直活着。

  • 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。

多例对象:scope="prototype"

每次访问对象时,都会重新创建对象实例。

生命周期:

  • 对象出生:当使用对象时,创建新的对象实例。

  • 对象活着:只要对象在使用中,就一直活着。

  • 对象死亡:当对象长时间不用时,被 Java 的垃圾回收器回收了。

3、实例化 Bean 的三种方式

1、使用默认无参构造函数

  <!--在默认情况下: 
   它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。  
  -->
 <bean id="userService" class="cn.fage.spring.impl.UserServiceImpl"/>

2、Spring管理静态工厂-使用静态工厂的方法创建对象

 /**
  * @author lin
  * @version 1.0
  * @date 2020-08-10 15:30
  * @Description //模拟一个静态工厂,创建业务层实现类
  */
 public class StaticFactory {
     public static UserService createUserService() {
         return new UserServiceImpl();
    }
 }
 
 static-factory.xml:
 
     <!-- 此种方式是:
       使用 StaticFactory 类中的静态方法 createUserService 创建对象,并存入 spring 容器
       id 属性:指定 bean id,用于从容器中获取
       class 属性:指定静态工厂的全限定类名
       factory-method 属性:指定生产对象的 静态方法
     -->
     <!-- 配置 service -->
     <bean id="userService" class="cn.fage.factory.StaticFactory" factory-       method="createUserService"/>

3、Spring管理实例工厂-使用实例工厂的方法创建对象

 public class InstanceFactory {
     /**
      * 模拟一个实例工厂,创建业务层实现类
      * 此工厂创建对象,必须现有工厂实例对象,再调用方法
      */
     public UserService createUserService() {
         return new UserServiceImpl();
    }
 }
 
     <!-- 此种方式是:
          先把工厂的创建交给 spring 来管理。
          然后在使用工厂的 bean 来调用里面的方法
          factory-bean 属性:用于指定实例工厂 bean id。
          factory-method 属性:用于指定实例工厂中创建对象的方法。
     -->
     <bean id="instanceFactory" class="cn.fage.factory.InstanceFactory"/>
     <bean id="userService2" factory-bean="instanceFactory"  factory-method="createUserService"/>

2.5、Spring的依赖注入

1、依赖注入的概念

依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。

我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。 ioc 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。

那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。 简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

2、构造函数注入

顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。具体代码如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private int age;
    private Date birthday;
}

    <!-- 使用构造函数的方式,给 service 中的属性传值
        要求:
            类中需要提供一个对应参数列表的构造函数。
        涉及的标签:
            constructor-arg
                属性:
                    index:指定参数在构造函数参数列表的索引位置
                    type:指定参数在构造函数中的数据类型
                    name:指定参数在构造函数中的名称     用这个找给谁赋值
                    =======上面三个都是找给谁赋值,下面两个指的是赋什么值的==============
                    value:它能赋的值是基本数据类型和 String 类型
                    ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
    -->
    <bean id="user" class="cn.fage.pojo.User">
        <constructor-arg name="id" value="1"/>
        <constructor-arg name="name" value="张三"/>
        <constructor-arg name="age" value="18"/>
        <constructor-arg name="birthday" ref="now"/>
    </bean>
    <bean id="now" class="java.util.Date"/>

3、Set方法注入

顾名思义,就是在类中提供需要注入成员的 set 方法。具体代码如下:

    <!-- 通过配置文件给 bean 中的属性传值:使用 set 方法的方式
	涉及的标签:
		property
			属性:
				name:找的是类中 set 方法后面的部分
				ref:给属性赋值是其他 bean 类型的
				value:给属性赋值是基本数据类型和 string 类型的
		实际开发中,此种方式用的较多。
-->
    <bean id="user2" class="cn.fage.pojo.User">
        <property name="age" value="21"/>
        <property name="name" value="test"/>
        <property name="birthday" ref="now"/>
    </bean>

4、使用 p 名称空间注入数据(本质还是调用 set 方法)

此种方式是通过在 xml中导入 p名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的 set 方法实现注入功能。

--引入命名空间描述
xmlns:p="http://www.springframework.org/schema/p" 

	<!--  P 标签 注入  -->
    <bean id="user3"
          class="cn.fage.pojo.User"
          p:name="user3" p:age="22" p:birthday-ref="now"/>

5 、注入集合属性

顾名思义,就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。 我们这里介绍注入数组,List,Set,Map,Properties。具体代码如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private int age;
    private Date birthday;
    private String[] phones;
    private Set<String> tels;
    private List<User> friends;
    private Map<String, Object> family;
    private Properties pros;

    public User(Integer id, String name, int age, Date birthday) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }
}
  <!-- 注入集合数据
        List 结构的:
            array,list,set
        Map 结构的
            map,entry,props,prop
    -->
    <!--    集合注入    -->
    <bean class="cn.fage.pojo.User" id="user4">
        <!--    基础属性    -->
        <property name="id" value="1"/>
        <property name="age" value="18"/>
        <property name="name" value="user4"/>
        <property name="birthday" ref="now"/>
        <!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
        <!-- 给数组注入数据 -->
        <property name="phones">
            <array>
                <value>华为手机</value>
                <value>苹果手机</value>
            </array>
        </property>
        <!-- 注入Set集合数据 -->
        <property name="tels">
            <set>
                <value>134xxx</value>
                <value>135xxx</value>
                <value>136xxx</value>
                <!--  测试重复数据 -->
                <value>134xxx</value>
            </set>
        </property>
        <!-- 注入List集合数据 -->
        <property name="friends">
            <list value-type="cn.fage.pojo.User">
                <ref bean="user"/>
                <ref bean="user2"/>
                <ref bean="user3"/>
            </list>
        </property>
        <!-- 注入Map数据 -->
        <property name="family">
            <map>
                <entry key="老婆" value="陈乔恩"/>
                <entry key="朋友" value="易烊千玺"/>
            </map>
        </property>
        <!-- 注入properties数据 -->
        <property name="pros">
            <props>
                <prop key="testA">aaa</prop>
                <prop key="testB">bbb</prop>
            </props>
        </property>
    </bean>

结果:

User(id=1, name=user4, age=18, birthday=Mon Aug 10 16:17:35 CST 2020, phones=[华为手机, 苹果手机], tels=[134xxx, 135xxx, 136xxx], friends=[User(id=1, name=user, age=18, birthday=Mon Aug 10 16:17:35 CST 2020, phones=null, tels=null, friends=null, family=null, pros=null), User(id=null, name=user2, age=21, birthday=Mon Aug 10 16:17:35 CST 2020, phones=null, tels=null, friends=null, family=null, pros=null), User(id=null, name=user3, age=22, birthday=Mon Aug 10 16:17:35 CST 2020, phones=null, tels=null, friends=null, family=null, pros=null)], family={老婆=陈乔恩, 朋友=易烊千玺}, pros={testB=bbb, testA=aaa})

3、给予注解的IOC配置

1、明确:写在最前

企业中开发常常使用的一种方式

注解配置和 xml 配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。

关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌握。

在学习注解配置时,采用上一章节的案例,把 spring 的 xml 配置内容改为使用注解逐步实现。

2 、使用注解配置管理的资源

@Repository(value = "userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public User saveUser(User user) {
        System.out.println("UserDaoImpl 执行 saveUser() 方法 ");
        return user;
    }
}

@Service(value="userService")
public class UserServiceImpl implements UserService {
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao;

    @Override
    public User saveUser(User user) {
        return userDao.saveUser(user);
    }
}

@Configuration
public class SpringConfiguration {

    @Bean(value = "user")
    public User createUser1() {
        return new User(1, "user1", 18, new Date());
    }

}

3、修改bean.xml开启对注解的支持

注意: 基于注解整合时,导入约束时需要多导入一个 context 名称空间下的约束。

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 告知spring在创建容器时要扫描注解的包 -->
    <context:component-scan base-package="cn.fage"/>

</beans>

4、常用注解

4.1 、用于创建对象的

相当于:<bean id="" class="">

1、 @Component

作用: 把资源让 spring 来管理。相当于在 xml 中配置一个 bean。

属性: value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。

2、 @Controller @Service @Repository

他们三个注解都是针对@Component的衍生注解,他们的作用及属性都是一模一样的。

他们只不过是提供了更加明确的语义化。

  • @Controller:一般用于表现层的注解。

  • @Service:一般用于业务层的注解。

  • @Repository:一般用于持久层的注解。

细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value在赋值是可以不写。

4.2 用于注入数据的

相当于:<property name="" ref=""> <property name="" value="">

1、 @Autowired

作用:

自动按照类型注入。当使用注解注入属性时,set方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。

2、 @Qualifier

作用:

在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和@Autowire 一起使用;但是给方法参数注入时,可以独立使用。

属性:

value:指定 bean 的 id。

3、 @Resource

作用:

直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。

属性:

name:指定 bean 的 id。

4、 @Value

作用:

注入基本数据类型和 String 类型数据的

属性:

value:用于指定值

4.3、用于改变作用范围的

相当于:<bean id="" class=""scope=""/>

1、 @Scope

作用:

指定 bean 的作用范围。

属性:

value: 指定范围的值

取值:singleton prototype request session globalsession

4.4、和生命周期相关的(了解)

相当于:<bean id="" class=""init-method="" destroy-method=""/>

1、 @PostConstruct

作用: 用于指定初始化方法。

2、 @PreDestroy

作用: 用于指定销毁方法。

4.5 、关于 Spring 注解 和 XML 的选择问题

  • 注解的优势

    • 配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。

  • XML的优势

    • 修改时,不用改源码。不涉及重新编译和部署。

  • Spring管理Bean方式的比较:

image-20200810165843390

4.5 、spring管理对象细节

基于注解的 spring IoC 配置中,bean 对象的特点和基于 XML 配置是一模一样的。

4.6、 spring的纯注解配置

写到此处,基于注解的 IoC 配置已经完成,但是我们依然离不开 spring 的 xml 配置文件,那么能不能不写这个 bean.xml,所有配置都用注解来实现呢?

4.7、配置类注解

1、 @Configuration

作用:

用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用 AnnotationApplicationContext(有@Configuration 注解的类.class)。

属性:

value:用于指定配置类的字节码

//spring的配置类,相当于bean.xml文件
@Configuration
public class SpringConfiguration {

}

2 、@ComponentScan

如何配置创建容器时要扫描的包呢?

作用:

用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:<context:component-scan base-package="cn.fage"/> 是一样的。

属性:

basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。

//spring的配置类,相当于bean.xml文件
@Configuration
@ComponentScan("cn.fage")
public class SpringConfiguration {

}

3、 @Bean

作用:

该注解只能写在方法上,用于把当前方法的返回值作为bean对象存入spring的ioc容器中。

属性:

name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。当不写时,默认值是当前方法的名称。

细节:

当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。 查找的方式和Autowired注解的作用是一样的。

示例参考第三章第三节

4、 @PropertySource

读取配置文件

作用:

用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。

属性:

value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:

示例代码:

@Configuration
@PropertySource(value = {"classpath:user/user.properties"})
public class UserConfigs {
    @Value("${id}")
    int id;
    @Value("${name}")
    String name;
    @Value(("${age}"))
    int age;

    @Bean(value = "user2")
    public User createUser1() {
        return new User(id, name, age);
    }

}
id=2
name=user2
age=18

5、 @Import

作用:

用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问题。如果ComponentScan扫描包里面包括其,那么则不需要引入。

属性:

value[]:用于指定其他配置类的字节码。

示例代码:

复制代码

@Configuration
@Import(value = {UserConfigs.class})
@ComponentScan(value = "cn.fage")
public class SpringConfiguration {}

6 、通过注解获取容器

/**
 * @author lin
 * @version 1.0
 * @date 2020-08-10 11:36
 * @Description TODO
 */
public class TestSpringAnnotation {
    ApplicationContext context;
    @Before
    public void before() {
        // 使用 xml 里面的 <context:component-scan base-package="cn.fage"/>
        // context = new ClassPathXmlApplicationContext("spring-xml/annotation-bean.xml");
        context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
    }
    @Test
    public void test01() {
        System.out.println(context);
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        UserService userService = (UserService) context.getBean("userService");
        userService.saveUser(context.getBean("user", User.class));
    }
}

运行结果如下:

org.springframework.context.annotation.AnnotationConfigApplicationContext@4d49af10, started on Mon Aug 10 17:28:07 CST 2020
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfiguration
userConfigs
userDao
userService
user2
user
UserDaoImpl 执行 saveUser() 方法 

进程已结束,退出代码0

5、使用spring-test的启动器

在代码中我们使用before方法

    ApplicationContext context;

    @Before
    public void before() {
        context = new ClassPathXmlApplicationContext("spring-xml/bean.xml");
    }

这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。

1、解决思路分析

针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建 spring 容器,我们就无须手动创建了,问题也就解决了。

我们都知道,junit 单元测试的原理,但显然,junit 是无法实现的,因为它自己都无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在,junit 给我们暴露了一个注解,可以让我们替换掉它的运行器。

这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我们只需要告诉它配置文件在哪就行了。

当然你可以想,你如果启动的是tomcat后台服务器,再把接口暴露出来,那么是不是就想到Spring Boot了!!

2、配置步骤

第一步:使用@RunWith注解替换原有运行器

第二步:使用@ContextConfiguration指定spring配置文件的位置

第三步:使用@Autowired给测试类中的变量注入数据(就是使用了!!)

/**
 * @author lin
 * @version 1.0
 * @date 2020-08-10 17:31
 * @Description TODO
 */
// SpringJUnit4ClassRunner 和 SpringRunner 都可以
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class TestRunner {
    @Autowired
    UserService userService;

    @Autowired
    User user;

    @Test
    public void test01() {
        User user = userService.saveUser(this.user);
        System.out.println(user);
    }

}

运行结果如下:

UserDaoImpl 执行 saveUser() 方法 
User(id=1, name=user1, age=18, birthday=Mon Aug 10 18:00:09 CST 2020, phones=null, tels=null, friends=null, family=null, pros=null)

image-20200810180023927

@ContextConfiguration 注解:

  • locations 属性:用于指定配置文件的位置。如果是类路径下,需要用 classpath:表明 例如 locations=("classpath:bean.xml")

  • classes 属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。

3、为什么到 不把测试类配到 xml 中

配到xml中肯定是可以使用的。

那么为什么不采用配置到 xml 中的方式呢?

这个原因是这样的:

第一:当我们在 xml 中配置了一个 bean,spring 加载配置文件创建容器时,就会创建对象。

第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。

所以,基于以上两点,我们不应该把测试配置到 xml 文件中。

QQ

公众号发哥讲

这是一个稍偏基础和偏技术的公众号,甚至其中包括一些可能阅读量很低的包含代码的技术文,不知道你是不是喜欢,期待你的关注。

img

如果你觉得文章还不错,就请点击右上角选择发送给朋友或者转发到朋友圈~

● 扫码关注我们

据说看到好文章不推荐的人,服务器容易宕机!

本文版权归 发哥讲博客园 共有,原创文章,未经允许不得转载,否则保留追究法律责任的权利。

posted @ 2020-08-10 18:31  发哥讲Java  阅读(318)  评论(1编辑  收藏  举报