2020:0608 --- Spring(二)

587 阅读8分钟

Spring第二天:spring基于注解的IOC以及IOC的案例

    1.spring中的IOC的常用注解
    2.案例使用xml方式和注解方式实现单标的CRUD操作
        持久层技术选择:dbutils
    3.改造基于注解的ioc案例,使用纯注解的方式实现
        spring的一些新注解使用
    4.spring和Junit整合

1. spring中的IOC的常用注解

1.1 用于创建对象的注解

    曾经的XML配置
    ```
    <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl"
           scope="" init-method="" destroy-method="">
         <property name="" value=""/ref=""></property>
    </bean>
    ```
    现在我们要将这个XML配置,用注解来完成

    1.@Component: 一般不属于三层的会用这个
        使用步骤:
        1. 配置在你要创建的对象上
        作用:反射创建一个当前类对象,并把当前类对象存入spring容器中
        属性:
            value:用于指定bean的id。默认值是当前类名且首字母小写,
                   如果连着首字母2个或多个大写那么id是当前类名。
        
        2. 光配置完还不够:解析完配置文件后,怎么知道在哪写了注解呢?
           配置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在创建容器时,要扫描的包(扫描这个包就可以扫描到这个注解)
                    注意:配置的标签不是在beans的的约束中,而是一个名为context名称空间和约束中
                    此时就会扫描com.itheima包下包括子包的所有类或接口上的注解
                -->
            
                <context:component-scan base-package="com.itheima"></context:component-scan>
            
            </beans>
        ```
        
                   
    2.@Controller:一般用在表现层
    3.@Service:一般用在业务层
    4.@Repository:一般用在持久层
    
        这三个注解他们的作用和属性与@Component是一摸一样的。他们三个是spring框架为我们提
    供的明确的三层使用的注解,使我们对三层对象更加清晰
    *
    ```
    @Service("iAccountServiceImpl")
    public class IAccountServiceImpl implements IAccountService {
    }
    ```

1.2 用于注入数据的注解----注入其他bean对象

    注入引用类型的成员变量。
1.2.1 @Autowired
    ```
    public class IAccountServiceImpl implements IAccountService {
        @Autowired
        private IAccountDao accountDao1 = null;
    
        //构造函数:只能在本类中创建。
        public IAccountServiceImpl(){
            System.out.println("对象创建了");
        }
    
        public void saveAccount() {
            accountDao1.saveAccount();
        }
    }
    ```
    他们的作用就和在XML配置文件中的<bean/>标签的<property/>标签类似
    
    * Autowired:
        作用:自动按照类型注入。
        只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配就可以注入成功。
    注意这里是唯一。如果没有匹配的就会报错。
        
    * 如果有多个能匹配的bean对象类型:
        比如:注解标注在接口成员变量上,但是容器中有两个实现类。
        ```
        @Autowired
        private IAccountDao accountDao1, = null;
        
        @Repository("accountDao2")
        public class IAccountDaoImpl2 implements IAccountDao {
        }
        
        @Repository("accountDao1")
        public class IAccountDaoImpl implements IAccountDao {
        }
        ```
        
       此时,想要注入的是 IAccountDao accountDao1,但是容器中有两个IAccountDao的实现类。
    那么就会让变量名accountDao1再去匹配key--->匹配到@Repository("accountDao1")。就可以
    成功注入了。

    * Autowired的缺陷也就很明显了:
        当有IOC容器中有多个bean对象能和要注入的属性匹配上,而且这几个bean对象的在容器中的key
    也无法和变量名对应上,那么该注解就无法注入了。
1.2.2 @Qualifier和@Resource
    1.@Qualifier
    作用:在按照类中注入的基础上在按照名称注入。它在给成员注入时不能单独使用(搭配@Autowired)。
          但是在给方法参数注入时可以单独使用。
    value:用于指定注入bean的id

    ```
    @Autowired
    @Qualifier("accountDao1")
    private IAccountDao accountDao1 = null;
    ```
    2. @Resource
    作用:直接按照bean的id注入。它可以独立使用
    属性:name用于指定bean的id
    ```
    @Resource(name="accountDao1")
    private IAccountDao accountDao1 = null;
    ```
    
    3.以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现,
      另外集合类型的注入只能通过XML来实现。
1.2.3 注入基本类型
    @Value:
    作用:用于注入基本类型和String类型的数据
    属性:value用于指定数据的值,它可以使用spring中的SpEl(也就是spring的el表达式)
          SpEL的写法:$(表达式)

1.3 用于改变作用范围的--@Scope

    用于指定ApplicationContext接口是创建单例还是多例对象的:
    ApplicationContext接口可以根据我们的需要来创建单例/多例:我们怎么发出需求,
通过scope。如果配置的scope取值为singleton那么单例创建,如果配置的scope取值为
prototype那么多例创建。
    
    属性:value
    指定范围的取值,常用取值:singleton prototype(默认情况下是单例的)
    ```
    @Scope(value = "prototype")
    public class IAccountServiceImpl implements IAccountService
    ```

1.4 和生命周期相关的标签

    @PreDestroy和@PostConstruct
    他们的作用就和在XML配置文件中<bean/>标签的init-method="" destroy-method=""类似。
    
    ```
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService as = (IAccountService)ac.getBean("iAccountServiceImpl");
        as.saveAccount();
        ac.close();
    }
    
    
    @Scope(value = "singleton")
    public class IAccountServiceImpl implements IAccountService {
        @Resource(name="accountDao1")
        private IAccountDao accountDao1 = null;
    
        @PostConstruct
        public void init(){
            System.out.println("初始化方法执行");
        }
        @PreDestroy
        public void destroy(){
            System.out.println("销毁方法执行了");
        }
    }
    ```
    1.PostConstruct:用于指定初始化方法
    2.PreDestroy:用于指定销毁方法
        ---IOC容器关闭时.close()才会执行该方法。
        注意:其关闭容器的close()方法,是ApplicationContext子类的方法,@PreDestroy要想起作用。
    容器对象必须是ClassPathXmlApplicationContext对象。
        注意:如果注解的方法所在的类,是通过构造器多例创建的那么容器不能通过close()方法关闭,
    spring不负责销毁。因为它不知道你什么时候用完,所以只有单例时才可以用这个注解。

2. 案例:使用xml方式和注解方式实现单表的CRUD操作

2.1 使用xml方式实现单表的CRUD操作

    1.bean.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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--配置service对象-->
        <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
            <!--通过set方法:注入dao-->
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!--配置Dao对象-->
        <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
            <!--通过set方法:注入QueryRunner-->
            <property name="runner" ref="queryRunner"></property>
        </bean>
    
        <!--配置注入QueryRunner对象:由于现在是单表的CRUD操作,所以可以选择传入数据源(连接池)。
            注意这里获取的QueryRunner默认是单例的,这样就可能出现线程问题,所以用多例模式创建
            即:每次使用QueryRunner对象都会创建一个新的QueryRunner对象。
        -->
        <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
            <!--注入数据源-->
            <constructor-arg name="ds" ref="dataSource"></constructor-arg>
        </bean>
    
        <!--配置数据源-->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <!--连接数据库的必备信息-->
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/txl_spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    </beans>
    ```
    
        注意:
        1.配置AccountServiceImpl Bean对象
            2.通过set属性方法(property),注入AccountServiceImpl中的Bean类型的成员变量:AccountDaoImpl
            3.配置AccountDaoImpl Bean对象
                4.通过set属性方法(property),注入AccountDaoImpl的Bean类型的成员变量:QueryRunner
                5.配置QueryRunner Bean对象
                    *6.通过构造方法(constructor-arg)注入DataSource
                        ```
                        public QueryRunner(DataSource ds) {
                            super(ds);
                        }
                        ```
                        *7注入(property)DataSource的4个属性
                      
                        
        2.注入QueryRunner对象:
        
            1.由于现在是单表的CRUD操作,所以可以选择传入数据源(连接池)。(考虑到事务)
            
            2.注意这里获取的QueryRunner默认是单例的,这样就可能出现线程问题,所以用多例模式创建
              每次使用QueryRunner对象都会创建一个新的QueryRunner对象。
              
            ```
            <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
                <!--注入数据源-->
                <constructor-arg name="ds" ref="dataSource"></constructor-arg>
            </bean>
            ```
    
    2. Test
        ```
            public void testfindAll() {
            //1.获取容器
            ApplicationContext container = new ClassPathXmlApplicationContext("bean.xml");
            /**
             * 2.得到业务层对象:
             *      由于IAccountServiceImpl有IAccountDaoImpl成员属性
             *      IAccountDaoImpl有QueryRunner成员属性
             *      所以同时得到了IAccountServiceImpl对象,IAccountDaoImpl对象,QueryRunner对象
             */
            IAccountService accountService = container.getBean("accountService", IAccountService.class);
            //3.执行方法
            List<Account> accounts = accountService.findAllAccount();
            for (Account account : accounts) {
                System.out.println(account);
            }
        }
        ```
        注意:得到业务层对象时
            由于IAccountServiceImpl有IAccountDaoImpl成员属性
            IAccountDaoImpl有QueryRunner成员属性
            所以同时得到了IAccountServiceImpl对象,IAccountDaoImpl对象,QueryRunner对象。

2.2 使用注解方式实现单表的CRUD操作

1. bean.xml

    * 文件头要改:context
    * 告知spring:在创建容器时要扫描的包
      <context:component-scan base-package="com.itheima"></context:component-scan>
    * 只留下注入QueryRunner和数据源
    ```
    <?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">
    
        <context:annotation-config/>
    
        <!--告知spring:在创建容器时要扫描的包-->
        <context:component-scan base-package="com.itheima"></context:component-scan>
    
        <!--配置注入QueryRunner对象:由于现在是单表的CRUD操作,所以可以选择传入数据源(连接池)。
            注意这里获取的QueryRunner默认是单例的,这样就可能出现线程问题,所以用多例模式创建
            即:每次使用QueryRunner对象都会创建一个新的QueryRunner对象。
        -->
        <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
            <!--注入数据源-->
            <constructor-arg name="ds" ref="dataSource"></constructor-arg>
        </bean>
    
        <!--配置数据源-->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <!--连接数据库的必备信息-->
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/txl_spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    </beans>
    ```

2.  AccountServiceImpl和AccountDaoImpl

        * 需要注入的属性就不需要set方法了。
        * 创建Bean:
            @Service("accountService")
            @Repository("accountDao")
        * 注入(引用类型)属性
            @Resource(name="accountDao") 
            @Resource(name="queryRunner")

2.3 面临的一些问题

    1. 我们目前学习的注解:
       *1 只能加到我们自己的类上面,所以如果将其他jar包中的类也交给spring    来创建或注入的话,
          能用xml来配置,这是我们目前面临的一个问题。
       
          比如此例中:QueryRunner是外部jar包中的一个类,显然不能在上面加注解。
        
       *2 还有告知spring要扫描的包,我们也要在xml中写。
        
    2. 还有一个问题test类中重复代码太多。  

3 解决仍然Anno有xml的问题@Bean

3.1 让其他jar包中的类也交给spring来创建或注入:@Bean

    想要去掉XML中的配置,全部用注解Anno来完成这个案例:
    我们可以手动创建一个配置类来完成之前XML配置中剩下的内容。
    
    SpringConfiguration类
    ```
    package config; //在java资源文件下建立config包
    @Configuration
    @ComponentScan(value={"com.itheima"})
    public class SpringConfiguration {
    }
    ```
    
    1.来看一下这两个注解
        * 1.@Configuration 
            作用:指定当前类是一个配置类 SpringConfiguration
        
        * 2.ComponentScan
            作用:通过该注解指定spring在创建容器时要扫描的包
            属性:value和basePackages互为别名,所以是一样的。
                  都是数组,所以标准写法:
                  value={"com.itheima", "xxx"}
                  @ComponentScan(value={"com.itheima"}):用于指定创建容器时要扫描的包。
        
                  
    * 那么现在我们就解决了遗留的第一个XML配置
        ```
            <!--告知spring:在创建容器时要扫描的包-->
            <context:component-scan base-package="com.itheima"></context:component-scan>
        ```
        
    2. 第二个问题:怎么通过注解的方式让其他jar包中的类也交给spring来创建或注入
        
        <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">

        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        
        发现:可以通过调用构造函数来实例化QueryRunner和DataSource,QueryRunner是有参构造,
              DataSource是无参构造。
    
    
        1 继续完善SpringConfiguration    
        
        我们分析一下:
            ```
            1.<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
            ```
            ```
            2.public QueryRunner creatQueryRunner(DataSource dataSource){
                return new QueryRunner(dataSource);
            }
            ```
            这两者都可以创建一个quertRunner对象,但是前者创建后会放到IOC容器中,而后者仅仅是
        为调用这实例化了一个queryRunner对象,不会放在IOC中。
        
        2 新的注解:@Bean  标注在方法上,将返回值放到容器中。
        
            作用:用于把当前方法中的返回值bean对象存入springIOC容器中。
            属性:
                name:用于指定bean的id。默认值:当不指定时:当前方法的名称
            细节:
                1.当我们使用注解配置时,如果方法有参数,spring框架会去容器中
                  查找有没有可用的bean对象。查找的方式和AutoWired的注解方式
                  是一样的:自动按照类型注入,如果有唯一的类型直接注入,如果
                  有多个再按照变量名注入。
                  
                  ```
                  @Bean(name="queryRunner")
                  @Scope(value="prototype")   //注意QueryRunner我们需要多例创建
                  public QueryRunner creatQueryRunner(DataSource dataSource){
                    return new QueryRunner(dataSource);
                  }
                  ```
                  
                  即参数DataSource dataSource也会去容器中匹配相应的bean对象,匹配
                  方式同@AutoWerid的引用类型注入。
                
                2.注意这里指定的@Bean(name="queryRunner"),也就是要用queryRunner取
                  容器中匹配QueryRunner对象,所以如果其他类中要注入QueryRunner对象时,
                  要注意指明name是queryRunner。
                  
                3.这种也是调用构造方法创建的QueryRunner对象,而且我们没有指明是多例创建,
                  所以默认也是单例创建,针对这个获取数据源连接池的QueryRunner对象多例
                  创建,再加上:
                  @Scope(value="prototype")   
                
                ```
                    @Bean(name="queryRunner")
                    @Scope(value="prototype")   //注意QueryRunner我们需要多例创建
                    public QueryRunner creatQueryRunner(DataSource dataSource){
                        return new QueryRunner(dataSource);
                    }
                    
                    @Bean("dataSource")
                    public DataSource creatDataSource(){
                        ComboPooledDataSource ds = new ComboPooledDataSource();
                        try {
                            ds.setDriverClass("com.mysql.jdbc.Driver");
                            ds.setJdbcUrl("jdbc:mysql://localhost:3306/txl_spring");
                            ds.setUser("root");
                            ds.setPassword("root");
                
                            return ds;
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                ```
        
        3. 这个时候我们就完全用注解实现了这个案例:可以不用xml配置文件了
            
           最后:修改一下获取容器的方式
           用注解方式获容器,不用加载配置文件的方式。
           
        Test方法中:
        
           * 之前的获取方式:
            //获取容器
            ApplicationContext container = new ClassPathXmlApplicationContext("bean.xml");
            
           * 现在的方式   
            //注解方式:获取容器的方法也变了,参数是被@Configuration注解过的类(可以使多个)。
            ApplicationContext container  = new AnnotationConfigApplicationContext(SpringConfiguration.class);
            
           注意:参数是被@Configuration注解过的类(可以有多个)。

3.2 如果需要些多个配置类:

    * 背景:
            主配置类  SpringConfiguration
            子配置类  JdbcConfig

    1.注意
        new AnnotationConfigApplicationContext(SpringConfiguration.class, JdbcConfig.class);
    是一个可变参的构造函数,而且指向性很强大明确,因为我没有在JdbcConfig类上写:
        @Configuration
        @ComponentScan(value={"com.itheima.config"})
    这些注解。
        只要你给了相应的字节码参数,用容器构造方法都可以把相应的bean对象加载到容器中。
        
    2. 但是
        如果你想在实例化容器时,直传主配置类字节码对象作为参数。那么在不使用其他注解的情况下:
    
        要再JdbcConfig子配置类加上@Configuration,同时还要在主配置类SpringConfiguration指明
    扫描的包@ComponentScan(value={"com.itheima.config"})。这显然又要用到不少重复的注解,
    显得臃肿。
    
    3. 引出新的注解:@Import
    
        作用:用于导入其他的配置类。
              可以把其他的子配置类都导入到主配置类中,相当于把这些细小的配置类都加载进主配置文件里了。
        那么就不用在主配置类专门指定扫描子配置类的包、给子配置类加上@Configuration,在实例化容器
        时传入子配置类的字节码文件这些操作。
        
        属性:
            value:用于指定其他配置类的字节码
        
        @Import(value={JdbcConfig.class})
        
        *注意:一般注解在主配置类上。

3.3 继续解决问题:

    配置数据源属性时,我们将这些属性写死了,这肯定是不合适的。

    1. 在resources文件下,写一个properties配置文件:jdbcConfig.properties

    2. 改造原来配置数据源连接属性的子配置类:JdbcConfig
        
    ```
        @Value("${jdbc.driver}")
        private String driver;
        
        @Value("${jdbc.url}")
        private String url;
        
        @Value("${jdbc.username}")
        private String username;
        
        @Value("${jdbc.password}")
        private String password;
        
        @Bean("dataSource")
        public DataSource creatDataSource(){
            ComboPooledDataSource ds = new ComboPooledDataSource();
            try {
                ds.setDriverClass(driver);
                ds.setJdbcUrl(url);
                ds.setUser(username);
                ds.setPassword(password);
    
                return ds;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    ```
    注意:
        1.这里我们用到@Value注解,它是用来注入基本类型数据的。在这里为基本类型成员变量注入数据,
          语法是EL表达式:
          ("${jdbc.driver}")
          
          
    3. 指明jdbcConfig.properties所在的位置----@PropertySource/@PropertySources
        PropertySources : 可以指定多个PropertySource
        PropertySource  : String[] value();
        
        作用:用于指定properties文件的位置
        
        
    4. 我们要知道resources/jdbcConfig.properties在项目部署运行后后,
       是在类路径根目录下:target/classes/config目录下。

        属性:
            value:指定文件的名称和路径
            关键字:classpath,表示类路径下classpath=target/classes目录中的文件
                    value={"classpath:jdbcConfig.properties"}  
                    表示后面的路径是一个类路径,从而让他找到来加载
                    
            有包的话还可以指明包 :
                    value={"classpath:config/spring/jdbcConfig.properties"}
                    
        注意:@PropertySources挡在主配置类中。

3.4 @Qualifier单独使用的情况

    1.  背景
        我们知道@Bean标注在方法上时,将返回值放到容器中。而且如果方法中有参数时:
        spring框架会去容器中查找有没有可用的bean对象。查找的方式和AutoWired的注解方式。
        
    2.  示例:

        即形参dataSource:在容器中匹配的类型有两个而且key分别是dataSource1和dataSource2。
    此时id和key值也不匹配,那么此时就会报错。
        同@Autowired注解规则一样。我们想到@Qualifier,但是@Qualifier在给类成员标注时
    必须搭配@Autowired。我们又发现:
        @Qualifier是可以标注在方法参数中的: ElementType.PARAMETER,而且此时不需要搭配
    同@Autowired。所以我们为形参指定id(key):

    3.注意:
        @Qualifier优先级高于其本身的匹配规则的。

3.5 小结

        在去掉所有的xml配置时,用纯注解的方式来开发。我们发现:反而更费事了,
    还没有保留一部分XML注解更方便。
        一般来说如果这个类是jar中的还是用xml文件配置好一些,如果是我们自己写的
    注解也可能更方便。

        所以一般原则(思考方向):哪个更方便,就选择哪个。

4. Spring和Junit整合

    ------解决我们测试类中出现许多重复的代码

4.1 先用JUNIT来改进

```
    private ApplicationContext container;
    private IAccountService accountService;

    @Before
    public void init(){
        //1.获取容器
        container = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2.得到业务层对象:
        accountService = container.getBean("accountService", IAccountService.class);
    }
    
    
```

        有个说法(我不太理解):开发工程师和测试工程师的着眼点不一样。测试工程师不一定知道spring框架
    测试工程师拿到手的测试代码:

    显然这会报空指针异常。

4.2 解决方法

    1.我们先理一下这些概念:
    
        1.应用程序的入口:
            main方法
        2.JUNIT单元测试,没有main方法也能执行,原因:
            junit继承了一个main方法
            该方法会判断当前测试类中哪些方法有@Test注解
            junit会让有注解的方法执行
        3.JNUITT不会管我们是否采用了spring矿建
            在执行测试方法时,JUNIT根本不知道我们是不是使用了spring框架
            所以也就不会为我们读取配置文件/配置类创建spring核心容器了。
        4.由以上三点可知
            当测试方法执行时,没有IOC核心容器。
            就算写了@Autowired注解,也无法实现注入。
    
        注意:测试方法执行时,没有IOC核心容器
        
    2.Spring整个JUNIT的配置:
        想办法把JUNIT原来不能加载IOC容器的main方法换掉,换成一个能加载IOC容器的。
        
        1.导入spring整合junit的jar包(坐标)
            ```
                <!--spring整合junit的jar包-->
                <dependency>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-test</artifactId>
                    <version>5.0.2.RELEASE</version>
                </dependency>
            ```
        2.使用JUNIT提供的一个注解把原有的main方法替换掉,替换成spring提供的
        
            @RunWith(SpringJUnit4ClassRunner.class)
            
                Class<? extends Runner> value(); 里面的值必须是一个继承Runner的字节码类。
                SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner
            
                SpringJUnit4ClassRunner是Spring提供的,它一定会为我们提供容器。它还得知道
            你有的是XML配置文件还是注解。配置文件放哪了,配置类是谁?
            
        3. 告知Spring的运行器:
            spring的IOC创建是基于 是XML配置文件还是注解,并且说明位置。
            @ContextConfiguration
                locations:指定xml文件的位置,加上classpath关键字,表示在类路径下。
                classes:指定注解类所在的位置
                
                @ContextConfiguration(locations = "classpath:bean.xml")
                @ContextConfiguration(classes = SpringConfiguration.class)
        
        4. 小细节:版本要求
            当我们使用spring 5.x版本时,要求junit的jar必须是4.12及以上。这样
        在运行测试方法时,SpringJUnit4ClassRunner才会为我们创建IOC容器。
            而且测试方法报错时:不会提示版本错误
            运行整个测试类是:展开所有的异常提示,才会发现版本要求。
        
        * 最后我们分析一下:


运行测试方法时: 1.SpringJUnit4ClassRunner是spring运行器会为我们提供容器 2.@ContextConfiguration(classes = SpringConfiguration.class) 告知了spring运行器,我们是xml配置的还是注解配置的,并且指定了位置。

              然后就会将需要生成的Bean对象放到IOC容器里。
              
            3.所以我们就不需要在定义容器对象属性了,
              而且@Autowired也会为我们注入对应的bean对象。
              当然也可以用:
              @Resource(name = "accountService")