Mga butangSpring nga artikulo

Spring 中的事务管理

1、事务简介
  • 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性。
  • 事务就是一系列的动作,它们被当做一个单独的工作单元。这些动作要么全部完成,要么全部不起作用
  • 事务的四个关键属性 (ACID)

    • 原子性 (atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成要么完全不起作用。
    • 一致性 (consistency):一旦所有事务动作完成,事务就被提交。数据和资源就处于一种满足业务规则的一致性状态中。
    • 隔离性 (isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
    • 持久性 (durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该收到影响。通常情况下,事务的结果被写到持久化存储器中。
2、Spring 中的事务管理
  • 作为企业级应用程序框架,Spring 在不同的事务管理 API 之上定义了一个抽象层。而应用程序开发人员不必了解底层的事务管理 API,就可以使用 Spring 的事务管理机制。
  • Spring 既支持编程式的事务管理,也支持声明式的事务管理。
  • 编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码。
  • 声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过 AOP 方法模块化。Spring通过 Spring AOP 框架支持声明式事务管理。
3、Spring 中的事务管理器
  • Spring 从不同的事务管理 API 中抽象了一整套的事务机制。开发人员不必了解底层的事务 API,就可以利用这些事务机制。有了这些事务机制,事务管理代码就能独立于特定的事务技术了。
  • Spring 的核心事务管理抽象是 Interface PlatformTransactionManager 管理封装了一组独立于技术的方法。无论使用 Spring 的哪种事务管理策略 (编程式或声明式),事务管理器都是必须的。
  • Spring 中的事务管管理器的不同实现

    • Class DataSourceTransactionManager:在应用程序中主需要处理一个数据源,而且通过 JDBC 存取

      配置事务管理器:

      <!-- 配置事务管理器 -->
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"/>
      </bean>
      
      <!-- 启用事务注解 -->
      <tx:annotation-driven transaction-manager="transactionManager"/>

      添加事务注解 @Transactional:

      @Repository("bookShopService")
      public class BookShopServiceImpl implements BookShopService {
      
          @Autowired
          private BookShopDao bookShopDao;
      
          //添加事务注解
          @Transactional
          @Override
          public void purchase(String isbn, String username) {
              //1.获取书的单价
              int price = bookShopDao.findBookPriceByIsbn(isbn);
      
              //2.更新书的库存
              bookShopDao.updateBookStock(isbn);
      
              //3.更新用户余额
              bookShopDao.updateUserAccount(username, price);
      
          }
      
      }
    • Class JtaTransactionManager:在 JavaEE 应用服务器上用 JTA (Java Transaction API) 进行事务管理
    • Class HibernateTransactionManager:用 Hibernate 框架存取数据库
    • ……
    • 事务管理器以普通的 Bean 形式声明在 Spring IOC 容器中
4、事务的传播属性
  • 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新的事务,并在自己的事务中运行。
  • 事务的传播行为可以由 传播属性指定。Spring 定义了 7 中类传播行为。
  • 传播属性描述
    REQUIRED如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。
    REQUIRED_NEW当前的方法必须启动新事务,并在它自己的事务给运行。如果有事务正在运行,应该将它挂起。
    SUPPORTS如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中。
    NOT_SUPPORTED当前的方法不应该运行在事务中,如果有运行的事务,将它挂起。
    MANDATORY当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常。
    NEVER当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常。
    NESTED如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行。
    • REQUIRED 传播行为

      当 bookService 的 purchase() 方法被另一个事务方法 checkout() 调用时,它默认会在现有的事务内运行。这个默认的传播行为就是 REQUIRED。 因此在 checkout() 方法的开始和终止边界内只有一个事务。这个事务只在 checkout() 方法结束的时候被提交,结果用户一本书都买不了。

      事务传播属性可以在 @Transactional 注解的 propagation 属性中定义。

    • REQUIRES_NEW 传播行为

      另一种常见的传播行为是 REQUIRES_NEW。它表示该方法必须启动一个新事物,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

5、事务的其他属性
  • 隔离级别

    • 使用 ioslation 指定事务的隔离级别,最常用的取值为 READ_COMMITTED。
  • 回滚

    • 默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下取默认值即可。
  • 只读

    • 使用 readOnly 指定事务是否为只读。
    • 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。
    • 若真的只是一个只读取数据库值的方法,应设置 readOnly = true。
  • 过期

    • 使用 timeout 指定强制回滚之前事务可以占用的时间。
    • 单位是 秒(s)。
6、基于 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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx" 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/context http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!-- 导入资源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>

    <!-- 配置 c3p0 数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>

        <property name="initialPoolSize" value="${jdbc.initPoolSize}"/>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
    </bean>

    <!-- 配置 Spring 的 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置 bean -->
    <bean id="bookShopDao" class="com.spring.tx.xml.BookShopDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    <bean id="bookShopService" class="com.spring.tx.xml.service.impl.BookShopServiceImpl">
        <property name="bookShopDao" ref="bookShopDao"/>
    </bean>
    <bean id="cashier" class="com.spring.tx.xml.service.impl.CashierImpl">
        <property name="bookShopService" ref="bookShopService"/>
    </bean>

    <!-- 1.配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 2.配置事务属性 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 根据方法名指定事务的属性 -->
            <tx:method name="purchase" propagation="REQUIRES_NEW"/>
            <!-- 通常情况下,对于 get 或者 find 开头的方法 read-only 的值为 true -->
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 -->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.spring.tx.xml.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>

</beans>
7、Spring 整合 Hibernate
  • Spring 整合 Hibernate 整合什么?

    • 由 IOC 容器来管理 Hibernate 的 SessionFactory
    • 让 Hibernate 使用上 Spring 的声明式事务
  • 整合步骤:

    • 加入 hibernate

      • 导入 jar 包

        <dependency>
                <groupId>org.jboss.logging</groupId>
                <artifactId>jboss-logging</artifactId>
                <version>3.1.0.GA</version>
            </dependency>
            <dependency>
                <groupId>org.jboss.spec.javax.transaction</groupId>
                <artifactId>jboss-transaction-api_1.1_spec</artifactId>
                <version>1.0.1.Final</version>
            </dependency>
            <dependency>
                <groupId>org.javassist</groupId>
                <artifactId>javassist</artifactId>
                <version>3.15.0-GA</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate.javax.persistence</groupId>
                <artifactId>hibernate-jpa-2.0-api</artifactId>
                <version>1.0.1.Final</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>4.2.4.Final</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate.common</groupId>
                <artifactId>hibernate-commons-annotations</artifactId>
                <version>4.0.2.Final</version>
            </dependency>
            <dependency>
                <groupId>dom4j</groupId>
                <artifactId>dom4j</artifactId>
                <version>1.6.1</version>
            </dependency>
            <dependency>
                <groupId>antlr</groupId>
                <artifactId>antlr</artifactId>
                <version>2.7.7</version>
            </dependency>
      • 添加 hibernate 的配置文件:hibernate.cfg.xml

        <?xml version='1.0' encoding='utf-8'?>
        <!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD//EN"
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
        <hibernate-configuration>
          <session-factory>
            <!-- 配置 hibernate 的基本信息 -->
            <!-- 1. 数据源需配置到 IOC 容器中,所以在此处不需要再配置数据源 -->
            <!-- 2. 关联的 .hbm.xml 也在 IOC 容器配置 SessionFactory 实例时再进行配置 -->
            <!-- 3. 配置 hibernate 的基本属性:方言,SQL 显示及格式化,生成数据表的策略以及二级缓存等 -->
            <property name="hibernate.dialect"> org.hibernate.dialect.MySQL5InnoDBDialect </property>
        
            <property name="hibernate.show_sql"> true </property>
            <property name="hibernate.format_sql"> true </property>
        
            <property name="hibernate.hbm2ddl.auto"> update </property>
        
            <!-- 配置 hibernate 二级缓存相关的属性 -->
        
            
          </session-factory>
        </hibernate-configuration>
  • 加入 Spring
  • 整合

Spring 对 JDBC 的支持

  1. 创建资源文件 db.properties
    jdbc.user=root
    jdbc.password=jie13727507037
    jdbc.driverClass=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql:///spring4?serverTimezone=UTC
    
    jdbc.initPoolSize=5
    jdbc.maxPoolSize=10
  2. 在 xml 文件中导入资源文件
    <!-- 导入资源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!-- 配置 c3p0 数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
    
        <property name="initialPoolSize" value="${jdbc.initPoolSize}"/>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
    </bean>
  3. 配置 JdbcTemplate
    <!-- 配置 Spring 的 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
  4. java 代码
    package com.spring.jdbc;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.RowMapper;
    
    import javax.sql.DataSource;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;
    
    public class JDBCTest {
    
        private ApplicationContext app = null;
        private JdbcTemplate jdbcTemplate;
        private EmployeeDao employeeDao;
        private DepartmentDao departmentDao;
    
        {
            app = new ClassPathXmlApplicationContext("applicationContext.xml");
            jdbcTemplate = (JdbcTemplate) app.getBean("jdbcTemplate");
            employeeDao = (EmployeeDao) app.getBean("employeeDao");
            departmentDao = (DepartmentDao) app.getBean("departmentDao");
        }
    
        @Test
        public void testDepartmentDao() {
            System.out.println(departmentDao.get(2));
        }
    
        @Test
        public void testEmployeeDao() {
            System.out.println(employeeDao.get(1));
        }
    
        /**
         * 获取单个列的值,或做统计查询
         * 使用 queryForObject(String sql, Class<Long> requiredType) 方法
         */
        @Test
        public void testQueryForObject2() {
            String sql = "SELECT count(id) FROM employees";
            long count = jdbcTemplate.queryForObject(sql, Long.class);
    
            System.out.println(count);
        }
    
        /**
         * 查到实体类的集合
         * 注意调用的不是 queryForList 方法
         */
        @Test
        public void testQueryForList() {
            String sql = "SELECT id, last_name lastName, email FROM employees WHERE id > ?";
            RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
            List<Employee> employees = jdbcTemplate.query(sql, rowMapper, 5);
    
            System.out.println(employees);
        }
    
        /**
         * 从数据库中获取一条记录,实际得到对应的一个对象
         * 注意不是调用 queryForObject(String sql, Class<Employee> requiredType, Object... args) 方法
         * 而需要调用 queryForObject(String sql, RowMapper<Employee> rowMapper, Object... args)
         * 1.其中的 RowMapper 指定如何去映射结果集的行,常用的实现类为 BeanPropertyRowMapper
         * 2.使用 SQL 中的别名完成列名和类的属性名的映射,例如 last_name laseName
         * 3.不支持级联属性,JdbcTemplate 到底是一个 JDBC 的小工具,而不是 ORM 框架
         */
        @Test
        public void testQueryForObject() {
            String sql = "SELECT id, last_name lastName, email FROM employees WHERE id = ?";
            RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
            Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
    
            System.out.println(employee);
        }
    
        /**
         * 执行批量更新:批量 INSERT,UPDATE,DELETE
         * 最后一个参数是 Object[] 的 List 类型:因为修改一条记录需要一个 Object 的数组,那么多条就需要多个 Object 的数组
         */
        @Test
        public void testBatchUpdate() {
            String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(?,?,?)";
            List<Object[]> batchArgs = new ArrayList<Object[]>();
    
            batchArgs.add(new Object[]{"AA", "aa@123.com", 1});
            batchArgs.add(new Object[]{"BB", "bb@123.com", 2});
            batchArgs.add(new Object[]{"CC", "cc@123.com", 3});
            batchArgs.add(new Object[]{"DD", "cc@123.com", 3});
            batchArgs.add(new Object[]{"EE", "ee@123.com", 2});
    
            jdbcTemplate.batchUpdate(sql, batchArgs);
        }
    
        /**
         * 执行 INSERT,UPDATE,DELETE
         */
        @Test
        public void testUpdate() {
            String sql = "UPDATE employees SET last_name = ? WHERE id = ?";
            jdbcTemplate.update(sql, "Jack", 5);
        }
    
        @Test
        public void testDataSource() throws SQLException {
    
            DataSource dataSource = (DataSource) app.getBean("dataSource");
            System.out.println(dataSource.getConnection());
    
        }
    
    }
    
    package com.spring.jdbc;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.jdbc.core.support.JdbcDaoSupport;
    import org.springframework.stereotype.Repository;
    
    import javax.sql.DataSource;
    
    /**
     * 不推荐使用 JdbcDaoSupport,而推荐使用 JdbcTemplate 作为 Dao 类的成员变量
     */
    
    @Repository
    public class DepartmentDao extends JdbcDaoSupport {
    
        @Autowired
        public void setDataSource2(DataSource dataSource) {
            setDataSource(dataSource);
        }
    
        public Department get(Integer id) {
            String sql = "SELECT id, dept_name name FROM departments WHERE id = ?";
            RowMapper<Department> rowMapper = new BeanPropertyRowMapper<>(Department.class);
            Department department = getJdbcTemplate().queryForObject(sql, rowMapper, id);
    
            return department;
        }
    
    }
    
    package com.spring.jdbc;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class EmployeeDao {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        public Employee get(Integer id) {
            String sql = "SELECT id, last_name lastName, email FROM employees WHERE id = ?";
            RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
            Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, id);
    
            return employee;
        }
    }
    
  5. 在 JDBC 模板中使用具名参数
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate = (NamedParameterJdbcTemplate) app.getBean("namedParameterJdbcTemplate");
    /**
     * 可以为参数起名字,
     * 1.好处:若有多个参数则不用再去对应位置,直接对应参数名,便于维护
     * 2.缺点:较为麻烦
     */
    @Test
    public void testNamedParameterJdbcTemplate() {
        String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:ln, :email, :deptid)";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("ln", "FF");
        paramMap.put("email", "ff@123.com");
        paramMap.put("deptid", 2);
        namedParameterJdbcTemplate.update(sql, paramMap);
    }
    /**
     * 使用具名参数时,可以使用 update(String sql, SqlParameterSource paramSource) 方法进行更新操作
     * 1. sql 语句中的参数名和类的属性一致
     * 2.使用 SqlParameterSource 的 BeanPropertySqlParameterSource 实现类作为参数
     */
    @Test
    public void testNamedParameterJdbcTemplate2() {
        String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:lastName, :email, :deptId)";
    
        Employee employee = new Employee();
        employee.setLastName("XZJ");
        employee.setEmail("xzj@95.com");
        employee.setDeptId(1);
    
        SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(employee);
    
        namedParameterJdbcTemplate.update(sql, parameterSource);
    }

Spring AOP (面向切面编程)

1、AOP 前奏
package com.spring.aop.helloworld;

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    public int add(int i, int j) {
        System.out.println("The method add begins with[" + i + "," + j + "]");
        int result = i + j;
        System.out.println("The method add ends with " + result);
        return result;
    }

    public int sub(int i, int j) {
        System.out.println("The method sub begins with[" + i + "," + j + "]");
        int result = i - j;
        System.out.println("The method sub ends with " + result);
        return result;
    }

    public int mul(int i, int j) {
        System.out.println("The method mul begins with[" + i + "," + j + "]");
        int result = i * j;
        System.out.println("The method mul ends with " + result);
        return result;
    }

    public int div(int i, int j) {
        System.out.println("The method div begins with[" + i + "," + j + "]");
        int result = i / j;
        System.out.println("The method div ends with " + result);
        return result;
    }

}

问题:

  • 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
  • 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。
2、使用动态代理解决上述问题
  • 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始的对象上。

    public class ArithmeticCalculatorLoggingProxy {
    
        //要代理的对象
        private ArithmeticCalculator target;
    
        public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
            this.target = target;
        }
    
        public ArithmeticCalculator getLoggingProxy() {
            ArithmeticCalculator proxy = null;
    
            //代理对象由哪一个类加载器负责加载
            ClassLoader loader = target.getClass().getClassLoader();
            //代理对象的类型,即其中有哪些方法
            Class [] interfaces = new Class[]{ArithmeticCalculator.class};
            //当调用代理对象其中的方法时,该执行的代码
            InvocationHandler h = new InvocationHandler() {
                /**
                 * @param proxy:正在返回的那个代理对象,一般情况下,在 invoke 方法中都不使用该对象
                 * @param method:正在被调用的方法
                 * @param args:调用方法时传入的参数
                 * @return
                 * @throws Throwable
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
                    String methodName = method.getName();
                    //日志
                    System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
                    //执行方法
                    Object result = method.invoke(target, args);
                    //日志
                    System.out.println("The method " + methodName + " ends with " + result);
                    return result;
                }
            };
    
            proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
    
            return proxy;
        }
    
    }
    
    
    
    public class Main {
    
        public static void main(String[] args) {
    
            //ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculatorLoggingImpl();
    //        arithmeticCalculator = new ArithmeticCalculatorImpl();
    
            ArithmeticCalculator target = new ArithmeticCalculatorImpl();
            ArithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();
    
            int result = proxy.add(1, 2);
            System.out.println("-->" + result);
            result = proxy.div(4, 2);
            System.out.println("-->" + result);
    
        }
    
    }
3、AOP 简介
  • AOP (Aspect-OrientedProgramming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-OrientedProgramming,面向对象编程)的补充。
  • AOP 的主要编程对象是切面(aspect),而切面模块化横切关注点
  • 在应用 AOP 编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的对象(切面)里。
  • AOP 的好处:

    • 每个事物逻辑位于一个位置,代码不分散,便于维护和升级。
    • 业务模块更简洁,只包含核心业务代码。
  • image-20220216005157686
4、AOP 术语
  • 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
  • 通知(Advice):切面必须要完成的工作
  • 目标(Target):被通知的对象
  • 代理(Proxy):向目标对象应用通知之后创建的对象
  • 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmeticCalculator#add() 方法执行前的连接点,执行点为 ArithmeticCalculator#add();方位为该方法执行前的位置。
  • 切点(pointcut):每个类都拥有多个连接点;例如:ArithmeticCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
5、在 Spring 中启用 AspectJ 注解支持
  • 要在 Spring 应用中使用 Aspect 注解,必须在 classpath 下包含 AspectJ 类库:aopalliance.jar、aspectj.weaver,jar 和 spring-aspects.jar
  • 将 aopSchema 添加到 <beans> 根元素中。
  • 要在 Spring IOC 容器中启用 AspectJ注解支持,只要在 Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy> 
  • 当 Spring IOC 容器侦测到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素时,会自动为与 AspectJ 切面匹配的 Bean 创建代理。
6、使用 SpringAOP 步骤
  1. 加入 jar 包:

    <dependencies>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
    </dependencies>
  2. 在配置文件中加入 aop 的命名空间

    <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"
           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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  3. 基于注解的方式

    • 在配置文件中加入如下配置

      <!-- 使 AspectJ 注解起作用:自动为匹配的类生成代理对象-->
      <aop:aspectj-autoproxy/>
    • 把横切关注点的代码抽象到切面的类中。

      • 切面首先是一个 IOC 中的 bean ,即加入 @Component 注解
      • 切面还需要加入 @Aspect 注解
      • //把这个类声明为一个切面:需要把该类放入到 IOC 容器中、再声明为一个切面
        @Aspect
        @Component
        public class LoggingAspect {
        
            //声明该方法是一个前置通知:在目标方法开始之前执行
            @Before("execution(public int com.spring.aop.impl.ArithmeticCalculator.*(int, int))")
            public void beforeMethod(JoinPoint joinPoint) {
                String methodName = joinPoint.getSignature().getName();
                List<Object> args = Arrays.asList(joinPoint.getArgs());
        
                System.out.println("The method " + methodName + " begins with " + args);
            }
        
    • 在类中声明各种通知

      AspectJ 支持 5 种类型的通知注解:

      • @Before:前置通知,在方法执行之前执行
      • @After:后置通知,在方法执行之后执行
      • @AfterRunning:返回通知,在方法返回结果之后执行

        @AfterThrowing:异常通知,在方法抛出异常之后执行

      • @Around:环绕通知,围绕着方法执行
    • 可以在通知方法中声明一个类型为 JoinPoint 的参数,然后就能访问链接细节,如方法名称和参数值。

      //声明该方法是一个前置通知:在目标方法开始之前执行
      @Before("execution(public int com.spring.aop.impl.ArithmeticCalculator.*(int, int))")
      public void beforeMethod(JoinPoint joinPoint) {
          String methodName = joinPoint.getSignature().getName();
          List<Object> args = Arrays.asList(joinPoint.getArgs());
      
          System.out.println("The method " + methodName + " begins with " + args);
    
    ##### 7、AspectJ 支持 5 种类型的通知注解:
  4. @Before:前置通知,在方法执行之前执行

    /**
    * 在 com.spring.aop.ArithmeticCalculator 接口的每一个实现类的每一个方法开始之前执行一段代码
    * @param joinPoint
    */
    @Before("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
    public void beforeMethod(JoinPoint joinPoint) {
     String methodName = joinPoint.getSignature().getName();
     List<Object> args = Arrays.asList(joinPoint.getArgs());
    
     System.out.println("The method " + methodName + " begins with " + args);
    }
  5. @After:后置通知,在方法执行之后执行,即连接点返回结果或者抛出异常的时候,下面的后置通知记录了方法的终止。

    /**
    * 在方法执行之后执行的代码,无论该方法是否出现异常
    * @param joinPoint
    */
    @After("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
     String methodName = joinPoint.getSignature().getName();
    
     System.out.println("The method " + methodName + " ends.");
    }
  6. @AfterRunning:返回通知,在方法返回结果之后执行

    /**
    * 在方法正常结束后执行的代码
    * 返回通知是可以访问到方法的返回值的
    */
    @AfterReturning(value = "execution(public int com.spring.aop.ArithmeticCalculator.*(..))", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
     String methodName = joinPoint.getSignature().getName();
    
     System.out.println("The method " + methodName + " ends with " + result);
    }
  7. @AfterThrowing:异常通知,在方法抛出异常之后执行

    /**
    * 在目标方法出现异常时会执行的代码
    * 可以访问到异常对象;且可以指定在出现特定异常时再执行通知代码
    * @param joinPoint
    * @param ex
    */
    @AfterThrowing(value = "execution(public int com.spring.aop.ArithmeticCalculator.*(..))", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
     String methodName = joinPoint.getSignature().getName();
    
     System.out.println("The method " + methodName + " occurs exception: " + ex);
    }
  8. @Around:环绕通知,围绕着方法执行

    /**
    * 环绕通知需要携带 ProceedingJoinPoint 类型的参数
    * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 这个类型的参数可以决定是否执行目标方法
    * 且环绕通知必须有返回值,返回值即为目标方法的返回值
    * @param pjd
    */
    @Around("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
    public Object aroundMethod(ProceedingJoinPoint pjd) {
     Object result = null;
     String methodName = pjd.getSignature().getName();
    
     //执行目标方法
     try {
         //前置通知
         System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
         result = pjd.proceed();
         //返回通知
         System.out.println("The method " + methodName + " ends with " + result);
     } catch (Throwable ex) {
         //异常通知
         System.out.println("The method " + methodName + " occurs exception: " + ex);
     }
     //后置通知
     System.out.println("The method " + methodName + " ends.");
    
     return result;
    }
    8、指定切面的优先级
  9. 在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。
  10. 切面的优先级可以通过实现 Ordered 接口或者利用 @Order 注解指定。
  11. 实现 Ordered 接口,getOrder() 方法的返回值越小,优先级越高。
  12. 若使用 @Order 注解,序号出现在注解中。

    /**
    * 可以使用 @Order 注解指定切面的优先级,值越小优先级越高
    */
    
    @Order(1)
    @Aspect
    @Component
    public class VlidationAspect {
    
     @Before("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
     public void validateArgs(JoinPoint joinPoint) {
         System.out.println("validate: " + Arrays.asList(joinPoint.getArgs()));
     }
    
    }
    
    9、重用切点表达式

    使用 @PointCut 来声明切入点表达式。

    后面的其他通知直接使用方法名来引用当前的切入点表达式。

    /**
     * 定义一个方法,用于声明切入点表达式。一般地,该方法不再需要舔入其他代码
     */
    @Pointcut("execution(public int com.spring.aop.ArithmeticCalculator.*(..))")
    public void declareJoinPointExpression() {}

@Before("declareJoinPointExpression()")
public void validateArgs(JoinPoint joinPoint) {

System.out.println("validate: " + Arrays.asList(joinPoint.getArgs()));

}


##### 10、基于配置文件的方法来配置 AOP

<?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">

<!-- 配置 bean -->
<bean id="arithmeticCalculator" class="com.spring.xml.ArithmeticCalculatorImpl"/>

<!-- 配置切面的 bean -->
<bean id="loggingAspect" class="com.spring.xml.LoggingAspect"/>
<bean id="vlidationAspect" class="com.spring.xml.VlidationAspect"/>

<!-- 配置 AOP -->
<aop:config>
    <!-- 配置切点表达式 -->
    <aop:pointcut id="pointcut" expression="execution(public int com.spring.xml.ArithmeticCalculator.*(..))"/>
    <!-- 配置切面及通知 -->
    <aop:aspect ref="loggingAspect" order="2">
        <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
        <aop:after method="afterMethod" pointcut-ref="pointcut"/>
        <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
        <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
        <aop:around method="aroundMethod" pointcut-ref="pointcut"/>
    </aop:aspect>

    <aop:aspect ref="vlidationAspect" order="1">
        <aop:before method="validateArgs" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

基于注解的方式

1、在 classpath 中扫描组件
  • 组件扫描(component scanning):Spring 能够从 classpath 下自动扫描,侦测和实例化具有特定注解的组件。
  • 特定组件包括:

    • @Component:基本注解,标识了一个受 Spring 管理的组件
    • @Respository:标识持久层组件
    • @Service:标识服务层(业务层)组件
    • @Controller:标识表现层组件
  • 对于扫描到的组件, Spring 有默认的命名策略:使用非限定类名,第一个字母小写;也可以在注解中通过 value 属性值标识组件的名称。
  • 当在组件类上使用了特定的注解之后,还需要在 Spring 的配置文件中声明 <context:component-scan> :

    • base-package 属性指定一个需要扫描的基类包,Spring 容器将会扫描这个基类包里及其子包中的所有类。
    • 当需要扫描多个包时,可以使用逗号分隔。
    • 如果仅希望扫描特定的类而非基包下的所有类,可使用 resource-pattern 属性过滤特定的类,示例:

      <context:component-scan
          base-package="com.spring.beans"
          resource-pattern="autowire/*.class"/>
    • <context:include-filter> 子节点表示要包含的目标类
    • <context:exclude-filter> 子节点表示要排除在外的目标类
    • <context:component-scan> 下可以拥有若干个<context:include-filter> 和 <context:exclude-filter> 子节点。
    • context:include-filter过滤器有五种type:

      • annotation-指定扫描使用某个注解的类
      • assignable-指定扫描某个接口派生出来的类
  • 代码:

    java 代码:

    @Component
    public class TestObject {
    
    }
    
    public interface UserRepository {
    
        void save();
    
    }
    
    @Repository("userRepository")
    public class UserRepositoryImpl implements UserRepository {
        @Override
        public void save() {
            System.out.println("UserRepository save ...");
        }
    }
    
    @Service
    public class UserService {
    
        public void add() {
            System.out.println("UserService add...");
        }
    
    }
    
    @Controller
    public class UserController {
    
        public void execute() {
            System.out.println("UserController execute...");
        }
    
    }
    
    public class Main11 {
    
        public static void main(String[] args) {
    
            ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-annotation.xml");
    
            TestObject to = (TestObject) ctx.getBean("testObject");
            System.out.println(to);
    
            UserController userController = (UserController) ctx.getBean("userController");
            System.out.println(userController);
    
            UserService userService = (UserService) ctx.getBean("userService");
            System.out.println(userService);
    
            UserRepositoryImpl userRepository = (UserRepositoryImpl) ctx.getBean("userRepository");
            System.out.println(userRepository);
    
        }
    
    }

    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"
           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 IOC 容器扫描包 -->
        <!-- 可以通过 resource-pattern 指定扫描的资源 -->
        <!--
        <context:component-scan
                base-package="com.spring.beans.annotation"
                resource-pattern="service/*.class"/>
        -->
    
        <!-- context:exclude-filter 子节点指定排除哪些指定表达式的组件 -->
        <!-- context:include-filter 子节点指定包含哪些表达式的组件,该子节点需要 use-default-filters 配合使用 -->
        <context:component-scan
                base-package="com.spring.beans.annotation"
                use-default-filters="true">
            <!--
            <context:exclude-filter type="annotation"
                                    expression="org.springframework.stereotype.Repository"/>
            -->
            <!--
            <context:include-filter type="annotation"
                                    expression="org.springframework.stereotype.Repository"/>
            -->
            <!--
            <context:exclude-filter type="assignable"
                                    expression="com.spring.beans.annotation.repository.UserRepository"/>
            -->
            <context:include-filter type="assignable"
                                    expression="com.spring.beans.annotation.repository.UserRepository"/>
        </context:component-scan>
    
    </beans>
2、组件装配

​ <context:component-scan> 元素还会自动注册 AutowiredAnnotationBeanPostProcessor 实例,该实例可以自动装配具有 @Autowired 和 @Resource、@Inject 注解的属性。

  • 使用 @Autowired 注解自动装配具有兼容类型的单个 Bean 属性

    • 构造器,普通字段(即使是非 public ),一切具有参数的方法都可以应用 @Autowired 注解
    • 默认情况下,所有使用 @Autowired 注解的属性都需要被设置。当 Spring 找不到匹配的 Bean 装配属性时,会抛出异常,若某一属性允许不被设置,可以设置 @Autowired 注解的 required 属性为 false

      @Repository("userRepository")
      public class UserRepositoryImpl implements UserRepository {
      
          @Autowired(required=false)
          private TestObject testObject;
      
          @Override
          public void save() {
              System.out.println("UserRepository save ...");
              System.out.println(testObject);
          }
      
      }
    • 默认情况下,当 IOC 容器里存在多个类型兼容的 Bean 时,通过类型的自动装配将无法工作,此时可以在 @Qualifier 注解里提供 Bean 的名称。Spring 允许对方法的入参标注 @Qualifier 已指定注入 Bean 的名称 ,也可以在声明 bean 的时候 bean 的名字跟这个属性的字段名相一致

      @Service
      public class UserService {
      
          private UserRepository userRepository;
      
      //    @Autowired
      //    @Qualifier("userRepository")
      //    public void setUserRepository(UserRepository userRepository) {
      //        this.userRepository = userRepository;
      //    }
      
          @Autowired
          public void setUserRepository(@Qualifier("userRepository") UserRepository userRepository) {
              this.userRepository = userRepository;
          }
      
          public void add() {
              System.out.println("UserService add...");
              userRepository.save();
          }
      
      }
    • @Autowired 注解也可以应用在数据类型的属性上,此时 Spring 将会把所有匹配的 Bean 进行自动装配。
    • @Autowired 注解也可以应用在集合属性上,此时 Spring 读取该集合的类型信息,然后自动装配所有与之兼容的 Bean。
    • @Autowired 注解用在 java.util.Map 上时,若该 Map 的键值为 String,那么 Spring 将自动装配与之 Map 值类型兼容的 Bean,此时 Bean 的名称作为键值。
  • 使用 @Resource 或 @Inject 自动装配 Bean

    • Spring 还支持 @Resource 和 @Inject 注解,这两个注解和 @Autowired 注解的功用类似
    • @Resource 注解需要提供一个 Bean 名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为 Bean 的名称
    • @Inject 和 @Autowired 注解一样也是按类型匹配注入的 Bean,但没有 required 属性
    • 建议使用 @Autowired 注解

泛型依赖注入

image-20220209233634373

配置 bean

主要内容:
  • 配置形式:基于 XML 文件的方式;基于注解的方式(基于注解配置 bean ;基于注解来装配 bean 的属性)。
  • Bean 的配置方式:通过全类名(反射)、通过工厂方法(静态工厂方法&实例工厂方法)、FactoryBean。
  • IOC 容器 BeanFactory & ApplicationContext 概述。
  • 依赖注入的方式:属性注入;构造器注入。
  • 注入属性值细节。
  • 自动装配。
  • bean 之间的关系:继承;依赖。
  • bean 的作用域:singleton;prototype;WEB 环境作用域。
  • 使用外部属性文件。
  • spEL。
  • IOC 容器中 Bean 的生命周期。
  • Spring 4.x 新特性:泛型依赖注入。

基于 XML 文件的方式:

1、在 Spring 的 IOC 容器里配置 Bean
  • 在 xml 文件中通过 bean节点来配置 bean。

    <!--
        配置 bean
        class:bean 的全类名,通反射的方式在 IOC 容器中创建 Bean,所以要求 Bean 中必须有无参构造方法
        id:标识容器中的 bean,id唯一。
    -->
    <bean id="helloWorld" class="com.spring.beans.HelloWorld">
            <property name="name" value="Spring"/>
    </bean>
  • id:Bean 的名称。

    • 在 IOC 容器中必须是唯一的
    • 若 id 没有指定,Spring 自动将权限定性类名作为 Bean 的名字
    • id 可以指定多个名字,名字直渐可用逗号、分号、或空格分隔
2、Spring 容器
  • Spring IOC 容器读取 Bean 配置创建 Bean 实例之前,必须对它进行实例化。只有在容器实例化后,才可以从 IOC 容器里获取 Bean 实例并使用。
  • Spring 提供了两种类型的 IOC 容器实现。

    • BeanFactory:IOC 容器的基本实现。
    • ApplicationContext:提供了更多的高级特性,是 BeanFactory 的子接口。
    • BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;

      ApplicationContext 面向使用 Spring 框架的开发者,几乎所有的应用场合都直接使用 ApplicationContext 而非底层的 BeanFactory。

    • 无论使用何种方式,配置文件时都相同的。
  • ApplicationContext

    • ApplicationContext 的主要实现类:

      ClassPathXmlApplicationContext:从类路径下加载配置文件

      — FileSystemXmlApplicationContext:从文件系统中加载配置文件

    • ConfigurableApplicationContext 扩展于 ApplicationContext,新增两个主要方法:refresh() 和 close(),让 ApplicationContext 具有启动、刷新和关闭上下文的能力
    • ApplicationContext 在初始化上下文时就实例化所有单例的 Bean。
    • WebApplicationContext 是专门为 WEB 应用而准备的,它允许从相对于 WEB 根目录的路径中完成初始化工作。
  • getBean() 方法

    参数有两种

    • 参数是 Bean 的 id;

      利用 id 定位到 IOC 容器中的 bean

      HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloWorld");
  • 参数是 类名.class;

    利用类型返回 IOC 容器中的 Bean,但要求 IOC 容器中只能有一个该类型的 Bean

    HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
3、依赖注入的方式
  • 属性注入

    • 属性注入即通过 setter 方法注入 Bean 的属性值或依赖的对象
    • 属性注入使用 <property> 元素,使用 name 属性指定 Bean 的属性名称,value 属性或 <value> 子节点指定属性值
    • 属性注入是实际应用中最常用的注入方式

      <bean id="helloWorld" class="com.spring.beans.HelloWorld">
              <property name="name" value="Spring"/>
      </bean>
  • 构造方法注入

    • 通过构造方法注入 Bean 的属性值或依赖的对象,它保证了 Bean 实例在实例化后就可以使用。
    • 构造器注入在 <constructor-arg> 元素里生命属性

      <constructor-arg> 中没有 name 属性

      public class Car {
      
          private String brand;
          private String corp;
          private double price;
          private int maxSpeed;
      
          @Override
          public String toString() {
              return "Car{" +
                      "brand='" + brand + '\'' +
                      ", corp='" + corp + '\'' +
                      ", price=" + price +
                      ", maxSpeed=" + maxSpeed +
                      '}';
          }
      
          public Car(String brand, String corp, double price) {
              this.brand = brand;
              this.corp = corp;
              this.price = price;
          }
      
          public Car(String brand, String corp, int maxSpeed) {
              this.brand = brand;
              this.corp = corp;
              this.maxSpeed = maxSpeed;
          }
      }
      <!--<bean id="car" class="com.spring.beans.Car">
          <constructor-arg value="Audi" index="0"/>
          <constructor-arg value="ShangHai" index="1"/>
          <constructor-arg value="300000" index="2"/>
      </bean>-->
      
      <bean id="car" class="com.spring.beans.Car">
          <constructor-arg value="Audi" type="java.lang.String"/>
          <constructor-arg value="ShangHai" type="java.lang.String"/>
          <constructor-arg value="300000" type="double"/>
      </bean>
      
      <bean id="car2" class="com.spring.beans.Car">
          <constructor-arg value="BaoMa" type="java.lang.String"/>
          <constructor-arg value="ShangHai" type="java.lang.String"/>
          <constructor-arg value="300000" type="int"/>
      </bean>

      其中 index 表示构造函数中参数的位置,type 表示类型。以区分重载的构造函数。

  • 工厂方法注入(很少使用,不推荐)
4、注入属性值的细节
  • 字面值:可用字符串表示的值,可以通过 <value> 元素标签或 value 属性进行注入。

    <bean id="car2" class="com.spring.beans.Car">
        <constructor-arg value="BaoMa" type="java.lang.String"/>
        <constructor-arg value="ShangHai" type="java.lang.String"/>
        <constructor-arg type="int">
            <value>250</value>
        </constructor-arg>
    </bean>
  • 基本数据类型及其封装类、String 等类型都可以采取字面值注入方式。
  • 若字面值中包含特殊字符,可以使用 <![CDATA[]]> 把字面值包裹起来。

    <bean id="car2" class="com.spring.beans.Car">
        <constructor-arg value="BaoMa" type="java.lang.String"/>
        <constructor-arg type="java.lang.String">
            <value><![CDATA[<ShangHai^>]]></value>
        </constructor-arg>
        <constructor-arg type="int">
            <value>250</value>
        </constructor-arg>
    </bean>
5、引用其它 Bean
  • 组成应用程序的 Bean 经常需要相互协作以完成应用程序的功能,要使 Bean 能够相互访问,就必须在 Bean 配置文件中指定对 Bean 的引用。
  • 在 Bean 的配置文件中,可以通过 <ref> 或 ref 属性为 Bean 的属性或构造器参数指定对 Bean 的引用。

    <bean id="person" class="com.spring.beans.Person">
        <property name="name" value="Tom"/>
        <property name="age" value="24"/>
        <!--<property name="car" ref="car2"/>-->
        <property name="car">
            <ref bean="car2"/>
        </property>
    </bean>
  • 也可以在属性或构造器里包含 Bean 的声明,这样的 Bean 称为內部 Bean

    在属性内使用內部 Bean:

    <bean id="person" class="com.spring.beans.Person">
        <property name="name" value="Tom"/>
        <property name="age" value="24"/>
        <!--<property name="car" ref="car2"/>-->
        <!--<property name="car">-->
        <!--    <ref bean="car2"/>-->
        <!--</property>-->
        <property name="car">
            <bean class="com.spring.beans.Car">
                <constructor-arg value="Ford"/>
                <constructor-arg value="ChangAn"/>
                <constructor-arg value="200000" type="double"/>
            </bean>
        </property>
    </bean>

    在构造器内使用內部 Bean:

    public Person() {
    
    }
    
    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }
    <bean id="person2" class="com.spring.beans.Person">
        <constructor-arg value="Jerry"/>
        <constructor-arg value="25"/>
        <constructor-arg ref="car"/>
    </bean>

    !注意:当 Bean实例仅仅给一个特定的属性使用时,可以将其声明为內部 Bean。內部 Bean 声明直接包含在 <property> 或 <constructor-arg> 元素里,不需要设置任何 id 或 name 属性。內部 Bean 不能使用在其他任何地方。

6、注入参数详解:null 值和级联属性
  • 可以使用专用的 <null/> 元素标签为 Bean 的字符串或其它对象类型的属性注入 bull 值

    <bean id="person2" class="com.spring.beans.Person">
        <constructor-arg value="Jerry"/>
        <constructor-arg value="25"/>
        <!--<constructor-arg ref="car"/>-->
        <constructor-arg><null/></constructor-arg>
    </bean>
  • 和 Struts、Hiberante 等框架一样,Spring 支持级联属性的配置

    <bean id="person2" class="com.spring.beans.Person">
        <constructor-arg value="Jerry"/>
        <constructor-arg value="25"/>
        <!--<constructor-arg ref="car"/>-->
        <!--<constructor-arg><null/></constructor-arg>-->
        <constructor-arg ref="car2"/>
        <property name="car.maxSpeed" value="250"/>
    </bean>

    !注意:属性需要先初始化后才可以为级联属性赋值,否则会有异常,和 Struts2 不同。

7、集合属性
  • 在 Spring 中可以通过一组内置的 xml 标签(例如:<list> , <set> , <map> )来配置集合属性。
  • 配置 Java.util.List 类型的属性,需要指定 <list> 标签,在标签里包含一些元素。这些标签可以通过 <value> 指定简单的常量值,通过<ref> 指定对其他 Bean 的引用。通过 <bean> 指定内置 Bean 定义。通过 <null/> 指定空元素。甚至可以内嵌其他集合。
  • 数组的定义和 List 一样,都使用 <list> 。
  • 配置 java.util.Set 需要使用 <set> 标签,定义元素的方法与 List 一样。

    <!-- 使用 list 节点为 List 类型的属性赋值 -->
    <bean id="person3" class="com.spring.beans.collections.Person">
        <property name="name" value="Mike"/>
        <property name="age" value="27"/>
        <property name="cars">
            <list>
                <ref bean="car"/>
                <ref bean="car2"/>
            </list>
        </property>
    </bean>
    <!-- 使用 list 节点为 List 类型的属性赋值(內部 Bean) -->
    <bean id="person3" class="com.spring.beans.collections.Person">
        <property name="name" value="Mike"/>
        <property name="age" value="27"/>
        <property name="cars">
            <list>
                <ref bean="car"/>
                <ref bean="car2"/>
                <bean class="com.spring.beans.Car">
                    <constructor-arg value="Ford"/>
                    <constructor-arg value="ChangAn"/>
                    <constructor-arg value="200000" type="double"/>
                </bean>
            </list>
        </property>
    </bean>
  • Java.util.Map 通过 <map> 标签定义,<map> 标签里可以使用多个 <entry> 作为子标签。每个条目包含一个键和一个值。
  • 必须在 <key> 标签里定义键
  • 因为键和值的类型没有限制,所以可以自由地为它们指定 <value> ,<ref> ,<bean> 或 <null> 元素。
  • 可以将 Map 的键和值作为 <entry> 的属性定义;简单常量使用 key 和 value 来定义;Bean 引用通过 key-ref 和 value-ref 属性定义
  • 使用 <props> 定义 java.util.Properties,该标签使用多个 <prop> 作为子标签。每个 <prop> 标签必须定义 key 属性。

    <!-- 使用 map 节点及 map 的 entry 子节点配置 Map 类型的成员变量 -->
    <bean id="newPerson" class="com.spring.beans.collections.NewPerson">
        <property name="name" value="Rose"/>
        <property name="age" value="28"/>
        <property name="cars">
            <map>
                <entry key="AA" value-ref="car"/>
                <entry key="BB" value-ref="car2"/>
            </map>
        </property>
    </bean>
    
    <!-- 使用 props 和 prop 子节点来为 Properties 属性赋值 -->
    <bean id="dataSource" class="com.spring.beans.collections.DataSource">
        <property name="properties">
            <props>
                <prop key="user">root</prop>
                <prop key="password">1234</prop>
                <prop key="jdbcUel">jdbc:mysql:///test</prop>
                <prop key="driverClass">com.mysql.jdbc.Driver</prop>
            </props>
        </property>
    </bean>
8、使用 utility schema 定义集合
  • 使用基本的集合标签定义集合时,不能将集合作为独立的 Bean 定义,导致其他 Bean 无法引用该集合,所以无法在不同 Bean 之间共享集合。

    <!-- 配置独立的集合 bean,以供多个 bean 进行引用,需要导入 util 命名空间 -->
    <util:list id="cars">
        <ref bean="car"/>
        <ref bean="car2"/>
    </util:list>
    <bean id="person4" class="com.spring.beans.collections.Person">
        <property name="name" value="Jack"/>
        <property name="age" value="29"/>
        <property name="cars" ref="cars"/>
    </bean>
  • 可以使用 util schema 里的集合标签定义独立的集合 Bean 。需要注意的是,必须在 <beans> 根元素里添加 util schema 定义。
9、使用 p 命名空间
  • 为了简化 XML 文件的配置,越来越多的 XML 文件采用属性而非子元素配置信息。
  • Spring 从 2.5 版本开始引用了一个新的 p 命名空间,可以通过 <bean> 元素属性的方式配置 Bean 的属性。
  • 使用 p 命名空间后,基于 XML 的配置方式将进一步简化。

    <!-- 通过 p 命名空间为 bean 的属性赋值,需要先导入 p 命名空间,相对于传统的配置方式更加的简洁 -->
    <bean id="person5" class="com.spring.beans.collections.Person" p:name="Queen" p:age="30" p:cars-ref="cars"/>
10、XML 配置里的 Bean 自动装配
  • Spring IOC 容器可以自动装配 Bean。需要做的仅仅是在 <bean> 的 autowire 属性里指定自动装配的模式
  • byType(根据类型自动装配):若 IOC 容器中有多个与目标 Bean 类型一致的 Bean。在这种情况下,Spring 将无法判定哪个 Bean 最合适该属性,所以不能执行自动装配。
  • byName(根据名称自动装配):必须将目标 Bean 的名称和属性名设置的完全相同。
  • constructor(通过构造器自动装配):当 Bean 中存在多个构造器时,此种自动装配方式将会很复杂,不推荐使用

    <bean id="address" class="com.spring.beans.autowire.Address" p:city="Beijing" p:street="HuiLongGuan"/>
    
    <bean id="car" class="com.spring.beans.autowire.Car" p:brand="Audi" p:price="300000"/>
    
    <!--手工装配-->
    <bean id="person" class="com.spring.beans.autowire.Person" p:name="Tom" p:address-ref="address" p:car-ref="car"/>
    
    <!--自动装配-->
    <!--byName: 根据名字自动装配,要求 xml 文件里的 bean 的 id 与当前 bean 的 setter 风格的属性名进行自动装配,若有匹配的,则进行自动装配;若没有匹配的,则不装配。-->
    <bean id="person" class="com.spring.beans.autowire.Person" autowire="byName"/>
    <!--byType: 根据 bean 的类型和当前 bean 的属性的类型进行自动装配,若 IOC 容器中有一个以上的类型匹配的 bean,则抛异常。-->
    <bean id="person" class="com.spring.beans.autowire.Person" autowire="byType"/>

XML 配置里的 Bean 自动装配的缺点

  • 在 Bean 配置文件里设置 autowire 属性进行自动装配将会装配 Bean 的所有属性。然而,若只希望装配个别属性时,autowire 属性就不够灵活了。
  • autowire 属性要么根据类型自动装配,要么根据名称自动装配,不能两者兼而有之。
  • 一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力。
11、bean 之间的关系
  • 继承 Bean 的配置

    • Spring 允许继承 bean 的配置,被继承的 bean 称为父 bean。继承这个父 bean 的 bean 称为子 bean。
    • 子 Bean 从父 Bean中继承配置,包括 Bean 的属性配置
    • 子 Bean 也可以覆盖从父 Bean 继承过来的配置

      <bean id="address" class="com.spring.beans.autowire.Address" p:city="Beijing^" p:street="WuDaoKou"/>
      
      <!--bean 配置的继承:使用 bean 的 parent 属性指定继承哪个 bean 配置-->
      <!-- 三种例子 -->
      <bean id="address2" parent="address"/>
      <bean id="address2" p:street="DaZhongSi" parent="address"/>
      <bean id="address2" p:city="Beijing" p:street="DaZhongSi" parent="address"/>
    • 父 Bean 可以作为配置模板,也可以作为 Bean 实例。若只想把父 Bean 作为模板,可以设置 <bean> 的 abstract 属性为 true,这样 Spring 将不会实例化这个 Bean。

      <!-- abstract = true 时,该 bean 称为抽象 bean。这样的 bean 不能被 IOC 容器实例化,只用来被继承配置 -->
      <bean id="address" class="com.spring.beans.autowire.Address" p:city="Beijing^" p:street="WuDaoKou" abstract="true"/>
    • 并不是 <bean> 元素里的所有属性都会被继承。比如:autowire,abstract等。
    • 可以忽略父 Bean 的 class 属性,让子 Bean 指定自己的类,而共享相同的属性配置。但此时 abstract 必须设为 true。

      <!-- 若某一个 bean 的 class 属性没有指定,则该 bean 必须是一个抽象 bean -->
      <bean id="address" abstract="true" p:city="Beijing^" p:street="WuDaoKou"/>
      
      <!--bean 配置的继承:使用 bean 的 parent 属性指定继承哪个 bean 配置-->
      <bean id="address2" class="com.spring.beans.autowire.Address" parent="address"/>
  • 依赖 Bean 的配置

    • Spring 允许用户通过 depends-on 属性设定 Bean 前置依赖的 Bean,前置依赖的 Bean 会在本 Bean 实例化之前创建好。
    • 如果前置依赖于多个 Bean,则可以通过逗号,空格或的方式配置 Bean 的名称。
    • 在实际应用中实例化某个 bean 时,经常要在实例化该 bean 之前实例化另外一个 bean,因此产生了依赖关系。

      <!--要求再配置 Person 时,必须有一个关联的 Car。-->
      <bean id="person" class="com.spring.beans.autowire.Person"
            p:name="Tom" p:address-ref="address2" depends-on="car"/>
      <bean id="car" class="com.spring.beans.autowire.Car"
            p:brand="Audi" p:price="300000"/>

      注意:depends-on = ” car ” 只是指定了在获取 person 的 bean 时,要现在 IOC 容器中能获取 car 的 bean,但是并不是说就把 car 的 bean 注入到了 person 的 bean 中了,要想 person 用有 car 的 bean,需要在 person 的 bean 的配置中获取 car 的 bean。

12、bean 的作用域
  • 使用 bean 的 scope 属性来配置 bean 的作用域
  • singleton:默认值,容器初始化时创建 bean 实例,在整个容器的生命周期内只创建这一个 bean. 单例的。

    <bean id="car"
          class="com.spring.beans.autowire.Car"
          scope="singleton">
        <property name="brand" value="Audi"/>
        <property name="price" value="300000"/>
    </bean>
  • prototype:原型的,容器初始化时不创建 bean 的实例,而在每次请求时都创建一个新的 bean 实例,并返回。

    <bean id="car"
          class="com.spring.beans.autowire.Car"
          scope="prototype">
        <property name="brand" value="Audi"/>
        <property name="price" value="300000"/>
    </bean>
13、使用外部属性文件
  • 在配置文件里配置 bean 时,有时需要在 bean 的配置里混入系统部署的细节信息(例如:文件路径,数据源配置信息等)。而这些部署细节实际上需要和 bean 的配置相分离。
  • Spring 提供了一个 PropertyPlaceholderConfigurer 的 BeanFactory 后置处理器,这个处理器允许用户将 Bean 配置的部分内容外移到属性文件中。可以在 bean 配置文件里使用形式为 ${var} 的变量, PropertyPlaceholderConfigurer 从属性文件里加载属性,并使用这些属性来替换变量。
  • Spring 还允许在属性文件中使用 ${propName},以实现属性之间的相互吸引。

    xml 文件:

    <!--导入属性文件-->
    <context:property-placeholder location="db.properties"/>
    
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--使用外部化属性文件的属性-->
        <property name="user" value="${user}"/>
        <property name="password" value="${password}"/>
        <property name="driverClass" value="${driverClass}"/>
        <property name="jdbcUrl" value="${jdbcUrl}"/>
    </bean>

    properties 文件:

    user=root
    password=jie13727507037
    driverClass=com.mysql.cj.jdbc.Driver
    jdbcUrl=jdbc:mysql:///text?serverTimezone=UTC

    注意:mysql-connector-java 5版本之后的 driverClass 使用 com.mysql.cj.jdbc.Driver,同时还要在 jdbcUrl 后面加上 ?serverTimezone=UTC。

14、Spring 表达式语言:SpEL

Spring 表达式语言(简称 SpEL ):是一个支持运行时查询和操作对象图的强大表达式语言。
语法类似于 EL:SpEL 使用 #{...} 作为定界符,所有在大括号中的字符都将被认为是 SpEL。
SpEL 为 bean 的属性进行动态赋值提供了便利。

通过 SpEL 可以实现:

  • 赋字面值,如:int string Boolean double等类型的值

    <bean id="address" class="com.spring.beans.spel.Address">
        <!--使用 spel 为属性赋一个字面值-->
        <property name="city" value="#{'BeiJing'}"/>
        <property name="street" value="WuDaoKou"/>
    </bean>
  • 通过 bean 的 id 对 bean 进行引用

    • 引用其他对象
  • 调用方法以及引用对象中的属性

    • 引用其他对象的属性
    • 调用其他方法,还可以链式操作
    • 调用静态方法或静态属性:通过 T() 调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性

      <bean id="car" class="com.spring.beans.spel.Car">
          <property name="brand" value="Audi"/>
          <property name="price" value="500000"/>
          <!--使用 spel 引用类的静态属性-->
          <property name="tyrePerimeter" value="#{T(java.lang.Math).PI * 80}"/>
      </bean>
  • 计算表达式的值

    • 算术运算符:+,-,*,/,%,^;
    • 加号还可以用作字符串连接
    • 比较运算符:<,>,==,<=,>=;
    • 逻辑运算符:and,or,not
    • if-else(相当于三目运算符)运算符:?:(temary), ?:(Elvis)
    • if-else(相当于三目运算符)的变体
    • 正则表达式:matches
  • 正则表达式的匹配

    <bean id="person" class="com.spring.beans.spel.Person">
    
        <!--<property name="car" ref="car"/>-->
        <!--使用 spel 来引用其他的 bean-->
        <property name="car" value="#{car}"/>
        <!--使用 spel 来引用其他 bean 的属性-->
        <property name="city" value="#{address.city}"/>
        <!--在 spel 中使用运算符-->
        <property name="info" value="#{car.price > 300000 ? '金领' : '白领'}"/>
        <property name="name" value="Tom"/>
    </bean>
15、IOC 容器中 Bean 的生命周期方法
  • Spring IOC 容器可以管理 Bean 的生命周期,Spring 允许在 Bean 生命周期的特定点执行定制的任务。
  • Spring IOC 容器对 Bean 的生命周期进行管理的过程:

    • 通过构造器或工厂方法创建 Bean 实例
    • 为 Bean 的属性设置值和其他 Bean 的引用
    • 调用 Bean 的初始化方法
    • Bean 可以使用了
    • 当容器关闭时,调用 Bean 的销毁方法
  • 在 Bean 的声明里设置 init-method 和 destroy-method 属性,为 Bean 指定初始化和销毁方法。

    xml 代码:

    <bean id="car" class="com.spring.beans.cycle.Car"
          init-method="init" destroy-method="destroy">
        <property name="brand" value="Audi"/>
    </bean>

    java 代码:

    public static void main(String[] args) {
    
        //将 ctx 转换成 ClassPathXmlApplicationContext 类型才有关闭 IOC 容器的 close() 方法
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans-cycle.xml");
    
        Car car = (Car) ctx.getBean("car");
        System.out.println(car);
    
        //关闭 IOC 容器
        ctx.close();
    }

创建 bean 的后置处理器

  • Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理。
  • Bean 后置处理器对 IOC 容器里的所有 Bean 实例逐一处理,而非单一实例。其典型应用是:检查 Bean 属性的正确性或根据特定的标准更改 Bean 的属性。
  • 对 Bean 后置处理器而言,需要实现 Interface BeanPostProcessor 接口。在初始化方法被调用前后,Spring 将把每个 Bean 实例分别传递给上述接口的一下两个方法:

    • public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              return null;
      }
    • public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              return null;
      }
  • Spring IOC 容器对 Bean 的生命周期进行管理的过程:

    • 通过构造器或工厂方法创建 Bean 实例
    • 为 Bean 的属性设置值和其他 Bean 的引用
    • 将 Bean 实例传递给 Bean 后置处理器的 postProcessBeforeInitialization 方法
    • 调用 Bean 的初始化方法
    • 将 Bean 实例传递给 Bean 后置处理器的 postProcessAfterInitialization 方法
    • Bean 可以使用了
    • 当容器关闭时,调用 Bean 的销毁方法
    <!--
        实现 BeanPostProcessor 接口,并具体提供两个方法
        Object postProcessBeforeInitialization(Object bean, String beanName):init-method 之前被调用
        Object postProcessAfterInitialization(Object bean, String beanName):init-method 之后被调用
        的实现
    
        bean:bean实例本身
        beanName:IOC 容器中配置的 bean 的名字
        返回值:是实际上返回给用户的那个 bean,注意可以在以上两个方法中修改返回的 bean,甚至返回一个新的 bean
    -->
    <!-- 配置 bean 的后置处理器:不需要配置 id,IOC 容器自动识别是一个 BeanPostProcessor -->
    <bean class="com.spring.beans.cycle.MyBeanPostProcessor"/>
16、通过工厂方法配置 Bean

通过调用静态工厂方法创建 Bean

  • 调用静态工厂方法创建 Bean 是将对象创建的过程封装到静态方法中。当客户端需要对象时,只需要简单地调用静态方法,而不需要关心创建对象的细节。
  • 要声明通过静态方法创建的 Bean, 需要在 Bean 的 class 属性里指定拥有该工厂的方法的类,同时在 factory-method 属性里指定工厂方法的名称。最后,使用 <constructor-arg> 元素为该方法传递方法参数。

    /**
     * 静态工厂方法:直接调用某一个类的静态方法就可以返回 bean 的实例
     */
    public class StaticCarFactory {
    
        private static Map<String, Car> cars = new HashMap<String, Car>();
    
        static {
            cars.put("audi", new Car("audi", 300000));
            cars.put("ford", new Car("ford", 400000));
        }
    
        //静态工厂方法:
        public static Car getCar(String name) {
            return cars.get(name);
        }
    
    }
    <!--通过静态工厂方法来配置 bean, 注意不是配置静态工厂方法实例,而是配置 bean 实例-->
    <!--
        class 属性:指向静态工厂方法的全类名
        factory-method:指向静态工厂方法的名字
        constructor-arg:如果工厂方法需要传入参数,则使用 constructor-arg 来配置参数
    -->
    <bean id="car1"
          class="com.spring.beans.factory.StaticCarFactory"
          factory-method="getCar">
        <constructor-arg value="ford"/>
    </bean>

通过调用实例工厂方法创建 Bean

  • 实例工厂方法:将对象的创建过程封装到另外一个对象实例的方法里。当客户端需要请求对象时,只需要简单的调用该实例方法而不需要关心对象的创建细节。
  • 要声明通过实例工厂方法创建的 Bean

    • 在 bean 的 factory-bean 属性里指定拥有该工厂方法的 bean
    • factory-method 属性里指定该工厂方法的名称
    • 使用 constructor-arg 元素为工厂方法传递方法参数
    /**
     * 实例工厂方法:实例工厂的方法,即先需要创建工厂本身,再调用工厂的实例方法来返回 bean 的实例
     */
    public class InstanceCarFactory {
    
        private Map<String, Car> cars = null;
    
        public InstanceCarFactory() {
            cars = new HashMap<String, Car>();
            cars.put("audi", new Car("audi", 300000));
            cars.put("ford", new Car("ford", 00000));
        }
    
        public Car getCar(String brand) {
            return cars.get(brand);
        }
    
    }
    <!--配置工厂的实例-->
    <bean id="carFactory" class="com.spring.beans.factory.InstanceCarFactory"/>
    
    <!--通过实例工厂方法来配置 bean-->
    <!--
        factory-bean:指向实例工厂方法的bean
        factory-method:指向实例工厂方法的名字
        constructor-arg:如果工厂方法需要传入参数,则使用 constructor-arg 来配置参数
    -->
    <bean id="car2" factory-bean="carFactory" factory-method="getCar">
        <constructor-arg value="audi"/>
    </bean>
17、通过 FactoryBean 配置 Bean

CarFactoryBean 实现 FactoryBean 接口:

package com.spring.beans.factorybean;

import org.springframework.beans.factory.FactoryBean;

//自定义的 FactoryBean 需要实现 FactoryBean 接口
public class CarFactoryBean implements FactoryBean<Car> {

    private String brand;

    public void setBrand(String brand) {
        this.brand = brand;
    }

    //返回 bean 的对象
    @Override
    public Car getObject() throws Exception {
        return new Car(brand, 500000);
    }

    /**
     * 返回的 bean 的类型
     */
    @Override
    public Class<?> getObjectType() {
        return Car.class;
    }

    //该 bean 是否为单例的
    @Override
    public boolean isSingleton() {
        return true;
    }

}

FactoryBean 的 xml 文件:

<!--
    通过 FactoryBean 来配置 Bean 的实例
    class:指向 FactoryBean 的全类名
    property:配置 FactoryBean 的属性

    但实际返回的实例却是 FactoryBean 的 getObject() 方法返回的实例!
-->
<bean id="car" class="com.spring.beans.factorybean.CarFactoryBean">
    <property name="brand" value="BMW"/>
</bean>

IOC 和 DI

  1. IOC(Inversion of Control):其思想是反转资源获取的方向

    传统的资源查找方式要求组件向容器发起请求查找资源。作为回应,容器适时的返回资。而应用了 IOC 之后,则是容器主动地将资源推送给它所管理的组件,组件所要做的仅是选择一种合适的方式来接受资源。这种行为也被成伪查找的被动形式。(反转控制)

  2. DI(Dependency Injection)— IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如: setter 方法)接受来自如容器的资源注入。相对于 IOC 而言,这种表述更直接。(依赖注入)

Spring 初体验

  1. 配置 bean

id 表示 bean 的标识

class 表示 bean 对象的类目录路径

property 是给 name 参数赋值 Spring

<bean id="helloWorld" class="com.spring.beans.HelloWorld">
        <property name="name" value="Spring"/>
</bean>
  1. HelloWorld.java 类
public class HelloWorld {

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void hallo(){
        System.out.println("hello: " + name);
    }

}
  1. 运行主类 Main.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {

        /*HelloWorld helloWorld = new HelloWorld();
        helloWorld.setName("AZhang");*/

        //1. 创建 Spring 的 IOC 容器对象
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2. 从 IOC 容器中获取 bean 实例
        HelloWorld helloWorld = (HelloWorld) ctx.getBean("helloWorld");

        //调用 hello 方法
        helloWorld.hallo();

    }

}
  1. pom.xml 文件使用 Maven 抓包
<dependencies>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.1.1</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
</dependencies>

Spring结构

  1. 分层

    • Web 层:SpringMVC
    • Dao 层:Spring JDBCTemplate
    • 业务层:Spring 提供的声明式事务控制
  2. 优势

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

    • 导入 Spring 开发的基本包坐标
    • 创建 Bean
    • 创建 applicationContext.xml
    • 在配置文件中进行配置
    • 创建 ApplicationContext 对象 getBean
  4. 依赖注入

    Spring 框架核心 IOC 的具体实现。

  5. 数据源(连接池)作用

    • 数据源是提高程序性能出现的
    • 事先实例化数据源,初始化部分连接资源
    • 使用连接资源时从数据源中获取
    • 使用完毕后将连接资源归还给数据源

    常见的数据源:DBCP、C3P0、BoneCP、Druid

  6. 数据源开发步骤

    • 导入数据源的坐标和数据库驱动
    • 创建数据源对象
    • 设置数据源的基本连接数据
    • 使用数据源获取连接资源和归还连接资源
  7. Spring 集成 Junit 步骤

    • 导入 spring 集成 Junit 的坐标
    • 使用 @Runwith 注解替换原来的运行期
    • 使用 @ContextConfiguration 指定配置文件或配置类
    • 使用 @Autowired 注入需要测试的对象
    • 创建测试方法进行测试
  8. Spring 集成 Web 环境

    • 在 web.xml 中配置 ContextLoaderListener 监听器(导入 spring-web 坐标)
    • 使用 WebApplicationContextUtils 获得应用上下文 ApplicationContext
  9. SpringMVC 开发步骤

    • 导入 SpringMVC 包
    • 配置 SpringMVC 核心控制器 DispathcerServlet
    • 创建 Controller 类和视图页面
    • 使用注解配置 Controller 类中业务方法的映射地址
    • 配置 SpringMVC 核心文件 spring-mvc.xml (配置组件扫描)
    • 执行访问测试
  10. SpringMVC 的执行流程

    • 用户发送请求至前端控制器 DispatcherServlet
    • DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器
    • 处理器映射器找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet
    • DispatcherServlet 调用 HandlerAdapter 处理器适配器
    • HandlerAdapter 经过适配调用具体的处理器( Controller,也叫后端控制器)
    • Controller 执行完成返回 ModelAndView
    • HandlerAdapter 将 Controller 执行结果 ModelAndView 返回给 DispatcherServlet
    • DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器
    • ViewReslover 解析后返回具体 View
    • DispatcherServlet 根据 View 进行进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet 响应用户
  11. SpringMVC 的相关组件

    • 前端控制器:DispatcherServlet
    • 处理器映射器:HandlerMapping
    • 处理器适配器:HandlerAdapter
    • 处理器:Handler
    • 视图解析器:ViewReslover
    • 视图:View
  12. SpringMVC 的注解和配置

  13. SpringMVC 的数据响应方式

    • 页面跳转:

      • 直接返回字符串

        此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转

      • 通过 ModelAndView 对象返回
    • 回写数据

      • 直接返回字符串

        将需要回写的字符串直接返回,但此时需要通过 @ResponseBody 注解告知 SpringMVC 框架,方法返回的字符串不是跳转是直接在 http 响应体中返回。

        @ResponseBody // 告知 SpringMVC 框架,该方法不进行视图跳转 直接进行数据响应

      • 返回对象或集合

        image-20220608113654146

  14. SpringMVC 获取请求数据

    • 获得基本参数

      Controller 中的业务方法的参数名称要与请求参数的 name 一致,参数值会自动映射匹配。

    • 获得 POJO 类型参数

      Controller 中的业务方法的 POJO 参数的属性名与参数的 name 一致,参数会自动映射匹配。

    • 获得数组类型参数

      Controller 中的业务方法数组名称与请求参数的 name 一致,参数会自动映射匹配。

    • 获取集合类型参数
      • 获取集合参数时,要将集合参数包装到一个 POJO 中才可以。
      • 当时用 ajax 提交时,可以指定 contentType 为 json 形式,那么在方法参数位置使用 @RequestBody 可以直接接收集合数据而无需使用 POJO 进行包装。
    • 开放静态资源访问
      <mvc:default-servlet-handler/>
    • 请求数据乱码问题

      当 post 请求时,数据会出现乱码,我们可以设置一个过滤器来进行编码的过滤。

      <!-- 配置全局过滤的 filter -->
        <filter>
          <filter-name>CharacterEncodingFilter</filter-name>
          <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
          <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
          </init-param>
        </filter>
        <filter-mapping>
          <filter-name>CharacterEncodingFilter</filter-name>
          <url-pattern>/*</url-pattern>
        </filter-mapping>
    • 参数绑定注解 @RequestParam

      当请求的参数名称与 Controller 的业务方法参数名称不一致时,就需要通过 @RequestParam 注解显示的绑定。

      @RequestParam 的使用

      • value: 与请求参数名称
      • required: 此在指定的请求参数是否必须包括,默认是 true,提交时如果没有此参数则报错。
      • defaultValue: 当没有指定请求参数时,则使用指定的默认值赋值。
    • 获得 Restful 风格的参数

      Restful 风格的请求是使用 “url + 请求方式” 表示一次请求目的,HTTP 协议里面四个表示操作方式的动词如下:

      • GET:用于获取资源
      • POST:用于新建资源
      • PUT:用于更新资源
      • DELETE:用于删除资源

      例如:

      • /user/1 GET: 得到 id = 1 的 user
      • /user/1 DELETE: 删除 id = 1 的 user
      • /user/1 PUT: 更新 id = 1 的 user
      • /user POST: 新增 user

      上述 url 地址 /user/1 中的就是要获得的请求参数,在 SpringMVC 中可以使用占位符进行参数绑定。地址 /user/1 可以写成 /user/{id},占位符 {id} 对应的值就是1,。在业务方法中我们可以使用 @PathVariable 注解进行占位符的匹配获取工作。

    • 自定义类型转换器
      • SpringMVC 默认提供了一些常用的类型转换器,例如客户端提交的字符串转换成 int 型进行参数设置。
      • 但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。

      步骤:

      • 定义转换器类实现 Converter 接口

        public class DateConverter implements Converter<String, Date> {
            @Override
            public Date convert(String dateStr) {
                // 将日期的字符串转换成日期对象 返回
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                Date date = null;
                try {
                    date = format.parse(dateStr);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                return date;
            }
        }
      • 在配置文件中声明转换器

        <!-- 声明转换器 -->
            <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
                <property name="converters">
                    <list>
                        <bean class="com.converter.DateConverter"/>
                    </list>
                </property>
            </bean>
      • 在 <annotation-driven> 中引用转换器

        <!-- mvc 的注解驱动 -->
        <mvc:annotation-driven conversion-service="conversionService"/>
    • 获得 Servlet 相关 API

      SpringMVC 支持使用原始 ServletAPI 对象作为控制器方法的参数进行注入,常用对象如下:

      • HttpServletRequest
      • HttpServletResponse
      • HttpSession
    • 获得请求头
      • @RequestHeader

        使用 @RequestHeader 可以获得请求头信息,相当于 web阶段学习的 request.getHeader(name)

        @RequestHeader 注解的属性如下:

        • value:请求头的名称
        • required:是否必须携带此请求头
      • @CookieValue

        使用 @CookieValue 可以获得指定 Cookie 的值

        @CookieValue 注解的属性如下:

        • value:指定 cookie 的名称
        • required:是否必须携带此 cookie
  15. 文件上传

    • 文件上传客户端三要素
      • 表单项 type = "file"
      • 表单提交方式是 post
      • 表单的 enctype 属性是多部分表单形式,及 enctype = "multiypart/form-data"
    • 文件上传原理
      • 当 form 表单修改为多部分表单时,request.getParameter() 将失效。
      • enctype = "application/x-www-form-urlencoded" 时,form 表单的正文内容格式是:key = value & key = value & key = value
      • 当 from 表单的 enctype 取值为 multipart/form-data 时,请求正文内容就变成多部分形式:

        image-20220614144223072

    • 单文件上传的步骤
      • 导入 fileupload 和 io 坐标

        <dependency>
          <groupId>commons-fileupload</groupId>
          <artifactId>commons-fileupload</artifactId>
          <version>1.3.1</version>
        </dependency>
        <dependency>
          <groupId>commons-io</groupId>
          <artifactId>commons-io</artifactId>
          <version>2.6</version>
        </dependency>
      • 配置文件上传解析器

        <!-- 配置文件上传解析器 -->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <!-- 上传文件总大小 -->
            <property name="maxUploadSize" value="500000"/>
            <!-- 上传单个文件的大小 -->
            <property name="maxUploadSizePerFile" value="500000"/>
            <!-- 上传文件的编码类型 -->
            <property name="defaultEncoding" value="UTF-8"/>
        </bean>
      • 编写文件上传代码

        public void save22(String username, MultipartFile upload) throws IOException {
            System.out.println(username);
            // 获得上传文件的名称
            String originalFilename = upload.getOriginalFilename();
            upload.transferTo(new File("D:\\SSM_2022\\Spring\\spring_mvc\\src\\main\\webapp\\docx\\" + originalFilename));
        }
    • 多文件上传
  16. JDBCTemplate 基本使用

    • JDBCTemplate 开发步骤
      • 导入 spring-jdbc 和 spring-tx 坐标

        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>5.3.20</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-tx</artifactId>
          <version>5.3.20</version>
        </dependency>
      • 创建数据库表和实体
      • 创建 JDBCTemplate 对象

        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        // 设置数据源对象 要知道数据库在哪
        jdbcTemplate.setDataSource(dataSource);
      • 执行数据库操作

        • 更新操作:

          jdbcTemplate.update(sql, params);
        • 查询操作:

          jdbcTemplate.query(sql, Mapper, params);
          jdbcTemplate.queryForObject(sql, Mapper, params);
  17. Spring 环境搭建步骤

    • 创建工程
    • 导入静态页面
    • 导入需要坐标 pom.xml
    • 创建包结构

      • controller
      • service
      • dao
      • domain
      • utils
    • 导入数据库脚本
    • 创建 POJO 类
    • 创建配置文件

      • applicationContext.xml
      • spring-mvc.xml
      • jdbc.properties
      • log4j.properties
  18. SpringMVC 拦截器 interceptor

    • interceptor 的作用

      相当于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。

    • 自定义 Interceptor 步骤

      • 创建拦截器实现 HandlerInterceptor 接口
      • 配置拦截器

        <!-- 配置拦截器 -->
        <mvc:interceptors>
            <mvc:interceptor>
                <!-- 对哪些资源进行拦截操作 -->
                <mvc:mapping path="/**"/>
                <bean class="com.interceptor.MyInterceptor"/>
            </mvc:interceptor>
            <mvc:interceptor>
                <mvc:mapping path="/**"/>
                <bean class="com.interceptor.MyInterceptor2"/>
            </mvc:interceptor>
        </mvc:interceptors>
      • 测试拦截器的拦截效果
  19. SpringMVC 异常处理

    • 异常处理思路

      系统中的异常包括两大类:预期异常和运行时异常( RuntimeException ),前者通过捕获异常从何获取异常信息,后者只要通过规范代码开发、测试等手段减少运行时异常的发生。

    • 两种方式

      • 使用 SpringMVC 提供的简单异常处理器 SimpleMappingExceptionResolver

        <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
            <property name="defaultErrorView" value="error"/>
            <property name="exceptionMappings">
                <map>
                    <entry key="java.lang.ClassCastException" value="error1"/>
                    <entry key="java.lang.ArithmeticException" value="error2"/>
                    <entry key="java.io.FileNotFoundException" value="error3"/>
                    <entry key="java.lang.NullPointerException" value="error4"/>
                    <entry key="com.exception.MyException" value="error5"/>
                </map>
            </property>
        </bean>
      • 实现 Spring 的异常处理器接口 HandlerExceptionResolver 自定义自己的异常处理器。
    • 自定义异常处理步骤

      • 创建异常处理器类实现 HandlerExceptionResolver

        public class MyExceptionResolver implements HandlerExceptionResolver {
        
            /*
                参数 Exception:异常对象
                返回值 ModelAndView:跳转到错误视图
             */
            @Override
            public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                ModelAndView modelAndView = new ModelAndView();
        
                if (ex instanceof MyException) {
                    modelAndView.addObject("info", "自定义异常");
                } else if (ex instanceof ClassCastException) {
                    modelAndView.addObject("info", "类转换异常");
                } else if (ex instanceof ArithmeticException) {
                    modelAndView.addObject("info", "零异常");
                } else if (ex instanceof FileNotFoundException) {
                    modelAndView.addObject("info", "文件找不到异常");
                } else if (ex instanceof NullPointerException) {
                    modelAndView.addObject("info", "空指针异常");
                } else {
                    modelAndView.addObject("info", "通用错误异常");
                }
                modelAndView.setViewName("error");
        
                return modelAndView;
            }
        }
      • 配置异常处理器

        <bean class="com.resolver.MyExceptionResolver"/>
      • 编写异常页面
      • 测试异常跳转
  20. AOP 面向切面编程

    通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
    
    • 动态代理的优点

      在不修改源码的情况下,对目标方法进行相应的增强。

      作用:完成程序之间的松耦合。

    • AOP 作用及其优势

      作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。

      优势:减少重复代码,提高开发优势,并且便于维护。

    • 常用的动态代理技术

      • JDK 代理:基于接口的动态代理技术
      • cglib 代理:基于父类的动态代理技术
    • AOP 相关概念 (术语)

      • Target(目标对象):代理的目标对象
      • Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类
      • Joinpoint(连接点):所谓连接点是指哪些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。可以被增强的方法叫做连接点
      • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpiont 进行拦截的定义。也叫切点,被增强的方法叫做切入点
      • Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。对目标方法进行增强的那个增强方法叫做增强
      • Aspect(切面):是切入点和通知的结合。目标方法+增强
      • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。切点和增强结合的过程叫做织入
    • AOP 开发明确的事项

      1. 需要编写的内容

        • 编写核心业务代码(目标类的目标方法)
        • 编写切面类,切面类中有通知(增强功能方法)
        • 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
      2. AOP 技术实现的内容

        Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

      3. AOP 底层使用哪种代理方式

        在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

      4. 基于 XML 的 AOP 开发步骤

        • 导入 AOP 相关坐标

          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.20</version>
          </dependency>
          <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
          </dependency>
        • 创建目标接口和目标类(内部有切点)
        • 创建切面类(内部有增强方法)
        • 将目标类和切面类的对象创建权交给 spring
        • 在 applicationContext.xml 中配置织入关系

          <!-- 目标对象 -->
          <bean id="target" class="com.aop.Target"/>
          
          <!-- 切面对象 -->
          <bean id="myAspect" class="com.aop.MyAspect"/>
          
          <!-- 配置织入 告诉 spring 框架 哪些方法(切点)需要进行哪些增强(前置、后置...) -->
          <aop:config>
              <!-- 声明切面 -->
              <aop:aspect ref="myAspect">
                  <!-- 切面:切点 + 通知 -->
                  <aop:before method="before" pointcut="execution(public void com.aop.Target.save())"/>
              </aop:aspect>
          </aop:config>

          切点表达式的写法:

          切点表达式:execution([修饰符] 返回值类型 包名.类名.方法名(参数))

          • 访问修饰符可以省略
          • 返回值类型、包名、类名、方法名可以使用 * 代表任意
          • 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
          • 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表

          通知的类型:

          通知的配置语法:

          <aop:通知类型 method="切面类中的方法名" pointcut="切点表达式"></aop:通知类型>
          • 前置通知

            <aop:before>
            用于配置前置通知,指定增强方法在切点方法之前执行
          • 后置通知

            <aop:after-returning>
            用于配置后置通知,指定增强方法在切点方法之后执行
          • 环绕通知

            <aop:around>
            用于配置环绕通知,指定增强方法在切点方法之前和之后都执行
          • 异常抛出通知

            <aop:after-throwing>
            用于配置异常抛出通知,指定增强方的法在出现异常时执行
          • 最终通知

            <aop:after>
            用于配置最终通知,无论增强方式执行是否有异常都会执行

          切点表达式的抽取:

          当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。

          <aop:pointcut id="myPointcut" expression="execution(* com.aop.*.*(..))"/>
          <aop:around method="around" pointcut-ref="myPointcut"/>
          <aop:after method="after" pointcut-ref="myPointcut"/>
        • 测试代码
      5. 基于注解的 AOP 开发步骤

        • 创建目标接口和目标类(内部有切点)
        • 创建切面类(内部有增强方法)
        • 将目标类和切面类的对象创建权交给 spring
        • 在切面类中使用注解配置织入关系
        • 在配置文件中开启组件扫描和 AOP 的自动代理

          <!-- Aop 自动代理 -->
          <aop:aspectj-autoproxy/>
        • 测试

        切点表达式的抽取:

        // 抽取
        @Pointcut("execution(* com.anno.*.*(..))")
        public void pointcut() {}
        
        // 两种方式使用:
        // 1.
        @Before("pointcut()")
        public void before() {
            System.out.println("前置增强。。。");
        }
        // 2.
        @AfterReturning("MyAspect.pointcut()")
        public void afterReturning() {
            System.out.println("后置增强。。。");
        }
  21. Spring 事务控制

    • 编程式事务控制三大对象

      • PlatformTransactionManager

        一个接口 根据不同的 Dao 层的实现来使用改接口对应的实现类

      • TransactionDefinition

        事务的定义对象

        1. 事务的隔离级别

          设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。

          • ISOLATION_DEFAULT:当前数据库默认的
          • ISOLATION_READ_UNCOMMITTED:读-未提交的,上述三种问题都不能解决
          • ISOLATION_READ_COMMITTED:读-已提交的,可解决脏读
          • ISOLATION_REPEATABLE_READ:可重复读,解决了不可重复读
          • ISOLATION_SERIALIZABLE:串行化,所有问题都能解决,但是性能很低
        2. 事务的传播行为

          image-20220630012149017

      • TransactionStatus

        事务的状态信息

    • 基于 XML 的声明式事务控制

      配置要点:

      • 平台事务管理器配置:

        <!-- 配置平台事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
      • 事务通知的配置:

        <!-- 通知 事务的增强 -->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <!-- 设置事务的属性信息 -->
            <tx:attributes>
                <tx:method name="*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" timeout="-1"/>
            </tx:attributes>
        </tx:advice>
      • 事务 AOP 织入的配置:

        <!-- 配置事务的 aop 织入 -->
        <aop:config>
            <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.service.impl.*.*(..))"/>
        </aop:config>
    • 基于注解的声明式事务控制

      1. 使用 @Transactional 在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如:隔离级别,传播行为等。
      2. 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
      3. 使用在方法上,不同的方法可以采用不同的事务参数配置。
      4. Xml 配置文件中要开启事务的注解驱动

        <!-- 事务的注解驱动 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>
        @Service("accountService")
        @Transactional(isolation = Isolation.REPEATABLE_READ)
        public class AccountServiceImpl implements AccountService {
        
            @Autowired
            private AccountDao accountDao;
        
            @Override
            @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
            public void transfer(String outMan, String inMan, double money) {
                accountDao.out(outMan, money);
        //        int i = 1/0;
                accountDao.in(inMan, money);
            }
        }

        配置要点:

        • 平台事务管理器配置(xml 方式)
        • 事务通知的配置(@Transactional 注解配置)
        • 事务注解驱动的配置

          <tx:annotation-driven/>