• spring 复习


    目录

    ===================================================

    01.Spring框架简介


    01.spring课程四天安排


    02.今日课程内容介绍


    03.spring概述


    04.spring发展历程


    05.spring的优势


    06.spring的体系结构

    ===================================================

    02.程序间耦合


    编写jdbc的工程代码用于分析程序的耦合

    <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>com.spring</groupId>
    	<artifactId>hello</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    
    	<packaging>jar</packaging>
    
    	<dependencies>
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    			<version>5.1.6</version>
    		</dependency>
    	</dependencies>
    
    </project>
    
    /**
     * 程序的耦合
     *      耦合:程序间的依赖关系
     *          包括:
     *              类之间的依赖
     *              方法间的依赖
     *      解耦:
     *          降低程序间的依赖关系
     *      实际开发中:
     *          应该做到:编译期不依赖,运行时才依赖。
     *      解耦的思路:
     *          第一步:使用反射来创建对象,而避免使用new关键字。
     *          第二步:通过读取配置文件来获取要创建的对象全限定类名
     *
     */
    package com.jdbc;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    
    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.获取连接
            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/easy","root","root");
            //3.获取操作数据库的预处理对象
            PreparedStatement pstm = conn.prepareStatement("select * from account");
            //4.执行SQL,得到结果集
            ResultSet rs = pstm.executeQuery();
            //5.遍历结果集
            while(rs.next()){
                System.out.println(rs.getString("name"));
            }
            //6.释放资源
            rs.close();
            pstm.close();
            conn.close();
        }
    }
    
    
    此中DriverManager.registerDriver(new com.mysql.jdbc.Driver());被代码Class.forName("com.mysql.jdbc.Driver");所替换。为什么呢?因为new com.mysql.jdbc.Driver()编译时候需要包mysql-connector-java,否则编译不通过。而Class.forName("com.mysql.jdbc.Driver")编译时候可以没有jdbc包达到解耦,编译期不依赖,运行时才依赖。
    

    编译期依赖


    程序的耦合和解耦的思路分析1


    曾经代码中的问题分析

    package com.bbb.factory.IAccountDao;
    
    public interface IAccountDao{
    
    	/**
         * 模拟保存账户
         */
        void saveAccount();
    }
    
    
    package com.bbb.factory.IAccountDao.impl;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    
    public class AccountDao implements IAccountDao{
    
    	public void saveAccount() {
    		System.out.println("保存了账户");
    	}
    
    }
    
    
    package com.bbb.factory.IAccountService;
    
    public interface IAccountService {
    
        /**
         * 模拟保存账户
         */
        void saveAccount();
    }
    
    
    package com.bbb.factory.IAccountService.impl;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountDao.impl.AccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    public class AccountService implements IAccountService {
    	
    	private IAccountDao accountDao = new AccountDao();
    
    	public void saveAccount() {
    		
    		accountDao.saveAccount();
    	}
    
    }
    
    
    package com.bbb.factory.client;
    
    import com.bbb.factory.IAccountService.IAccountService;
    import com.bbb.factory.IAccountService.impl.AccountService;
    
    public class Client {
    
    	public static void main(String[] args) {
    		IAccountService as = new AccountService();
    		as.saveAccount();
    	}
    
    }
    
    
    可以看到IAccountService as = new AccountService();还有private IAccountDao accountDao = new AccountDao();导致了耦合问题,为了降低程序间的依赖关系,应该对代码进行改造。
    

    编写工厂类和配置文件


    工厂模式解耦

    accountService=com.bbb.factory.IAccountService.impl.AccountService2
    accountDao=com.bbb.factory.IAccountDao.impl.AccountDao
    
    package com.bbb.factory.BeanFactory;
    
    import java.io.InputStream;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    public class BeanFactory {
    	
    	private static Properties props;
    	
    
        //使用静态代码块为Properties对象赋值
        static {
            try {
                //实例化对象
                props = new Properties();
                //获取properties文件的流对象
                InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
                props.load(in);
               
            }catch(Exception 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;
        }
    
    }
    
    
    package com.bbb.factory.IAccountService.impl;
    
    import com.bbb.factory.BeanFactory.BeanFactory;
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountDao.impl.AccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    public class AccountService2 implements IAccountService {
    
    	private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
    
    	public void saveAccount() {
    
    		accountDao.saveAccount();
    	}
    
    }
    
    package com.bbb.factory.client;
    
    import com.bbb.factory.BeanFactory.BeanFactory;
    import com.bbb.factory.IAccountService.IAccountService;
    import com.bbb.factory.IAccountService.impl.AccountService;
    
    public class Client2 {
    
    	public static void main(String[] args) {
    		IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
    		as.saveAccount();
    	}
    
    }
    
    

    运行结果:

    com.bbb.factory.IAccountService.impl.AccountService2
    com.bbb.factory.IAccountDao.impl.AccountDao
    保存了账户
    

    分析工厂模式中的问题并改造

    package com.bbb.factory.client;
    
    import com.bbb.factory.BeanFactory.BeanFactory;
    import com.bbb.factory.IAccountService.IAccountService;
    import com.bbb.factory.IAccountService.impl.AccountService;
    
    public class Client3 {
    
    	public static void main(String[] args) {
    		
    		for (int i = 0; i < 5; i++) {
    			IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
    			System.out.println(as);
    		}
    		
    	}
    
    }
    
    
    com.bbb.factory.IAccountService.impl.AccountService2@6d06d69c
    com.bbb.factory.IAccountService.impl.AccountService2@7852e922
    com.bbb.factory.IAccountService.impl.AccountService2@4e25154f
    com.bbb.factory.IAccountService.impl.AccountService2@70dea4e
    com.bbb.factory.IAccountService.impl.AccountService2@5c647e05
    
    上面的代码是多例的
    

    工厂模式解耦的升级版

    package com.bbb.factory.BeanFactory;
    
    import java.io.InputStream;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    public class BeanFactory2 {
    	
    	private static Properties props;
    	
    	//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
        private static Map<String,Object> beans;
    
        //使用静态代码块为Properties对象赋值
        static {
            try {
                //实例化对象
                props = new Properties();
                //获取properties文件的流对象
                InputStream in = BeanFactory2.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(Exception e){
                throw new ExceptionInInitializerError("初始化properties失败!");
            }
        }
    
        /**
         * 根据bean的名称获取对象
         * @param beanName
         * @return
         */
        public static Object getBean(String beanName){
            return beans.get(beanName);
        }
    }
    
    
    package com.bbb.factory.client;
    
    import com.bbb.factory.BeanFactory.BeanFactory2;
    import com.bbb.factory.IAccountService.IAccountService;
    import com.bbb.factory.IAccountService.impl.AccountService;
    
    public class Client4 {
    
    	public static void main(String[] args) {
    		
    		for (int i = 0; i < 5; i++) {
    			IAccountService as = (IAccountService) BeanFactory2.getBean("accountService");
    			System.out.println(as);
    		}
    		
    	}
    
    }
    
    

    运行结果:

    com.bbb.factory.IAccountService.impl.AccountService2@6d06d69c
    com.bbb.factory.IAccountService.impl.AccountService2@6d06d69c
    com.bbb.factory.IAccountService.impl.AccountService2@6d06d69c
    com.bbb.factory.IAccountService.impl.AccountService2@6d06d69c
    com.bbb.factory.IAccountService.impl.AccountService2@6d06d69c
    

    ===================================================

    03.Spring的 IOC 和 DI


    1 ioc的概念和作用


    2 spring中的Ioc前期准备


    3 spring基于XML的IOC环境搭建和入门

    <?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.bbb.factory.IAccountService.impl.AccountService3"></bean>
    
        <bean id="accountDao" class="com.bbb.factory.IAccountDao.impl.AccountDao"></bean>
    </beans>
    
    package com.bbb.factory.IAccountService.impl;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountDao.impl.AccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    public class AccountService3 implements IAccountService {
    	
    	private IAccountDao accountDao;
    
    	public void saveAccount() {
    		
    		accountDao.saveAccount();
    	}
    
    }
    
    
    package com.bbb.factory.client;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    public class Client5 {
    
    	public static void main(String[] args) {
    		// 1.获取核心容器对象
    		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    		// 2.根据id获取Bean对象
    		IAccountService as = (IAccountService) ac.getBean("accountService");
    		IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
    
            System.out.println(as);
            System.out.println(adao);
            
            as.saveAccount();
    	}
    
    }
    
    

    运行结果:

    十一月 02, 2020 5:17:54 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
    信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5a2e4553: startup date [Mon Nov 02 17:17:54 CST 2020]; root of context hierarchy
    十一月 02, 2020 5:17:54 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
    信息: Loading XML bean definitions from class path resource [bean.xml]
    com.bbb.factory.IAccountService.impl.AccountService3@475530b9
    com.bbb.factory.IAccountDao.impl.AccountDao@1d057a39
    Exception in thread "main" java.lang.NullPointerException
    	at com.bbb.factory.IAccountService.impl.AccountService3.saveAccount(AccountService3.java:13)
    	at com.bbb.factory.client.Client5.main(Client5.java:21)
    
    

    4 ApplicationContext的三个实现类


    5 BeanFactory和ApplicationContext的区别

    package com.itheima.ui;
    
    import com.itheima.dao.IAccountDao;
    import com.itheima.service.IAccountService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * 模拟一个表现层,用于调用业务层
     */
    public class Client {
    
        /**
         * 获取spring的Ioc核心容器,并根据id获取对象
         *
         * ApplicationContext的三个常用实现类:
         *      ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
         *      FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
         *
         *      AnnotationConfigApplicationContext:它是用于读取注解创建容器的,是明天的内容。
         *
         * 核心容器的两个接口引发出的问题:
         *  ApplicationContext:     单例对象适用              采用此接口
         *      它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。
         *
         *  BeanFactory:            多例对象使用
         *      它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。
         * @param args
         */
        public static void main(String[] args) {
            //1.获取核心容器对象
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    //        ApplicationContext ac = new FileSystemXmlApplicationContext("C:\Users\zhy\Desktop\bean.xml");
            //2.根据id获取Bean对象
            IAccountService as  = (IAccountService)ac.getBean("accountService");
            IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
    
            System.out.println(as);
            System.out.println(adao);
            as.saveAccount();
    
    
            //--------BeanFactory----------
    //        Resource resource = new ClassPathResource("bean.xml");
    //        BeanFactory factory = new XmlBeanFactory(resource);
    //        IAccountService as  = (IAccountService)factory.getBean("accountService");
    //        System.out.println(as);
        }
    }
    
    
    判断对象是否创建看构造方法。
    

    6 spring中bean的细节之三种创建Bean对象的方式

    使用默认构造函数创建
    package com.bbb.factory.IAccountService.impl;
    
    import java.util.concurrent.SynchronousQueue;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountDao.impl.AccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    public class AccountService4 implements IAccountService {
    	
    	public  AccountService4(String name){
    		System.out.println("AccountService4 创建了");
    	}
    
    	public void saveAccount() {
    		
    		System.out.println("AccountService4.saveAccount()");
    	}
    
    }
    
    
    <?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">
    
           <!--创建Bean的三种方式 -->
        <!-- 第一种方式:使用默认构造函数创建。
                在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
                采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
    
        -->
        
        <bean id="accountService" class="com.bbb.factory.IAccountService.impl.AccountService4"></bean>
        
    </beans>
    
    package com.bbb.factory.client;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    public class Client6 {
    
    	public static void main(String[] args) {
    		// 1.获取核心容器对象
    		ApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml");
    		// 2.根据id获取Bean对象
    		IAccountService as = (IAccountService) ac.getBean("accountService");
            as.saveAccount();
    	}
    
    }
    
    

    运行结果:

    Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'accountService' defined in class path resource [bean2.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.bbb.factory.IAccountService.impl.AccountService4]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.bbb.factory.IAccountService.impl.AccountService4.<init>()
       
    
    因为com.bbb.factory.IAccountService.impl.AccountService4中没有默认构造函数。
    

    更改后:

    package com.bbb.factory.IAccountService.impl;
    
    import java.util.concurrent.SynchronousQueue;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountDao.impl.AccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    public class AccountService4 implements IAccountService {
    	
    	public  AccountService4(){
    		System.out.println("AccountService4 创建了");
    	}
    
    	public void saveAccount() {
    		
    		System.out.println("AccountService4.saveAccount()");
    	}
    
    }
    
    

    运行结果:

    信息: Loading XML bean definitions from class path resource [bean2.xml]
    AccountService4 创建了
    AccountService4.saveAccount()
    
    使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
    package com.ccc.factory.BeanFactory;
    
    import com.bbb.factory.IAccountService.IAccountService;
    import com.bbb.factory.IAccountService.impl.AccountService4;
    
    /**
     * 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
     */
    public class InstanceFactory {
    
        public IAccountService getAccountService(){
            return new AccountService4();
        }
    }
    
    
    package com.ccc.factory.BeanFactory;
    
    import com.bbb.factory.IAccountService.IAccountService;
    import com.bbb.factory.IAccountService.impl.AccountService4;
    
    /**
     * 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
     */
    public class StaticFactory {
    
        public static IAccountService getAccountService(){
    
            return new AccountService4();
        }
    }
    
    
    <?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">
    
    	<!--创建Bean的三种方式 -->
    	<!-- 第一种方式:使用默认构造函数创建。 在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。 
    		采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。 <bean id="accountService" class="com.bbb.factory.IAccountService.impl.AccountService4"></bean> -->
    
    	<!-- 第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器) -->
    	<bean id="instanceFactory" class="com.ccc.factory.BeanFactory.InstanceFactory"></bean>
    	<bean id="accountService" factory-bean="instanceFactory"
    		factory-method="getAccountService"></bean>
    
    
    
    </beans>
    
    package com.bbb.factory.client;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    public class Client7 {
    
    	public static void main(String[] args) {
    		// 1.获取核心容器对象
    		ApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml");
    		// 2.根据id获取Bean对象
    		IAccountService as = (IAccountService) ac.getBean("accountService");
            as.saveAccount();
    	}
    
    }
    
    

    运行结果:

    信息: Loading XML bean definitions from class path resource [bean2.xml]
    AccountService4 创建了
    AccountService4.saveAccount()
    
    使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
    package com.ccc.factory.BeanFactory;
    
    import com.bbb.factory.IAccountService.IAccountService;
    import com.bbb.factory.IAccountService.impl.AccountService4;
    
    /**
     * 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
     */
    public class StaticFactory {
    
        public static IAccountService getAccountService(){
    
            return new AccountService4();
        }
    }
    
    
    <?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">
    
    	<!--创建Bean的三种方式 -->
    	<!-- 第一种方式:使用默认构造函数创建。 在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。 
    		采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。 <bean id="accountService" class="com.bbb.factory.IAccountService.impl.AccountService4"></bean> -->
    
    	<!-- 第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
    	<bean id="instanceFactory" class="com.ccc.factory.BeanFactory.InstanceFactory"></bean>
    	<bean id="accountService" factory-bean="instanceFactory"
    		factory-method="getAccountService"></bean>
    	 -->
    	
    
    <!-- 第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
        
        -->
    	<bean id="accountService" class="com.ccc.factory.BeanFactory.StaticFactory" factory-method="getAccountService"></bean>
    </beans>
    
    信息: Loading XML bean definitions from class path resource [bean2.xml]
    AccountService4 创建了
    AccountService4.saveAccount()
    
    singleton单例对象的销毁
    package com.bbb.factory.IAccountService.impl;
    
    import java.util.concurrent.SynchronousQueue;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountDao.impl.AccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    public class AccountService5 implements IAccountService {
    	
    	public  AccountService5(){
    		System.out.println("AccountService4 创建了");
    	}
    
    	public void saveAccount() {
    		
    		System.out.println("AccountService4.saveAccount()");
    	}
    	
    	public void  init(){
            System.out.println("对象初始化了。。。");
        }
        public void  destroy(){
            System.out.println("对象销毁了。。。");
        }
    
    }
    
    
    <?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来管理-->
        <!--spring对bean的管理细节
            1.创建bean的三种方式
            2.bean对象的作用范围
            3.bean对象的生命周期
        -->
        
    	<!--创建Bean的三种方式 -->
    	<!-- 第一种方式:使用默认构造函数创建。 在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。 
    		采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。 
    		<bean id="accountService" class="com.bbb.factory.IAccountService.impl.AccountService4"></bean> -->
    
    	<!-- 第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
    	<bean id="instanceFactory" class="com.ccc.factory.BeanFactory.InstanceFactory"></bean>
    	<bean id="accountService" factory-bean="instanceFactory"
    		factory-method="getAccountService"></bean>
    	 -->
    	
    
    <!-- 第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
        <bean id="accountService" class="com.ccc.factory.BeanFactory.StaticFactory" factory-method="getAccountService"></bean>
        -->
    	
    	
    	    <!-- bean的作用范围调整
            bean标签的scope属性:
                作用:用于指定bean的作用范围
                取值: 常用的就是单例的和多例的
                    singleton:单例的(默认值)
                    prototype:多例的
                    request:作用于web应用的请求范围
                    session:作用于web应用的会话范围
                    global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
        -->
        <!-- bean对象的生命周期
                单例对象
                    出生:当容器创建时对象出生
                    活着:只要容器还在,对象一直活着
                    死亡:容器销毁,对象消亡
                    总结:单例对象的生命周期和容器相同
                多例对象
                    出生:当我们使用对象时spring框架为我们创建
                    活着:对象只要是在使用过程中就一直活着。
                    死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
         -->
        <bean id="accountService" class="com.bbb.factory.IAccountService.impl.AccountService5" scope="singleton" init-method="init" destroy-method="destroy"></bean>
    </beans>
    
    package com.bbb.factory.client;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    public class Client8 {
    
    	public static void main(String[] args) {
    		// 1.获取核心容器对象
    		ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml");
    		// 2.根据id获取Bean对象
    		IAccountService as1 = (IAccountService) ac.getBean("accountService");
    		IAccountService as2 = (IAccountService) ac.getBean("accountService");
    		System.out.println(as1==as2);
    		
    		//手动关闭容器
            ac.close();
    
    	}
    
    }
    
    

    运行结果:

    信息: Loading XML bean definitions from class path resource [bean2.xml]
    AccountService4 创建了
    对象初始化了。。。
    true
    十一月 02, 2020 6:22:55 下午 org.springframework.context.support.AbstractApplicationContext doClose
    信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@5a2e4553: startup date [Mon Nov 02 18:22:55 CST 2020]; root of context hierarchy
    对象销毁了。。。
    
    多例对象的销毁
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
              scope="prototype" init-method="init" destroy-method="destroy"></bean>
    
        
    单例对象
        出生:当容器创建时对象出生
        活着:只要容器还在,对象一直活着
        死亡:容器销毁,对象消亡
        总结:单例对象的生命周期和容器相同
    多例对象
        出生:当我们使用对象时spring框架为我们创建
        活着:对象只要是在使用过程中就一直活着。
        死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
    

    7 spring中bean的细节之作用范围


    8 spring中bean的细节之生命周期


    9 spring的依赖注入


    10 构造函数注入

    构造函数注入:
    	使用的标签:constructor-arg
    	标签出现的位置:bean标签的内部
    	标签中的属性
    	type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
    	index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
    	name:用于指定给构造函数中指定名称的参数赋值  常用的
    	=============以上三个用于指定给构造函数中哪个参数赋值===========
    	value:用于提供基本类型和String类型的数据
    	ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
    
    优势:
    	在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
    弊端:
    	改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
    
    package com.ddd.service.impl;
    
    import java.util.Date;
    
    import com.ddd.service.IAccountService;
    
    public class AccountService implements IAccountService {
    
    	//如果是经常变化的数据,并不适用于注入的方式
        private String name;
        private Integer age;
        private Date birthday;
    
        public AccountService(String name,Integer age,Date birthday){
            this.name = name;
            this.age = age;
            this.birthday = birthday;
        }
    
        public void  saveAccount(){
            System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
        }
    
    }
    
    
    <?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">
    
    
    	<bean id="accountService" class="com.ddd.service.impl.AccountService">
    		<constructor-arg name="name" value="泰斯特"></constructor-arg>
    		<constructor-arg name="age" value="18"></constructor-arg>
    		<constructor-arg name="birthday" ref="now"></constructor-arg>
    	</bean>
    	
    	<!-- 配置一个日期对象 -->
        <bean id="now" class="java.util.Date"></bean>
    </beans>
    
    package com.ddd.client;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.ddd.service.IAccountService;
    
    public class Client {
    
        public static void main(String[] args) {
            //1.获取核心容器对象
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean3.xml");
            //2.根据id获取Bean对象
            IAccountService as  = (IAccountService)ac.getBean("accountService");
            as.saveAccount();
    
    
        }
    }
    
    

    运行结果:

    信息: Loading XML bean definitions from class path resource [bean3.xml]
    service中的saveAccount方法执行了。。。泰斯特,18,Mon Nov 02 18:46:04 CST 2020
    

    11 set方法注入

    set方法注入                更常用的方式
    	涉及的标签:property
    	出现的位置:bean标签的内部
    	标签的属性
    		name:用于指定注入时所调用的set方法名称
    		value:用于提供基本类型和String类型的数据
    		ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
    	优势:
    		创建对象时没有明确的限制,可以直接使用默认构造函数
    	弊端:
    		如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
    
    package com.ddd.service.impl;
    
    import java.util.Date;
    
    import com.ddd.service.IAccountService;
    
    public class AccountService2 implements IAccountService {
    
    	//如果是经常变化的数据,并不适用于注入的方式
        private String name;
        private Integer age;
        private Date birthday;
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public void setAge(Integer age) {
    		this.age = age;
    	}
    
    	public void setBirthday(Date birthday) {
    		this.birthday = birthday;
    	}
    
    	public void  saveAccount(){
            System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
        }
    
    }
    
    
    <?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">
    
    
    	<bean id="accountService" class="com.ddd.service.impl.AccountService">
    		<constructor-arg name="name" value="泰斯特"></constructor-arg>
    		<constructor-arg name="age" value="18"></constructor-arg>
    		<constructor-arg name="birthday" ref="now"></constructor-arg>
    	</bean>
    	
    	<!-- 配置一个日期对象 -->
        <bean id="now" class="java.util.Date"></bean>
        
        <bean id="accountService2" class="com.ddd.service.impl.AccountService2">
    		<property name="name" value="TEST" ></property>
    		<property name="age" value="21"></property>
    		<property name="birthday" ref="now"></property>
    	</bean>
    </beans>
    
    package com.ddd.client;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.ddd.service.IAccountService;
    
    public class Client2 {
    
        public static void main(String[] args) {
            //1.获取核心容器对象
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean3.xml");
            //2.根据id获取Bean对象
            IAccountService as  = (IAccountService)ac.getBean("accountService2");
            as.saveAccount();
    
    
        }
    }
    
    

    运行结果:

    信息: Loading XML bean definitions from class path resource [bean3.xml]
    service中的saveAccount方法执行了。。。TEST,21,Mon Nov 02 18:54:56 CST 2020
    

    注意:

    <property name="name" value="TEST" ></property>中的name=""属性取决于class AccountService2中的set方法:去掉set后面小写。
    

    12 注入集合数据

    
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    import java.util.Properties;
    import java.util.Set;
    
    import com.ddd.service.IAccountService;
    
    public class AccountServiceImpl3 implements IAccountService{
    
    	private String[] myStrs;
        private List<String> myList;
        private Set<String> mySet;
        private Map<String,String> myMap;
        private Properties myProps;
    
        public void setMyStrs(String[] myStrs) {
            this.myStrs = myStrs;
        }
    
        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;
        }
    
        public void  saveAccount(){
            System.out.println(Arrays.toString(myStrs));
            System.out.println(myList);
            System.out.println(mySet);
            System.out.println(myMap);
            System.out.println(myProps);
        }
    
    }
    
    
    <?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">
    
    
    	<bean id="accountService" class="com.ddd.service.impl.AccountService">
    		<constructor-arg name="name" value="泰斯特"></constructor-arg>
    		<constructor-arg name="age" value="18"></constructor-arg>
    		<constructor-arg name="birthday" ref="now"></constructor-arg>
    	</bean>
    
    	<!-- 配置一个日期对象 -->
    	<bean id="now" class="java.util.Date"></bean>
    
    	<bean id="accountService2" class="com.ddd.service.impl.AccountService2">
    		<property name="name" value="TEST"></property>
    		<property name="age" value="21"></property>
    		<property name="birthday" ref="now"></property>
    	</bean>
    	
    	<bean id="accountService3" class="com.ddd.service.impl.AccountServiceImpl3">
    	<property name="myStrs">
    		<set>
    			<value>AAA</value>
    			<value>BBB</value>
    			<value>CCC</value>
    		</set>
    	</property>
    
    	<property name="myList">
    		<list>
    			<value>AAA</value>
    			<value>BBB</value>
    			<value>CCC</value>
    		</list>
    	</property>
    
    	<property name="mySet">
    		<list>
    			<value>AAA</value>
    			<value>BBB</value>
    			<value>CCC</value>
    		</list>
    	</property>
    
    	<property name="myMap">
    		<map>
    			<entry key="testA" value="aaa"></entry>
    			<entry key="testB">
    				<value>BBB</value>
    			</entry>
    		</map>	
    
    	</property>
    
    	<property name="myProps">
    		<props>
    			<prop key="testC">ccc</prop>
    			<prop key="testD">ddd</prop>
    		</props>
    	</property>
    </bean>
    	
    	
    </beans>
    
    package com.ddd.client;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.ddd.service.IAccountService;
    
    public class Client3 {
    
        public static void main(String[] args) {
            //1.获取核心容器对象
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean3.xml");
            //2.根据id获取Bean对象
            IAccountService as  = (IAccountService)ac.getBean("accountService3");
            as.saveAccount();
    
    
        }
    }
    
    

    运行结果:

    [AAA, BBB, CCC]
    [AAA, BBB, CCC]
    [AAA, BBB, CCC]
    {testA=aaa, testB=BBB}
    {testD=ddd, testC=ccc}
    
    复杂类型的注入/集合类型的注入
    	用于给List结构集合注入的标签:
    		list array set
    	用于个Map结构集合注入的标签:
    		map  props
    	结构相同,标签可以互换
    

    13 课程知识梳理

    源码:hello

    ===================================================

    04.Spring的常用注解


    1 今日课程内容介绍


    2 常用IOC注解按照作用分类

    * 用于创建对象的
     *      他们的作用就和在XML配置文件中编写一个<bean>标签实现的功能是一样的
     *      Component:
     *          作用:用于把当前类对象存入spring容器中
     *          属性:
     *              value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
     *      Controller:一般用在表现层
     *      Service:一般用在业务层
     *      Repository:一般用在持久层
     *      以上三个注解他们的作用和属性与Component是一模一样。
     *      他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
     *
     *
     * 用于注入数据的
     *      他们的作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的
     *      Autowired:
     *          作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
     *                如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
     *                如果Ioc容器中有多个类型匹配时:
     *          出现位置:
     *              可以是变量上,也可以是方法上
     *          细节:
     *              在使用注解注入时,set方法就不是必须的了。
     *      Qualifier:
     *          作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以(稍后我们讲)
     *          属性:
     *              value:用于指定注入bean的id。
     *      Resource
     *          作用:直接按照bean的id注入。它可以独立使用
     *          属性:
     *              name:用于指定bean的id。
     *      以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
     *      另外,集合类型的注入只能通过XML来实现。
     *
     *      Value
     *          作用:用于注入基本类型和String类型的数据
     *          属性:
     *              value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
     *                      SpEL的写法:${表达式}
     *
     * 用于改变作用范围的
     *      他们的作用就和在bean标签中使用scope属性实现的功能是一样的
     *      Scope
     *          作用:用于指定bean的作用范围
     *          属性:
     *              value:指定范围的取值。常用取值:singleton prototype
     *
     * 和生命周期相关 了解
     *      他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的
     *      PreDestroy
     *          作用:用于指定销毁方法
     *      PostConstruct
     *          作用:用于指定初始化方法
    

    3 用于创建的Component注解

    <?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名称空间和约束中-->
        <context:component-scan base-package="com.bbb.factory"></context:component-scan>
    </beans>
    
    package com.bbb.factory.IAccountService.impl;
    
    import org.springframework.stereotype.Component;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountDao.impl.AccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    /**
     *      Component:
     *          作用:用于把当前类对象存入spring容器中
     *          属性:
     *              value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
     */
    @Component("accountService")
    public class AccountService implements IAccountService {
    	
    	private IAccountDao accountDao;
    	
    	public AccountService() {
    		System.out.println("AccountService()创建了");
    		
    	}
    
    	public void saveAccount() {
    		
    		accountDao.saveAccount();
    	}
    
    }
    
    
    package com.bbb.factory.client;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    
    public class Client {
    
    	public static void main(String[] args) {
    		//1.获取核心容器对象
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            //2.根据id获取Bean对象
            IAccountService as  = (IAccountService)ac.getBean("accountService");
    
            System.out.println(as);
            
    	}
    
    }
    
    
    信息: Loading XML bean definitions from class path resource [bean.xml]
    AccountService()创建了
    com.bbb.factory.IAccountService.impl.AccountService@68ceda24
    

    4 由Component衍生的注解

    package com.bbb.factory.IAccountDao.impl;
    
    import org.springframework.stereotype.Repository;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    
    @Repository("accountDao")
    public class AccountDao implements IAccountDao{
    
    	public void saveAccount() {
    		System.out.println("保存了账户");
    	}
    
    }
    
    
    package com.bbb.factory.client;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    
    public class Client {
    
    	public static void main(String[] args) {
    		//1.获取核心容器对象
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            //2.根据id获取Bean对象
            IAccountService as  = (IAccountService)ac.getBean("accountService");
            IAccountDao ad = (IAccountDao)ac.getBean("accountDao");
    
            System.out.println(as);
            System.out.println(ad);
            
    	}
    
    }
    
    
    信息: Loading XML bean definitions from class path resource [bean.xml]
    AccountService()创建了
    com.bbb.factory.IAccountService.impl.AccountService@5a4041cc
    com.bbb.factory.IAccountDao.impl.AccountDao@15b3e5b
    

    5 自动按照类型注入

    自动注入原理.png

    package com.bbb.factory.IAccountService.impl;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.stereotype.Service;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountDao.impl.AccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    /**
     *      Component:
     *          作用:用于把当前类对象存入spring容器中
     *          属性:
     *              value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
     */
    @Service("accountService")
    public class AccountService implements IAccountService {
    	
    	//作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
    	@Autowired
    	private IAccountDao accountDao;
    	
    	public AccountService() {
    		System.out.println("AccountService()创建了");
    		
    	}
    
    	public void saveAccount() {
    		
    		accountDao.saveAccount();
    	}
    
    }
    
    
    如果Ioc容器中有多个类型匹配时.png

    ![](.assets/ 如果Ioc容器中有多个类型匹配时.png)

    package com.bbb.factory.IAccountDao.impl;
    
    import org.springframework.stereotype.Repository;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    
    @Repository("accountDao1")
    public class AccountDao implements IAccountDao{
    
    	public void saveAccount() {
    		System.out.println("保存了账户accountDao()");
    	}
    
    }
    
    package com.bbb.factory.IAccountDao.impl;
    
    import org.springframework.stereotype.Repository;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    
    @Repository("accountDao2")
    public class AccountDao2 implements IAccountDao{
    
    	public void saveAccount() {
    		System.out.println("保存了账户accountDao2()");
    	}
    
    }
    
    package com.bbb.factory.IAccountService.impl;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.stereotype.Service;
    
    import com.bbb.factory.IAccountDao.IAccountDao;
    import com.bbb.factory.IAccountDao.impl.AccountDao;
    import com.bbb.factory.IAccountService.IAccountService;
    
    /**
     *      Component:
     *          作用:用于把当前类对象存入spring容器中
     *          属性:
     *              value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
     */
    @Service("accountService")
    public class AccountService implements IAccountService {
    	
    	//作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
    	@Autowired
    	private IAccountDao accountDao;
    	
    	public AccountService() {
    		System.out.println("AccountService()创建了");
    		
    	}
    
    	public void saveAccount() {
    		
    		accountDao.saveAccount();
    	}
    
    }
    
    
    @Autowired
    private IAccountDao accountDao
    
    如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
    如果Ioc容器中有多个类型匹配时:
    1.容器中bean对象类型和要注入的变量类型IAccountDao匹配
    2.要注入的变量名称accountDao匹配容器中accountDao1和accountDao2
    

    6 用于注入数据的注解

    *      Qualifier:
     *          作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以(稍后我们讲)
     *          属性:
     *              value:用于指定注入bean的id。
     
    
    *      Resource
     *          作用:直接按照bean的id注入。它可以独立使用
     *          属性:
     *              name:用于指定bean的id。
    
     *      Value
     *          作用:用于注入基本类型和String类型的数据
     *          属性:
     *              value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
     *                      SpEL的写法:${表达式}
    

    7 改变作用范围以及和生命周期相关的注解

    package com.itheima.service.impl;
    
    import com.itheima.dao.IAccountDao;
    import com.itheima.service.IAccountService;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    import javax.annotation.Resource;
    
    /**
     * 账户的业务层实现类
     *
     * 曾经XML的配置:
     *  <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
     *        scope=""  init-method="" destroy-method="">
     *      <property name=""  value="" | ref=""></property>
     *  </bean>
     *
     * 用于创建对象的
     *      他们的作用就和在XML配置文件中编写一个<bean>标签实现的功能是一样的
     *      Component:
     *          作用:用于把当前类对象存入spring容器中
     *          属性:
     *              value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
     *      Controller:一般用在表现层
     *      Service:一般用在业务层
     *      Repository:一般用在持久层
     *      以上三个注解他们的作用和属性与Component是一模一样。
     *      他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
     *
     *
     * 用于注入数据的
     *      他们的作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的
     *      Autowired:
     *          作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
     *                如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
     *                如果Ioc容器中有多个类型匹配时:
     *          出现位置:
     *              可以是变量上,也可以是方法上
     *          细节:
     *              在使用注解注入时,set方法就不是必须的了。
     *      Qualifier:
     *          作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以(稍后我们讲)
     *          属性:
     *              value:用于指定注入bean的id。
     *      Resource
     *          作用:直接按照bean的id注入。它可以独立使用
     *          属性:
     *              name:用于指定bean的id。
     *      以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
     *      另外,集合类型的注入只能通过XML来实现。
     *
     *      Value
     *          作用:用于注入基本类型和String类型的数据
     *          属性:
     *              value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
     *                      SpEL的写法:${表达式}
     *
     * 用于改变作用范围的
     *      他们的作用就和在bean标签中使用scope属性实现的功能是一样的
     *      Scope
     *          作用:用于指定bean的作用范围
     *          属性:
     *              value:指定范围的取值。常用取值:singleton prototype
     *
     * 和生命周期相关 了解
     *      他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的
     *      PreDestroy
     *          作用:用于指定销毁方法
     *      PostConstruct
     *          作用:用于指定初始化方法
     */
    @Service("accountService")
    //@Scope("prototype")
    public class AccountServiceImpl implements IAccountService {
    
    //    @Autowired
    //    @Qualifier("accountDao1")
        @Resource(name = "accountDao2")
        private IAccountDao accountDao = null;
    
        @PostConstruct
        public void  init(){
            System.out.println("初始化方法执行了");
        }
    
        @PreDestroy
        public void  destroy(){
            System.out.println("销毁方法执行了");
        }
    
        public void  saveAccount(){
            accountDao.saveAccount();
        }
    }
    
    源码:helloNano

    ===================================================

    05.基于XML的IOC的案例1


    1 测试基于XML的IOC案例

    源码:day02_eesy_02account_xmlioc
    范例:
    package com.itheima.test;
    
    import com.itheima.domain.Account;
    import com.itheima.service.IAccountService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import java.util.List;
    
    /**
     * 使用Junit单元测试:测试我们的配置
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:bean.xml")
    public class AccountServiceTest {
    
        @Autowired
        private  IAccountService as;
    
        @Test
        public void testFindAll() {
            //3.执行方法
            List<Account> accounts = as.findAllAccount();
            for(Account account : accounts){
                System.out.println(account);
            }
        }
    
        @Test
        public void testFindOne() {
            //3.执行方法
            Account account = as.findAccountById(1);
            System.out.println(account);
        }
    
        @Test
        public void testSave() {
            Account account = new Account();
            account.setName("test");
            account.setMoney(12345f);
            //3.执行方法
            as.saveAccount(account);
    
        }
    
        @Test
        public void testUpdate() {
            //3.执行方法
            Account account = as.findAccountById(4);
            account.setMoney(23456f);
            as.updateAccount(account);
        }
    
        @Test
        public void testDelete() {
            //3.执行方法
            as.deleteAccount(4);
        }
    }
    
    

    2 基于XML的IOC的案例-编写spring的Ioc配置


    3 基于XML的IOC的案例-案例准备


    4 注解IOC案例-把自己编写的类使用注解配置

    ===================================================

    06.Spring的新注解


    1 spring的新注解-Configuration和ComponentScan


    2 spring的新注解-Bean


    3 AnnotationConfigApplicationContext的使用


    4 spring的新注解-Import


    5 spring的新注解-PropertySource


    6 Qualifier注解的另一种用法

    package config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Import;
    import org.springframework.context.annotation.PropertySource;
    
    /**
     * 该类是一个配置类,它的作用和bean.xml是一样的
     * spring中的新注解
     * Configuration
     *     作用:指定当前类是一个配置类
     *     细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
     * ComponentScan
     *      作用:用于通过注解指定spring在创建容器时要扫描的包
     *      属性:
     *          value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
     *                 我们使用此注解就等同于在xml中配置了:
     *                      <context:component-scan base-package="com.itheima"></context:component-scan>
     *  Bean
     *      作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
     *      属性:
     *          name:用于指定bean的id。当不写时,默认值是当前方法的名称
     *      细节:
     *          当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。
     *          查找的方式和Autowired注解的作用是一样的
     *  Import
     *      作用:用于导入其他的配置类
     *      属性:
     *          value:用于指定其他配置类的字节码。
     *                  当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
     *  PropertySource
     *      作用:用于指定properties文件的位置
     *      属性:
     *          value:指定文件的名称和路径。
     *                  关键字:classpath,表示类路径下
     */
    //@Configuration
    @ComponentScan("com.itheima")
    @Import(JdbcConfig.class)
    @PropertySource("classpath:jdbcConfig.properties")
    public class SpringConfiguration {
    
    
    }
    
    
    package config;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    import org.apache.commons.dbutils.QueryRunner;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Scope;
    
    import javax.sql.DataSource;
    
    /**
     * 和spring连接数据库相关的配置类
     */
    public class 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;
    
        /**
         * 用于创建一个QueryRunner对象
         * @param dataSource
         * @return
         */
        @Bean(name="runner")
        @Scope("prototype")
        public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
            return new QueryRunner(dataSource);
        }
    
        /**
         * 创建数据源对象
         * @return
         */
        @Bean(name="ds2")
        public DataSource createDataSource(){
            try {
                ComboPooledDataSource ds = new ComboPooledDataSource();
                ds.setDriverClass(driver);
                ds.setJdbcUrl(url);
                ds.setUser(username);
                ds.setPassword(password);
                return ds;
            }catch (Exception e){
                throw new RuntimeException(e);
            }
        }
    
        @Bean(name="ds1")
        public DataSource createDataSource1(){
            try {
                ComboPooledDataSource ds = new ComboPooledDataSource();
                ds.setDriverClass(driver);
                ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
                ds.setUser(username);
                ds.setPassword(password);
                return ds;
            }catch (Exception e){
                throw new RuntimeException(e);
            }
        }
    }
    
    
    源码:app

    7 spring整合junit问题分析


    8 spring整合junit完成

    ===================================================

    07.银行转账案例


    1 今日课程内容介绍


    2 案例中添加转账方法并演示事务问题


    3 分析事务的问题并编写ConnectionUtils


    4 编写事务管理工具类并分析连接和线程解绑


    5 编写业务层和持久层事务控制代码并配置spring的ioc


    6 测试转账并分析案例中的问题


    7 代理的分析


    8 基于接口的动态代理回顾

    基于接口的动态代理
    /**
     * 一个生产者
     */
    public class Producer implements IProducer{
    
        /**
         * 销售
         * @param money
         */
        public void saleProduct(float money){
            System.out.println("销售产品,并拿到钱:"+money);
        }
    
        /**
         * 售后
         * @param money
         */
        public void afterService(float money){
            System.out.println("提供售后服务,并拿到钱:"+money);
        }
    }
    
    /**
     * 对生产厂家要求的接口
     */
    public interface IProducer {
    
        /**
         * 销售
         * @param money
         */
        public void saleProduct(float money);
    
        /**
         * 售后
         * @param money
         */
        public void afterService(float money);
    }
    
    
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 模拟一个消费者
     */
    public class Client {
    
        public static void main(String[] args) {
            final Producer producer = new Producer();
    
            /**
             * 动态代理:
             *  特点:字节码随用随创建,随用随加载
             *  作用:不修改源码的基础上对方法增强
             *  分类:
             *      基于接口的动态代理
             *      基于子类的动态代理
             *  基于接口的动态代理:
             *      涉及的类:Proxy
             *      提供者:JDK官方
             *  如何创建代理对象:
             *      使用Proxy类中的newProxyInstance方法
             *  创建代理对象的要求:
             *      被代理类最少实现一个接口,如果没有则不能使用
             *  newProxyInstance方法的参数:
             *      ClassLoader:类加载器
             *          它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
             *      Class[]:字节码数组
             *          它是用于让代理对象和被代理对象有相同方法。固定写法。
             *      InvocationHandler:用于提供增强的代码
             *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
             *          此接口的实现类都是谁用谁写。
             */
           IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                    producer.getClass().getInterfaces(),
                    new InvocationHandler() {
                        /**
                         * 作用:执行被代理对象的任何接口方法都会经过该方法
                         * 方法参数的含义
                         * @param proxy   代理对象的引用
                         * @param method  当前执行的方法
                         * @param args    当前执行方法所需的参数
                         * @return        和被代理对象方法有相同的返回值
                         * @throws Throwable
                         */
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //提供增强的代码
                            Object returnValue = null;
    
                            //1.获取方法执行的参数
                            Float money = (Float)args[0];
                            //2.判断当前方法是不是销售
                            if("saleProduct".equals(method.getName())) {
                                returnValue = method.invoke(producer, money*0.8f);
                            }
                            return returnValue;
                        }
                    });
            proxyProducer.saleProduct(10000f);
        }
    }
    
    源码:day03_eesy_02proxy

    9 基于子类的动态代理

    /**
     * 一个生产者
     */
    public class Producer {
    
        /**
         * 销售
         * @param money
         */
        public void saleProduct(float money){
            System.out.println("销售产品,并拿到钱:"+money);
        }
    
        /**
         * 售后
         * @param money
         */
        public void afterService(float money){
            System.out.println("提供售后服务,并拿到钱:"+money);
        }
    }
    
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * 模拟一个消费者
     */
    public class Client {
    
        public static void main(String[] args) {
            final Producer producer = new Producer();
    
            /**
             * 动态代理:
             *  特点:字节码随用随创建,随用随加载
             *  作用:不修改源码的基础上对方法增强
             *  分类:
             *      基于接口的动态代理
             *      基于子类的动态代理
             *  基于子类的动态代理:
             *      涉及的类:Enhancer
             *      提供者:第三方cglib库
             *  如何创建代理对象:
             *      使用Enhancer类中的create方法
             *  创建代理对象的要求:
             *      被代理类不能是最终类
             *  create方法的参数:
             *      Class:字节码
             *          它是用于指定被代理对象的字节码。
             *
             *      Callback:用于提供增强的代码
             *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
             *          此接口的实现类都是谁用谁写。
             *          我们一般写的都是该接口的子接口实现类:MethodInterceptor
             */
            Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
                /**
                 * 执行北地阿里对象的任何方法都会经过该方法
                 * @param proxy
                 * @param method
                 * @param args
                 *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
                 * @param methodProxy :当前执行方法的代理对象
                 * @return
                 * @throws Throwable
                 */
                @Override
                public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    //提供增强的代码
                    Object returnValue = null;
    
                    //1.获取方法执行的参数
                    Float money = (Float)args[0];
                    //2.判断当前方法是不是销售
                    if("saleProduct".equals(method.getName())) {
                        returnValue = method.invoke(producer, money*0.8f);
                    }
                    return returnValue;
                }
            });
            cglibProducer.saleProduct(12000f);
        }
    }
    
    

    10 使用动态代理实现事务控制

    源码:day03_eesy_01account

    ===================================================

    08.面向切面编程 AOP


    1 AOP的概念


    2 spring中的aop术语和细节


    3 spring基于XML的AOP-编写必要的代码


    4 spring基于XML的AOP-配置步骤

    <?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>com.itheima</groupId>
        <artifactId>day03_eesy_03springAOP</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.8.7</version>
            </dependency>
        </dependencies>
    </project>
    
    /**
     * 账户的业务层实现类
     */
    public class AccountServiceImpl implements IAccountService{
    
        @Override
        public void saveAccount() {
            System.out.println("执行了保存");
        }
    
        @Override
        public void updateAccount(int i) {
            System.out.println("执行了更新"+i);
    
        }
    
        @Override
        public int deleteAccount() {
            System.out.println("执行了删除");
            return 0;
        }
    }
    
    
    /**
     * 用于记录日志的工具类,它里面提供了公共的代码
     */
    public class Logger {
    
        /**
         * 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
         */
        public  void printLog(){
            System.out.println("Logger类中的pringLog方法开始记录日志了。。。");
        }
    }
    
    <?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:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 配置srping的Ioc,把service对象配置进来-->
        <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
    
        <!--spring中基于XML的AOP配置步骤
            1、把通知Bean也交给spring来管理
            2、使用aop:config标签表明开始AOP的配置
            3、使用aop:aspect标签表明配置切面
                    id属性:是给切面提供一个唯一标识
                    ref属性:是指定通知类bean的Id。
            4、在aop:aspect标签的内部使用对应标签来配置通知的类型
                   我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
                   aop:before:表示配置前置通知
                        method属性:用于指定Logger类中哪个方法是前置通知
                        pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
    
                切入点表达式的写法:
                    关键字:execution(表达式)
                    表达式:
                        访问修饰符  返回值  包名.包名.包名...类名.方法名(参数列表)
                    标准的表达式写法:
                        public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
                    访问修饰符可以省略
                        void com.itheima.service.impl.AccountServiceImpl.saveAccount()
                    返回值可以使用通配符,表示任意返回值
                        * com.itheima.service.impl.AccountServiceImpl.saveAccount()
                    包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
                        * *.*.*.*.AccountServiceImpl.saveAccount())
                    包名可以使用..表示当前包及其子包
                        * *..AccountServiceImpl.saveAccount()
                    类名和方法名都可以使用*来实现通配
                        * *..*.*()
                    参数列表:
                        可以直接写数据类型:
                            基本类型直接写名称           int
                            引用类型写包名.类名的方式   java.lang.String
                        可以使用通配符表示任意类型,但是必须有参数
                        可以使用..表示有无参数均可,有参数可以是任意类型
                    全通配写法:
                        * *..*.*(..)
    
                    实际开发中切入点表达式的通常写法:
                        切到业务层实现类下的所有方法
                            * com.itheima.service.impl.*.*(..)
        -->
    
        <!-- 配置Logger类 -->
        <bean id="logger" class="com.itheima.utils.Logger"></bean>
    
        <!--配置AOP-->
        <aop:config>
            <!--配置切面 -->
            <aop:aspect id="logAdvice" ref="logger">
                <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
                <aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
            </aop:aspect>
        </aop:config>
    
    </beans>
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * 测试AOP的配置
     */
    public class AOPTest {
    
        public static void main(String[] args) {
            //1.获取容器
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            //2.获取对象
            IAccountService as = (IAccountService)ac.getBean("accountService");
            //3.执行方法
            as.saveAccount();
            as.updateAccount(1);
            as.deleteAccount();
        }
    }
    

    5 切入点表达式的写法


    6 四种常用通知类型

    <?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:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 配置srping的Ioc,把service对象配置进来-->
        <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
    
    
        <!-- 配置Logger类 -->
        <bean id="logger" class="com.itheima.utils.Logger"></bean>
    
        <!--配置AOP-->
        <aop:config>
            <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
                  此标签写在aop:aspect标签内部只能当前切面使用。
                  它还可以写在aop:aspect外面,此时就变成了所有切面可用
              -->
            <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
            <!--配置切面 -->
            <aop:aspect id="logAdvice" ref="logger">
                <!-- 配置前置通知:在切入点方法执行之前执行
                <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->
    
                <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
                <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
    
                <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
                <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
    
                <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
                <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
    
                <!-- 配置环绕通知 详细的注释请看Logger类中-->
                <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
            </aop:aspect>
        </aop:config>
    
    </beans>
    
    四种常用通知类型.png


    7 通用化切入点表达式


    8 spring中的环绕通知


    9 spring基于注解的AOP配置


    10 总结和作业安排

    ===================================================

    09.JdbcTemplate的基本使用


    1 今日课程内容介绍


    2 JdbcTemplate的概述和入门


    3 JdbcTemplate在Dao中的使用


    4 JdbcTemplate的CRUD操作


    5 JdbcTemplate在spring的ioc中使用


    6 JdbcDaoSupport的使用以及Dao的两种编写方式

    ===================================================

    10.Spring中事务控制


    1 基于XML的AOP实现事务控制


    2 作业-基于注解的AOP实现事务控制及问题分析_上


    3 作业-基于注解的AOP实现事务控制及问题分析_下


    4 spring中事务控制的一组API


    5 spring事务控制的代码准备


    6 spring基于XML的声明式事务控制-配置步骤


    7 spring基于注解的声明式事务控制


    8 spring基于纯注解的声明式事务控制


    9 spring编程式事务控制1-了解


    10spring编程式事务控制2-了解


    11 spring5新特性的介绍

  • 相关阅读:
    python之函数嵌套与闭包
    python之高阶函数
    python之装饰器
    python之内置函数
    python之内置函数:map ,filter ,reduce总结
    Python之reduce函数
    install python2 python3 in same computer
    git basic
    git LF CRLF
    2 thread, first to open chat window, second to make the phone
  • 原文地址:https://www.cnblogs.com/RoyalGuardsTomCat/p/13924317.html
Copyright © 2020-2023  润新知