2020:0607 --- Spring(一)

247 阅读21分钟

Spring共四天

大纲

第一天:Spring矿建的概述以及Spring中基于xml的IOC配置
第二天:Spring中基于注解的IOC和ioc的案例
第三天:Spring中的aop和基于XML以及注解的AOP配置
第四天:Spring中的JdbcTemplate以及Spring事务控制

第一天:今日内容

1.Spring的概述
    Spring是什么
    Spring的两大核心
    Spring的发展历程和优势
    Spring体系结构
    
2.程序的耦合以及解耦
    曾经案例中的问题
    工厂模式解耦

3. IOC概念和Spring中的IOC
    Spring中基于XML的IOC环境搭建

4. 依赖注入(Dependency Injection DI)

5. 作业:

1. Spring概述

1.1 Spring是什么

    Spring 是分层的JavaSE/EE应用full-stack轻量级开源框架,以IOC(Inverse Of Control:反转控制)
和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层SpringMVC和持久层Spring 
JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成
为使用最多的Java EE 企业应用开源框架。
    我们目前学习的只是Spring体系中的冰山一角:SpringFramework部分

1.2 Spring的发展历程

1.3 Spring的优势

 方便解耦,简化开发
 AOP编程的支持
 声明式事务的支持
 方便程序的测试
 方便集成各种优秀框架
 降低JavaEE API的使用难度
 Java源码是经典学习范例

1.4 Spring的体系结构

2. 程序的耦合以及解耦

2.1 理解耦合

    1.分析这段熟悉的代码
    ```
    public class JdbcDemo1 {
        public static void main(String[] args) throws Exception{
            //1.注册驱动
            DriverManager.registerDriver(new com.mysql.jdbc.Driver());
    
            //2.获取连接
            Connection coon = DriverManager.getConnection("jdbc:mysql://localhost:3306/txl_spring", "root", "root");
            
            //3.获取操作数据库的预处理对象
            PreparedStatement ps = coon.prepareStatement("select * from account");
            
            //4.执行SQL:得到结果集
            ResultSet rs = ps.executeQuery();
            
            //5.遍历结果集
            while(rs.next()){
                System.out.println(rs.getString("name"));
            }
            
            //6.释放资源
            rs.close();
            ps.close();
            coon.close();
        }
    }
    ```
    2.我在pom.xml中导入了mysql的驱动,上面的程序是可以正常运行的。
    ```
        <!--添加mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
    ```
        显然,这个程序的运行,没有这个com.mysql.jdbc是不可能的,这也是我们长久的一个认识。
        
    3. 当我把pox中的mysql依赖注释掉:编译期的异常
            提示一个编译期的错误: om.mysql.jdbc程序包不存在
            这是一个编译期的异常,不是运行期异常。
     
            我们在写代码时,可能会遇到很多在编译器就给我们报错的情况。这种编译期的报错,说明
        我们这个类在运行时,如果没有这个依赖类是不能正常编译的。
        
        我们可以理解为程序的耦合。

2.2 耦合:程序间的依赖关系

    1.包括:
        类之间的依赖:我们是无法避免的,只能尽量降低。
        方法间的依赖
        
    2.解耦:降低程序间的依赖关系
        如果一个类在编译时就会非常依赖某个类/jar包,那么这个类的独立性就显得非常的差。
        
    3.实际开发中:
        编译期不依赖,运行时才依赖。
        
    4.改进上面的代码:    
        DriverManager.registerDriver(new com.mysql.jdbc.Driver());
            new com.mysql.jdbc.Driver() 代表一个类。
            
        Class.forName("com.mysql.jdbc.Driver"); 
            com.mysql.jdbc.Driver : 是一个字符串。
            这种方式的驱动加载:不再依赖某个具体的驱动类。
            上面的程序,就会更加的独立。
            
        由于没有导入依赖,虽然还是无法运行。但是这是一个运行时异常,不是一个编译期错误。
        
     5.我可以得到一些其他的知识:
        * 加载驱动方式1: DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        依赖:
        ```
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
        ```
        
        * 加载驱动方式2: Class.forName("com.mysql.jdbc.Driver")
        依赖:<scope>runtime</scope>
        ```
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
                <scope>runtime</scope>
            </dependency>
        ```
        * 因为第二种加载驱动方式在编译器不依赖jar包,所以可以指定依赖的范围是运行时
        
    6. 解耦的思路:
        第一步:使用反射来创建对象,而避免使用new关键字。
            一个依赖一个具体的驱动类
            一个只是依赖一个字符串
            
            会导致一个小问题:
            这里只能指定mysql的依赖,如果将来想要连接oracle...等数据库,这个驱动要修改。
        
        第二步:通过读取配置文件来获取要创建的对象全限定类型,然后在使用反射来创建对象。

2.3 工厂模式解耦

        在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,
    让一个类中方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用过程中,直接
    拿过来用就好了。
        那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
    
    1.小扩展:
        Bean:在计算机英语中,有可重用(在很多地方都可能被用到)组件的含义。
        JavaBean:用java语言编写的可重用组件,不等于实体类,实体类只是可重用组件的一部分。
        Bean工厂:它就是创建我们的service和dao对象的,需要两个东西。
                1.需要一个配置文件来配置我们的service和dao。
                  配置的内容:全限定类名,取出全限定类名的标志
                  标志 = 全限定类名(key = value)
                2.通过读取配置文件中配置的内容,反射创建对象。
                  我的配置文件可以是xml也可以是properties
                  
    2. BeanFactory类:用来生产service层对象和dao层对象
        * 注意:获取properties文件流对象时的一些细节。
        
        1.不建议用new FileInputStream()的方式
            虽然在此处我们可以写下这样一个相对路径:src/main/resources/bean.properties
        但是如果是web项目,那么该项目已部署,部署的目录根本不会有src这个目录,所以相对
        路径也不能用。
            磁盘绝对路径也不能用,虽然在当前工程下可以使用,但是不能保证以后的工程都有
        C/D/E/F盘吗?所以我们采用一种适用性更广的方式
            
        ```
        InputStream in = new FileInputStream("src/main/resources/bean.properties");
        ```
       
       2.采用反射的方式获取配置文件流 
       
            创建在resources目录下的文件,最终会成为classes根路径下的文件,这时候可以不用写任何包名。

            ```
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            ```
    
    3.BeanFactory代码
        1. properties配置文件
        ```
        accountService=com.itheima.service.impl.IAccountServiceImpl
        accountDao=com.itheima.dao.impl.IAccountDaoImpl
        ```
        
        2.BeanFactory代码
        ```
        public class BeanFactory {
            //定义一个Properties对象
            public static Properties props;
        
            //使用静态代码块为Properties对象赋值
            static {
                try {
                    //实例化对象
                    props = new Properties();
                    //获取properties文件的流对象
                    //InputStream in = new FileInputStream("src/main/resources/bean.properties");
                    InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
                    props.load(in);
                } catch (IOException e) {
                    throw new ExceptionInInitializerError("初始化properties失败!");
                }
            }
            /**
             * 根据bean名称,获取bean对象
             * @param beanName
             * @return
             */
            public static Object getBean(String beanName){
                Object bean = null;
                try{
                    String beanPath = props.getProperty(beanName);
                    bean = Class.forName(beanPath).newInstance();
                }catch (Exception e){
                    e.printStackTrace();
                }
        
                return bean;
            }
        }
        ```
        
    4. 小结:
        获取个层对象的方式变了,不再是相互依赖实例化:new 
        ```
        IAccountService as = new IAccountServiceImpl();
        private IAccountDao accountDao = new IAccountDaoImpl();
        ```
        而是通过一个统一工厂来生产。
        ```
        IAccountService as = (IAccountService)BeanFactory.getBean("accountService");
        IAccountDao accountDao = (IAccountDao)BeanFactory.getBean("accountDao");
        ```
        这样各层类之间的依赖关系就降低了很多,不会出现因为少了某个类就报编译错误(比如我们将Dao类删掉)
    运行时才会出现错误。通过工厂模式实现了一定程度上的解耦。

    5. 单例和多例
        每次都实例化了新的对象,那么就会重新实例化每个对象中的属性。
        
        5.1.工厂模式创建的对象是:多例。
        ```
        /**
         * 模拟一个表现层:用于调用业务层
         */
        public class Client {
            public static void main(String[] args) {
                //IAccountService as = new IAccountServiceImpl();
                for (int i = 0; i < 5; i++) {
                    IAccountService as = (IAccountService)BeanFactory.getBean("accountService");
                    System.out.println(as);
                }
            }
        }
        ```
        我在这个模拟的表现层中用工厂模式创建了5个业务层对象IAccountService,打印发现
    这5个对象是不同的对象。

        5.2.单例模式:会出现线程问题

        对象只被创建一次,从而类中的成员也就只会初始化一次。
        当你想操作类中的属性时,只有第一次是最初的属性值。后面再操作都会是改变后的值。
    所以说,在多个线程访问或多个对象访问时,由于对象的实例只有一个。那你操作类成员的话,
    单例对象会有限制。
        servlet就是单例:所以不建议在servlet中写类成员。
        
        5.3. 避免单例模式造成的线程安全问题
        
        多例对象执行效率不高,单例对象有线程问题。但是其实我们在代码中可以避免出现方法中有
    类的属性,和对类中属性进行操作的方法。
        一般在service和dao中也不存在这些成员和方法,从而进一步分析。我们其实是没有线程问题的。
    我们一般都是在方法中定义变量,而方法每次都会初始化,所以保证每次得到的都是初始值。 
        那么从这一点分析:我们不需要多例模式创建对象。
        
        分析BeanFactory代码:
            bean = Class.forName(beanPath).newInstance();
            每次都会调用默认构造函数创建对象,所以每次都是一个新的对象。
            
    6. 我们对代码继续进行改善:单例模式
    
            我们只用一次 bean = Class.forName(beanPath).newInstance();创建出来后,将其马上保存起来。
            
            用什么存:容器
                定义一个Map,用于存储我们要创建的对象,我们把它称之为容器
                key:是properties中的key,value:是创建出来的对象
                private static Map<String,Object> beans;
        
            ```
            public class BeanFactory {
                //定义一个Properties对象
                public static Properties props;
            
                //定义一个Map,用于存储我们要创建的对象,我们把它称之为容器
                private static Map<String,Object> beans;
            
                //使用静态代码块为Properties对象赋值
                static {
                    try {
                        //实例化对象
                        props = new Properties();
                        //获取properties文件的流对象
                        InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
                        props.load(in);
                        //实例化容器
                        beans = new HashMap<String, Object>();
                        //取出配置文件中所有的key
                        Enumeration keys = props.keys();
                        //遍历枚举
                        while(keys.hasMoreElements()){
                            //取出每个key
                            String key = keys.nextElement().toString();
                            //根据key获取value
                            String beanPath = props.getProperty(key);
                            //反射创建对象
                            Object value = Class.forName(beanPath).newInstance();
                            //把key和value存入容器之中
                            beans.put(key, value);
                        }
                    } catch (IOException e) {
                        throw new ExceptionInInitializerError("初始化properties失败!");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            
                //根据bean名称,获取bean对象:单例模式
                public static Object getBean(String beanName){
                    return beans.get(beanName);
                }
            }
            ```

3 使用Spring的IOC解决程序耦合

        IOC 控制反转:将创建Bean对象的权利交给了BeanFactory。

        概念:Inversion of Control。
            把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的专用术语。
        它包括依赖注入(Dependendcy Injection DI)和依赖查找(Dependency Lookup)。
        
        作用:
            削减计算机程序的耦合,降低我们代码中的依赖关系。

3.1 案例的前期准备

        这次我们使用的案例是,账户的业务层和持久层的依赖关系解决。在开始spring的配置之前,
    我们要先准备一下环境。由于我们是使用spring解决依赖关系,并不是真正的要增删该查操作,
    所以此时我们没有必要写实体类,而且用的是java工程。
    
    1. 导入依赖:

    2. 编写配置文件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">
    
        <!--把对象的创建交给spring来管理-->
        <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl"></bean>
        <bean id="accountDao" class="com.itheima.dao.impl.IAccountDaoImpl"></bean>
    </beans>
    ```
    这样核心容器就创建好了。
    
    3. 测试代码
    ```
    public static void main(String[] args) {

            //1.获取核心容器对象
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            //2.根据id获取Bean对象:要强转
            IAccountService as = (IAccountService)ac.getBean("accountService");
            //2.1这种获取方式不用强转
            IAccountDao aDao = ac.getBean("accountDao", IAccountDao.class);
    
            System.out.println(as);
            System.out.println(aDao);
        }
    }
    ```

3.2 ApplicationContext的三个常用实现类

    ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下
    FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
    AnnotationConfigApplicationContext:它是用于读取注解创建程序的
    
    ```
    ApplicationContext ac = new FileSystemXmlApplicationContext("
        E:\\Idea_Projects\\day0607_spring(1.3)\\src\\main\\resources\\bean.xml");
    ```
    ```
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");  
    ```

3.3 核心容器的两个接口引发的问题:

    1. ApplicationContext:单例模式适用
            * 它在创建核心容器是,创建对象采用的策略是立即加载方式。也就是说,只要一读取配置文件
        bean.xml,马上就创建配置文件中的配置的对象:
          ```
            <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl"></bean>
            <bean id="accountDao" class="com.itheima.dao.impl.IAccountDaoImpl"></bean>
          ```
            * 我们来分析下这个标签:
            <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl"></bean>
        这句话的意思就是:
                我要用id="accountService" 来取创建的IAccountServiceImpl对象
        所以走完这一步:ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");加载
        bean.xml配置文件之后,spring就会根据反射,调用IAccountServiceImpl中的默认构造犯法,创建出相
        应的对象。
        
    2. BeanFactory:多例模式适用
            BeanFactory是一个顶层的接口,显然功能不是那么完善。
            它在构建核心容器时,创建对象采取的是延迟加载的方式。也就是说,什么时候根据id获取对象了,
        什么时候才真正的创建对象。

3.4 Spring对bean的管理细节

3.4.1 创建bean的三种方式
    1. 第一种方式:使用默认构造函数创建
    
        <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl">
        
        在spring的配置文件中使用bean标签,仅仅以id和class属性。采用的就是默认构造函数创建bean对象。
    如果此类中没有默认构造方法,则对象无法创建。
        Failed to instantiate [com.itheima.service.impl.IAccountServiceImpl]: No default constructor found;
        
        也可以说:这个配置就是在找IAccountServiceImpl的默认构造方法。
        
    2. 第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
        * 普通工厂:普通方法
        ```
        public class InstanceFactory {
            public IAccountService getAccountService(){
                return new IAccountServiceImpl();
            }
        }
        ```
        
       * 看到这个类,我们可能会出现这样的困惑:  
            getAccountService()方法中也使用了new,这不也是耦合了吗?
       我们要知道可能存在的这种情况:这是一个模拟的工厂类,该类可能是存在jar包中的,他是一个class文件我们
       无法通过修改其源码的方式来提供默认构造函数。
            所以既然是jar包中的类,我们没必要着眼与方法中的具体实现,是要我们不new这个类即可。
        我们要的是。将其中的方法的返回值return new IAccountServiceImpl()放到我们的spring容器中。
        
        ```
        <bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
        <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
        ```
        * 分析一下这两句配置:
            首先,public IAccountService getAccountService()这是一个普通的方法,需要对象调用。
            1.先指定这个工厂的默认构造函数:得到instanceFactory对象
                <bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
            2.怎么得到这个方法呢?
                bean id="accountService"        :  指定要得到的对象的id——IAccountService
                factory-bean="instanceFactory"  :  指定那个工厂bean
                factory-method="getAccountService" : 指定这个工厂类中我们要用到的方法    
                
    3. 第三种方式:使用静态工厂中的静态方法(使用某个类中的静态方法创建对象,并存入spring容器)    
        * 静态工厂:静态方法
        ```
        public class StaticFactory {
            public static IAccountService getAccountService(){
                return new IAccountServiceImpl();
            }
        }
        ```
        * 我们要意识到,需要调用的是静态方法,所以不用实例化那个工厂类。
          即我们直接指定对应的静态方法即可。
        ```
        <bean id="accountService" class="com.itheima.factory.StaticFactory"
            factory-method="getAccountService"></bean>
        ```
        
        * 分析一下这句配置
            我要用id="accountService"来取StaticFactory中的getAccountService得到的结果。
            而且我们没有创建StaticFactory对象,所以getAccountService是一个静态方法。
3.4.2 bean对象的作用范围
        我们知道ApplicationContext是一个很智能的接口,它可以跟据我们对象的多例还是单例来采用
    延迟加载还是立即加载。
        * 那么我们怎么判断要创建的对象是单例还是多例呢? 
        
        * bean标签的scope属性:
            作用:用于指定bean的作用范围
            取值:
                singleton:单例(默认值)
                prototype:多例
                request:作用于web应用的请求范围
                session:作用于web应用的会话范围
                global-session:作用于集群环境的会话范围(全局会话范围,当不是集群环境是就是session)
        * 相应的配置:
        ```
            <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl" scope="singleton"></bean>
        ```
        * 扩展:global-session全局session
            由于实际情况下后台的服务器是多个的。我们需要多台服务器都能共享一些数据,那么就将这些数据
        放到global中,这是一个全局会话,这几台服务器都能得到里面的信息。

3.4.3 bean对象的生命周期
    1. 单例对象
        出生:当容器创建时,对象出生。
        活着:只要容器还在,对象一直活着
        死亡:容器销毁,对象消亡
        ```
        <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl"
                    scope="prototype" init-method="init" destroy-method="destroy"></bean>
        ```
            init-method:指定类中的初始化方法名称
            destroy-method:指定类中的销毁方法名称
                可以在destroy方法中写:销毁容器的这一步
        * 怎么理解当容器创建时,对象出生这句话?
            怎么理解这个容器,容器是一个map形式东西。我暂时的理解是这样的:
        当解析完bean.xml中的<bean/>标签时,就会创建出来一个map容器,同时将calss=""
        对应的对象创建出来放到map的value中,而key是id="accountService"
    
    2. 手动关闭容器
            main是一切应用程序的入口,当main方法结束之后,当前应用中线程占据的内存全部释放。
        当然也包含创建的容器,但是此时并没有调用销毁方法destroy,就已经把内存释放了。所以说还没
        有来得及调用销毁,容器就已经消失了。
            要是想要调用那么销毁方法,怎么做呢?———— 手动关闭容器。
            
        * 注意:
            close()方法存在于ApplicationContext的子类中,ApplicationContext本类没有该方法
            
            前面applicationContext对象我们是通过多态的方式实例化的。
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
            
            此时applicationContext被看成了父类的对象,那么调用的方法编译时看的是父类的方法。如果
        父类有这个方法,那么可以编译通过,执行的是子类的方法。
            
            close()方法存在于ApplicationContext的子类ClassPathXmlApplicationContext中,所以我们要
        想用.close()方法,实例化ClassPathXmlApplicationContext,就不要用多态。
            
            ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("bean.xml");
            classApplicationContext.close();
            
    
    3. 多例对象
            出生:当使用对象时,spring框架才为我们创建对象
            活着:对象在使用过程中一直活着。
            死亡:spring虽然很强大,但是它仍然无法知道我们这个对象要用到什么时候,什么时候销毁。
                  所以它不会轻易的将这个对象销毁的。
           
            * 当对象长时间不用且没有别的对象引用时,由java的垃圾回收器回收

4. 依赖注入(Dependency Injection DI)

4.1 依赖注入的概述

        * 依赖注入::Dependency Injection。它是spring框架的具体实现
        
        我们要清楚这样一个情况,我们在编写程序时,通过控制反转,把对象的创建交给了spring。
    但是代码中不可能出现没有依赖的情况。IOC解耦只是降低他们的依赖关系,但不会消除。例如:
    我们的业务层仍会调用持久层的办法。
    
        那这种业务层和持久层的依赖关系,在使用spring之后,就让spring来维护了。
        
        依赖关系的维护,就称为依赖注入。
        既然有依赖:那么涉及到的调用方法,对象,赋值等等操作都由我来(Spring)帮你管理。
        
        * 依赖注入的方式:
            1. 使用构造方法提供
            2. 使用set方法提供
            3. 使用注解提供
            
            * 能注入的数据:
                基本数据类型和String
                其他的bean类型(在配置文件中/注解 配置过的bean,即在spring容器中体现过的bean)
                复杂类型/集合类型

4.2 使用构造函数注入

    背景:    
        假如两个类产生了依赖关系,而且是不常变化的成员变量。比如:A类中要用到: new B(a,b,c)
    来实例化B,我们可以通过构造函数来给成员变量赋值。
    
    注意/思想:
        赋值的操作不是我们自己做的,而是通过配置的方式,让spring框架来为我们注入。
     
    1. 构造方法
        ```
            public IAccountServiceImpl(String name, Integer age, Date birthday){
                this.name = name;
                this.age = age;
                this.birthday = birthday;
            }
        ```
        分析:三个成员变量分别是 String Integer 其他的Bean类型(Date)
        
    2. Spring配置: bean.xml
        使用的标签: constructor-arg
        标签出现的位置:bean标签内部
        
        标签中的属性:
            type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
                  如果有两个String属性的参数,type就无法指定哪个第一个哪个放第二个
                  
            index:用于指定要注入的参数在构造函数中的索引位置。参数索引从0开始
            
            name:用于指定,给构造函数中指定名称的参数赋值(常用)
            
            * 以上三个用于指定给构造函数中哪个参数赋值
            
            value:用于提供基本类型和String类型的数据的值
            
            ref:用于指定其他的bean类型数据,它指的就是在spring的IOC核心容器中出现过的bean对象。
                  就是在xml或注解中配置过这个bean,那都可以通过ref来引用。
                
                  
   3. 配置文件bean.xml代码
        ```
        <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl">
            <constructor-arg name="age" value="18"></constructor-arg>
            <constructor-arg name="name" value="test"></constructor-arg>
            <constructor-arg name="birthday" ref="now"></constructor-arg>
        </bean>
        
        <bean id="now" class="java.util.Date"></bean>
        ```
        
        * 分析一下:
            <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl">
                指定了要注入的类的构造方法
                
            <constructor-arg name="age" value="18"></constructor-arg>
            <constructor-arg name="name" value="test"></constructor-arg>
            <constructor-arg name="birthday" ref="now"></constructor-arg>
                指定了其中的参数名和值
                
            <constructor-arg name="birthday" ref="now"></constructor-arg>
            <bean id="now" class="java.util.Date"></bean>
                注意第三个参数要注入的是一个Date数据类型,我们可以在bean.xml中先配置一个Date对象。
            然后在用ref引进去。
                class="java.util.Date":通过反射创建一个对象,再用id="now"获取这个对象,ref="now"引入。
                
    4. 优势和弊端
        优势:
            当我们某个类中的数据就想用某些值来提供时,就用构造函数来注入更直接,不会让人忽略这一要求。
        弊端:
            改变了这个bean对象的实例化方式,是我们在创建时,必须注入数据,否则对象无法创建成功。
        我们在创建时,如果用不到这些数据时也必须提供。
    
    5. 小结:除非必要,一般不用。

4.3 使用set函数注入 ----- 更灵活,更常用的方式

    1. 需要注入的Bean类
        ```
            private String name;
            private Integer age;
            private Date birthday;
        
            public void setQQName(String name) {
                this.name = name;
            }
        
            public void setAge(Integer age) {
                this.age = age;
            }
        
            public void setBirthday(Date birthday) {
                this.birthday = birthday;
            }

        ```
    2. bean.xml配置:
        使用的标签:property
        出现的位置:bean标签的位置
        
        标签的属性:
             name:用于指定,给构造函数中指定名称的参数赋值(常用)
            
            value:用于提供基本类型和String类型的数据的值
            
            ref:用于指定其他的bean类型数据,它指的就是在spring的IOC核心容器中出现过的bean对象。
                  就是在xml或注解中配置过这个bean,那都可以通过ref来引用。    
        
        
    3. bean.xml代码
        ```
            <bean id="accountService2" class="com.itheima.service.impl.IAccountServiceImpl2">
                <property name="age" value="21"></property>
                <property name="birthday" ref="now2"></property>
                <property name="QQName" value="撕撕撕"></property>
            </bean>
            
            <bean id="now2" class="java.util.Date"></bean>
        ```
    4. 优势:
        很灵活,没有明确的限制,不影响使用默认构造方法

4.4. 复杂类型的注入

    1.要注入的Bean类:成员变量都是数组,集合,map等。
    
     ```
        private String[] myStr;
        private List<String> myList;
        private Set<String> mySet;
        private Map<String, String> myMap;
        private Properties myProps;
    
        public void setMyStr(String[] myStr) {
            this.myStr = myStr;
        }
    
        public void setMyList(List<String> myList) {
            this.myList = myList;
        }
    
        public void setMySet(Set<String> mySet) {
            this.mySet = mySet;
        }
    
        public void setMyMap(Map<String, String> myMap) {
            this.myMap = myMap;
        }
    
        public void setMyProps(Properties myProps) {
            this.myProps = myProps;
        }
     ```
     
    2. 相应的bean.xml配置
    ```
        <bean id="accountService3" class="com.itheima.service.impl.IAccountServiceImpl3">
            <property name="myStr" >
                <array>
                    <value>AAA</value>
                    <value>BBB</value>
                    <value>CCC</value>
                </array>
            </property>
            <property name="myList" >
                <list>
                    <value>qweq</value>
                    <value>q55q</value>
                    <value>888q</value>
                </list>
            </property>
            <property name="myMap">
                <map>
                    <entry key="testa" value="aaaa"></entry>
                    <entry key="testb">
                        <value>bbbb</value>
                    </entry>
                </map>
            </property>
            <property name="mySet">
                <set>
                    <value>12..as</value>
                    <value>%$^.as</value>
                    <value>omg</value>
                </set>
            </property>
            <property name="myProps">
                <props>
                    <prop key="testC">cccc</prop>
                    <prop key="testD">dddd</prop>
                </props>
            </property>
        </bean>
    ```