CategorySSM article

MyBatis

  1. MyBatis 开发步骤:

    • 添加 MyBatis 的坐标

      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
      </dependency>
    • 创建 user 数据表
    • 创建 User 实体类
    • 编写映射文件 UserMapper.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="userMapper">
      
          <select id="findAll" resultType="com.domain.User">
              select * from user
          </select>
      
      </mapper>
    • 编写核心文件 SqlMapConfig.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE configuration
              PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-config.dtd" >
      <configuration>
      
          <!-- 配置数据源环境 -->
          <environments default="development">
              <environment id="development">
                  <transactionManager type="JDBC"/>
                  <dataSource type="POOLED">
                      <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                      <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                      <property name="username" value="root"/>
                      <property name="password" value="jie13727507037"/>
                  </dataSource>
              </environment>
          </environments>
      
          <!-- 加载映射文件 -->
          <mappers>
              <mapper resource="com.mapper\UserMapper.xml"/>
          </mappers>
      
      </configuration>
    • 编写测试类

      public void test1() throws IOException {
          // 加载核心配置文件
          InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
          // 获得 SQLSession 工厂对象
          SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
          // 获得 session 会话对象
          SqlSession sqlSession = sqlSessionFactory.openSession();
          // 执行操作     参数:namespace+语句id
          List<User> userList = sqlSession.selectList("userMapper.findAll");
          // 打印数据
          System.out.println(userList);
          // 释放资源
          sqlSession.close();
      }
  2. 映射文件概述

    image-20220630141304364

  3. MyBatis 的插入操作注意问题

    • 插入语句使用 insert 标签
    • 在映射文件中使用 parameterType 属性指定要插入的数据类型
    • Sql 语句中使用 #{实例属性名} 的方式引用实体中的属性值
    • 插入操作使用的 API 是 sqlSession.insert("namespace.id", 实体对象);
    • 插入操作涉及数据库数据变化,所以要使用 sqlSession对象显示的提交事务,即 sqlSession.commit()
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="userMapper">
    
        <!-- 查询操作 -->
        <select id="findAll" resultType="com.domain.User">
            select * from user
        </select>
    
        <!-- 插入操作 -->
        <insert id="save" parameterType="com.domain.User">
            insert into user values(#{id}, #{username}, #{password})
        </insert>
    
    </mapper>
    public void test2() throws IOException {
    
        // 模拟 user 对象
        User user = new User();
        user.setUsername("tom");
        user.setPassword("abc");
    
        // 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获得 SQLSession 工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获得 session 会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 执行操作     参数:namespace+语句id
        sqlSession.insert("userMapper.save", user);
    
        // mybatis 执行更新操作  需要提交事务
        sqlSession.commit();
    
        // 释放资源
        sqlSession.close();
    }
  4. MyBatis 修改操作注意问题

    • 修改语句使用 update 标签
    • 修改操作使用的 API 是 sqlSession.update("namespace.id", 实体对象);
    <!-- 修改操作 -->
    <update id="update" parameterType="com.domain.User">
        update user set username=#{username}, password=#{password} where id=#{id}
    </update>
    public void test3() throws IOException {
    
        // 模拟 user 对象
        User user = new User();
        user.setId(7);
        user.setUsername("lucy");
        user.setPassword("123");
    
        // 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获得 SQLSession 工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获得 session 会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 执行操作     参数:namespace+语句id
        sqlSession.update("userMapper.update", user);
    
        // mybatis 执行更新操作  需要提交事务
        sqlSession.commit();
    
        // 释放资源
        sqlSession.close();
    }
  5. MyBatis 删除操作注意问题

    • 删除语句使用 delete 标签
    • Sql 语句中使用 #{任意字符串} 方式引用传递的单个参数
    • 删除操作使用的 API 是 sqlSession.delete("namespace.id", Object);
    <!-- 删除操作 -->
    <delete id="delete" parameterType="java.lang.Integer">
        delete from user where id=#{id}
    </delete>
    public void test4() throws IOException {
    
        // 加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 获得 SQLSession 工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        // 获得 session 会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 执行操作     参数:namespace+语句id
        sqlSession.delete("userMapper.delete", 7);
    
        // mybatis 执行更新操作  需要提交事务
        sqlSession.commit();
    
        // 释放资源
        sqlSession.close();
    }
  6. MyBatis 核心配置文件常用配置

    • properties 标签:改标签可以加载外部的 properties 文件

      <!-- 加载外部 properties 文件 -->
      <properties resource="jdbc.properties"/>
    • typeAliases 标签:设置类型别名

      <!-- 定义别名 -->
      <typeAliases>
          <typeAlias type="com.domain.User" alias="user"/>
      </typeAliases>
    • mappers 标签:加载映射配置

      <!-- 加载映射文件 -->
      <mappers>
          <mapper resource="com.mapper/UserMapper.xml"/>
      </mappers>
    • environments 标签:数据源环境配置标签

      <!-- 配置数据源环境 -->
      <environments default="development">
          <environment id="development">
              <transactionManager type="JDBC"/>
              <dataSource type="POOLED">
                  <property name="driver" value="${jdbc.driver}"/>
                  <property name="url" value="${jdbc.url}"/>
                  <property name="username" value="${jdbc.username}"/>
                  <property name="password" value="${jdbc.password}"/>
              </dataSource>
          </environment>
      </environments>
  7. MyBatis 相应 API

    • SqlSession 工厂构造器 SqlSessionFactory build(InputStream inputstream)

      // 加载核心配置文件
      InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
      // 获得 SQLSession 工厂对象
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    • SqlSession 工厂对象 SqlSessionFactory

      • openSession():会默认开启一个事务,但事务不会自动提交,也就是一位置需要手动提交该事务,更新操作数据才会持久化到数据库中
      • openSession(boolean autoCommit):参数为是否自动提交,如果设置为 true,那么不需要手动提交事务
      // 获得 session 会话对象
      SqlSession sqlSession = sqlSessionFactory.openSession();
    • SqlSession 会话对象

      • 执行语句的方法

        // 查询单个对象
        T selectOne(String statement, Object parameter);
        // 查询全部
        List<E> selectList(String statement, Object parameter);
        // 插入
        int insert(String statement, Object parameter);
        // 修改
        int update(String statement, Object parameter);
        // 删除
        int delete(String statement, Object parameter);
      • 操作事务的方法

        void.commit();     // 事务提交
        void rollback(); // 事务回滚
  8. MyBatis DAO 层实现

    • 代理开发方式

      UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
      • Mapper 接口开发需要遵循以下规范

        1. Mapper.xml 文件中的 namespace 与 mapper 接口的全限定名相同
        2. Mapper 接口方法名和 Mapper.xml 中定义的每个 statement 的 id 相同
        3. Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同
        4. Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同
  9. MyBatis 映射文件深入

    • 动态 sql —— if

      <select id="findByCondition" resultType="user" parameterType="user">
          select * from user
          <where>
              <if test="id!=0">
                  id=#{id}
              </if>
              <if test="username!=null">
                  and username=#{username}
              </if>
              <if test="password!=null">
                  and password=#{password}
              </if>
          </where>
      </select>
    • 动态 sql —— foreach

      <select id="findByIds" parameterType="list" resultType="user">
          select * from user
          <where>
              <foreach collection="list" open="id in(" close=")" item="id" separator=",">
                  #{id}
              </foreach>
          </where>
      </select>
    • 动态 sql —— sql 语句的抽取

      <!-- sql 语句的抽取 -->
      <sql id="selectUser">select * from user</sql>
      
          <include refid="selectUser"></include>
  10. MyBatis 核心配置文件深入

    • typeHandlers 标签

      • 开发步骤

        1. 定义转换类继承类 BaseTypeHandler<T> 
        2. 覆盖 4 个未实现的方法,其中 setNonNullParameter 为 java 程序设置数据到数据库的回调方法,getNullableResult 为查询时 mysql 的字符串类型转换成 java 的 Type 类型的方法
        3. 在 MyBatis 核心配置文件中个进行注册
        4. 测试转换是否正确
    • plugins 标签

      • 开发步骤

        1. 导入通用 PageHelper 的坐标
        2. 在 mybatis 核心配置文件中配置 PageHelper 插件
        3. 测试分页数据获取
  11. MyBatis 多表操作

    1. 一对一

      • 第一种配置
     

     ```xml
     <resultMap id="orderMap" type="order">
         <!-- 手动指定字段与实体的映射关系
              column: 数据表的字段名称
              property: 实体的属性名称
          -->
         <id column="oid" property="id"/>
         <result column="ordertime" property="orderTime"/>
         <result column="total" property="total"/>
         <result column="uid" property="user.id"/>
         <result column="username" property="user.username"/>
         <result column="password" property="user.password"/>
         <result column="birthday" property="user.birthday"/>
     </resultMap>
     
     <select id="findAll" resultMap="orderMap">
         select *, o.id oid from orders o, user u where o.id=u.id
     </select>
     ```

   - 第二种配置

     ```xml
     <resultMap id="orderMap" type="order">
         <!-- 手动指定字段与实体的映射关系
              column: 数据表的字段名称
              property: 实体的属性名称
          -->
         <id column="oid" property="id"/>
         <result column="ordertime" property="orderTime"/>
         <result column="total" property="total"/>
         <!--<result column="uid" property="user.id"/>
         <result column="username" property="user.username"/>
         <result column="password" property="user.password"/>
         <result column="birthday" property="user.birthday"/>-->
     
         <!--
             property: 当前实体的属性名称
             javaType: 当前实体中的属性的类型
         -->
         <association property="user" javaType="user">
             <id column="uid" property="id"/>
             <result column="username" property="user.username"/>
             <result column="password" property="user.password"/>
             <result column="birthday" property="user.birthday"/>
         </association>
     </resultMap>
     
     <select id="findAll" resultMap="orderMap">
         select *, o.id oid from orders o, user u where o.id=u.id
     </select>
     ```

2. 一对多

   - ```xml
     <resultMap id="userMap" type="user">
         <id column="id" property="id"/>
         <result column="username" property="username"/>
         <result column="password" property="password"/>
         <result column="birthday" property="birthday"/>
     
         <!-- 配置集合信息
              property: 集合名称
              ofType: 当前集合中的数据类型
          -->
         <collection property="orderList" ofType="order">
             <id column="oid" property="id"/>
             <result column="ordertime" property="orderTime"/>
             <result column="total" property="total"/>
         </collection>
     </resultMap>
     
     <select id="findAll" resultMap="userMap">
         select *, o.id oid from user u, order o where u.id=o.uid
     </select>
     ```

3. 多对多

   - ```xml
     <resultMap id="userRoleMap" type="user">
         <id column="userId" property="id"/>
         <result column="username" property="username"/>
         <result column="password" property="password"/>
         <result column="birthday" property="birthday"/>
         
         <collection property="roleList" ofType="role">
             <id column="roleId" property="id"/>
             <result column="roleName" property="roleName"/>
             <result column="roleDesc" property="roleDesc"/>
         </collection>
     </resultMap>
     
     <select id="findUserAndRoleAll" resultMap="userRoleMap">
         select * from user u, sys_user_role ur, sys_role r where u.id=ur.userId and ur.roleId
     </select>
     ```
  1. MyBatis 注解开发

    • 常用注解

      • @Insert:实现新增
      • @Update:实现更新
      • @Delete:实现删除
      • @Select:实现查询
      • @Result:实现结果集封装
      • @Results:可以与 @Result 一起使用,封装多个结果集
      • @One:实现一对一结果封装
      • @Many:实现一对多结果集封装
    • 简单查询

      package com.mapper;
      
      import com.domain.User;
      import org.apache.ibatis.annotations.Delete;
      import org.apache.ibatis.annotations.Insert;
      import org.apache.ibatis.annotations.Select;
      import org.apache.ibatis.annotations.Update;
      
      import java.util.List;
      
      public interface UserMapper {
      
          @Insert("insert into user values(#{id}, #{username}, #{password}, #{birthday})")
          void save(User user);
      
          @Delete("delete from user where id=#{id}")
          void delete(int id);
      
          @Update("update user set username=#{username}, password=#{password} where id=#{id}")
          void update(User user);
      
          @Select("select * from user where id=#{id}")
          User findById(int id);
      
          @Select("select * from user")
          List<User> findAll();
      
      }
      <!-- 加载映射关系 -->
      <mappers>
          <!-- 指定一个包下的接口 -->
          <package name="com.mapper"/>
      </mappers>
    • 复杂查询 —— 一对一

      两种方式:

      • package com.mapper;
        
        import com.domain.Order;
        import org.apache.ibatis.annotations.Result;
        import org.apache.ibatis.annotations.Results;
        import org.apache.ibatis.annotations.Select;
        
        import java.util.List;
        
        public interface OrderMapper {
        
            @Select("select *, o.id oid from orders o, user u where o.id=u.id")
            @Results({
                    @Result(column = "oid", property = "id"),
                    @Result(column = "ordertime", property = "orderTime"),
                    @Result(column = "total", property = "total"),
                    @Result(column = "uid", property = "user.id"),
                    @Result(column = "username", property = "user.username"),
                    @Result(column = "password", property = "user.password")
            })
            List<Order> findAll();
        
        }
      • @Select("select * from orders")
        @Results({
                @Result(column = "id", property = "id"),
                @Result(column = "ordertime", property = "orderTime"),
                @Result(column = "total", property = "total"),
                @Result(
                        javaType = User.class,  // 要封装的实体类型
                        column = "uid",            // 根据哪个字段去查询 user 表的数据
                        property = "user",       // 要封装的属性名
                        // select 属性 代表查询哪个接口的方法获得数据
                        one = @One(select = "com.mapper.UserMapper.findById")
                )
        })
        List<Order> findAll();
    • 复杂查询——一对多

      @Select("select * from orders where uid=#{uid}")
      List<Order> findByUid(int uid);
      @Select("select * from user")
      @Results({
              @Result(id = true, column = "id", property = "id"),
              @Result(column = "username", property = "username"),
              @Result(column = "password", property = "password"),
              @Result(
                      javaType = List.class,  // 要封装的实体类型
                      column = "id",            // 根据哪个字段去查询 user 表的数据
                      property = "orderList",       // 要封装的属性名
                      // select 属性 代表查询哪个接口的方法获得数据
                      many = @Many(select = "com.mapper.OrderMapper.findByUid")
              )
      })
      List<User> findUserAndOrderAll();
    • 复杂查询 —— 多对多

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>