3、Spring

第一章 初识Spring

1.1 Spring简介

  • Spring是一个为简化企业级开发而生的开源框架

    • 开源:开放源代码
  • Spring是一个IOC(DI)和AOP容器框架。

    • IOC全称:Inversion Of Control【控制反转】

      • 控制反转:将对象控制权由程序员自己管理反转给Spring框架管理
    • DI全称:Dependency Injection【依赖注入】

      • 依赖注入:Spring管理对象与对象之间的依赖关系
    • AOP全称:Aspect-Oriented Programming【面向切面编程设计】

      • OOP:Object-Oriented Programming【面向对象编程设计】

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3rmFDwxn-1652445824674)(03Spring.assets\image-20220511161948974.png)]

  • Spring官网:https://spring.io

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v30oM9Cr-1652445824675)(03Spring.assets\image-20220511162453990.png)]

1.2 搭建Spring框架

  • 导入jar包

    <!--导入spring-context-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!--导入junit4.12-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J6gl2Yfa-1652445824676)(03Spring.assets\image-20220511163322187.png)]

  • 创建POJO

    public class Employee {
    
        private Integer id;         //员工id
        private String lastName;    //员工名称
        private String email;       //员工邮箱
        private Double salary;      //员工薪资
    
        @Override
        public String toString() {
            return "Employee{" +
                    "id=" + id +
                    ", lastName='" + lastName + '\'' +
                    ", email='" + email + '\'' +
                    ", salary=" + salary +
                    '}';
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public Double getSalary() {
            return salary;
        }
    
        public void setSalary(Double salary) {
            this.salary = salary;
        }
    
        public Employee() {
        }
        public Employee(Integer id, String lastName, String email, Double salary) {
            this.id = id;
            this.lastName = lastName;
            this.email = email;
            this.salary = salary;
        }
    }
    
  • 编写配置文件

    • 名称:applicationContext.xml【beans.xml或spring.xml】

    • 位置:src/main/resouces

    • 示例代码

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd">
          <!--
              装配pojo对象
              将empChai装配到的IOC容器中
          -->
          <bean id="empChai" class="com.atguigu.pojo.Employee">
              <property name="id" value="1001"></property>
              <property name="lastName" value="chails"></property>
              <property name="email" value="chails@163.com"></property>
              <property name="salary" value="1000.5"></property>
          </bean>
      
      </beans>
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MJR9TKlZ-1652445824676)(03Spring.assets\image-20220511163538117.png)]

  • 使用核心类库【容器对象:ApplicationContext】

    @Test
        public void testSpring(){
            //使用Spring之前
    //        Employee employee = new Employee();
    
            //使用Spring之后
            //创建IOC容器对象
            ApplicationContext ioc =
                    new ClassPathXmlApplicationContext("applicationContext.xml");
            //从容器中获取对象
            Employee empChai = (Employee) ioc.getBean("empChai");
            System.out.println("empChai = " + empChai);
    
        }
    

1.3 Spring基本特性

  • 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API。
  • 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期。
  • 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
  • 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的JDBCTemplate)。

第二章 Spring中getBean()三种方式

  • getBean(String beanId):通过beanId获取对象

    • 不足:需要强制类型转换
  • getBean(Class clazz):通过Class获取对象

    • 不足:如容器中存在多个对象时,会报如下错

      available: expected single matching bean but found 2: empChai,empLiu

  • getBean(String beanId,Class clazz):通过beanId&Class获取对象

    • 推荐使用

第三章 Spring中IOC底层实现

  • Spring底层就是一个对象工厂【BeanFactory】
  • BeanFactory结构
    • BeanFactory【面向Spring框架】
        • ApplicationContext【面向程序员】
          • ConfigurableApplicationContext【是ApplicationContext的扩展对象,提供关闭&刷新容器对象的方法】
              • ClassPathXmlApplicationContext:基于类路径检索xml文件
              • FileSystemXmlApplicationContext:基于系统路径检索xml文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O8I1ay7X-1652445824676)(03Spring.assets\image-20220513150318445.png)]

第四章 Spring中DI【依赖注入】三种方式

JavaSE为属性赋值

  1. 通过setXXX()方法,为属性赋值
  2. 通过构造器,为属性赋值
  3. 反射,为属性赋值

4.1 setter注入

  • 语法:标签

    • name属性:设置属性名称
    • value属性:设置属性数值
  • 示例代码

    <bean id="empChai" class="com.dudu.pojo.Employee">
        <property name="id" value="1001"></property>
        <property name="lastName" value="chailaoshi"></property>
        <property name="salary" value="10000.5"></property>
        <property name="email" value="chailaoshi@163.com"></property>
    </bean>
    

4.2 构造注入

  • 语法:

    • name属性:设置属性名称
    • value属性:设置属性数值
  • 示例代码

    <!-- 装配刘老师-->
    <bean id="empLiu" class="com.dudu.pojo.Employee">
        <constructor-arg name="id" value="1002"></constructor-arg>
        <constructor-arg name="lastName" value="liulaoshi"></constructor-arg>
        <constructor-arg name="salary" value="10000.5"></constructor-arg>
        <constructor-arg name="email" value="liulaoshi@163.com"></constructor-arg>
    </bean>
    

4.3 p名称注入

  • 导入p名称空间

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QVp61Idw-1652445824677)(03Spring.assets\image-20220513152007552.png)]

  • 示例代码:

    <?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:p="http://www.springframework.org/schema/p"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean 	id="yulaoshi"
                class="com.dudu.pojo.Employee"
                p:id="1003"
                p:lastName="taotao"
                p:email="taotao@163.com"
                p:salary="10000.5">
        </bean>
    </beans>
    
  • p名称空间注入,底层使用setter注入。

第五章 Spring中依赖注入【数值问题】

5.1 字面量数值

  • 字面量

    • 数据类型:基本类型&包装类型&String
    • 语法结构:value属性或value标签
  • CDATA区

    • 语法:<![CDATA[]]>

    • 作用:解决xml中特殊字符

    • 示例代码

      <!-- mybatis映射文件 -->
      <select id="selectEmpAndDeptByEmpIdAssociationStep" resultMap="empAndDeptAssociationStepRm">
          <![CDATA[
              select
                  id,
                  last_name,
                  email,
                  salary,
                  dept_id
              from
                  tbl_employee
              where
                  id = #{empId}
          ]]>
      </select>
      <!-- spring配置文件-->
      <bean id="empChai" class="com.dudu.pojo.Employee">
              <property name="id" value="1001"></property>
              <property name="lastName">
                  <value><![CDATA[<chailaoshi>]]></value>
              </property>
              <property name="salary" value="10000.5"></property>
              <property name="email">
                  <value>chailaoshi@163.com</value>
              </property>
          </bean>
      

5.2 外部bean

  • 注意:使用外部bean支持级联属性赋值,如级联属性数值更改,也会影响外部bean属性数值

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MXcd5jZu-1652445824678)(03Spring.assets\image-20220513155406807.png)]

  • 示例代码

    <!--    定义dept的bean-->
    <bean id="dept1" class="com.dudu.pojo.Dept">
        <property name="deptId" value="101"></property>
        <property name="deptName" value="研发部门"></property>
    </bean>
    <bean id="empJing" class="com.dudu.pojo.Employee">
        <property name="id" value="1004"></property>
        <property name="lastName" value="jingjign"></property>
        <property name="email" value="jingjing@163.com"></property>
        <property name="salary" value="10000.5"></property>
        <property name="dept" ref="dept1"></property>
        <property name="dept.deptName" value="教学部门"></property>
    </bean>
    

5.3 内部bean

  • 内部Bean:在一个bean中完整的定义另一个bean

  • 注意:内部bean不会直接装配到IOC容器中

  • 示例代码

    <!--    测试内部bean-->
    <bean id="empLi" class="com.dudu.pojo.Employee">
        <property name="id" value="1005"></property>
        <property name="lastName" value="jinlong"></property>
        <property name="email" value="longge@163.com"></property>
        <property name="salary" value="10000.5"></property>
        <property name="dept">
            <bean class="com.dudu.pojo.Dept">
                <property name="deptId" value="102"></property>
                <property name="deptName" value="教务部门"></property>
            </bean>
        </property>
    </bean>
    

5.4 装配list

    <!--list-->
    <bean id="dept1" class="com.dudu.pojo.Dept">
        <property name="deptId" value="1"></property>
        <property name="deptName" value="教学部"></property>
        <property name="empList">
            <list>
                <ref bean="empId"></ref>
                <ref bean="empId1"></ref>
            </list>
        </property>
    </bean>
    <!--提取list-->
    <util:list id="empList">
        <ref bean="empId"></ref>
        <ref bean="empId1"></ref>
    </util:list>
    <bean id="dept11" class="com.dudu.pojo.Dept">
        <property name="deptId" value="1"></property>
        <property name="deptName" value="教学部"></property>
        <property name="empList" ref="empList"></property>
    </bean>

5.5 装配map

	 <!--map-->
    <bean id="dept2" class="com.dudu.pojo.Dept">
        <property name="deptId" value="1"></property>
        <property name="deptName" value="教学部"></property>
        <property name="empMap">
           <map>
               <entry>
                   <key>
                       <value>111</value>
                   </key>
                   <ref bean="empId"></ref>
               </entry>
               <entry>
                   <key>
                       <value>222</value>
                   </key>
                   <ref bean="empId1"></ref>
               </entry>
           </map>
        </property>
    </bean>
            <!--提取map-->
    <util:map id="empMap">
        <entry>
            <key>
                <value>111</value>
            </key>
            <ref bean="empId"></ref>
        </entry>
        <entry>
            <key>
                <value>222</value>
            </key>
            <ref bean="empId1"></ref>
        </entry>
    </util:map>
    <bean id="dept22" class="com.dudu.pojo.Dept">
        <property name="deptId" value="1"></property>
        <property name="deptName" value="教学部"></property>
        <property name="empMap" ref="empMap"></property>
    </bean>

Test

    public void testSpringGetBean(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//        Employee empId = (Employee) context.getBean("empId");
//        System.out.println(empId);
//        System.out.println("====================================");
        Employee bean = context.getBean(Employee.class);
        System.out.println(bean);
//        System.out.println("====================================");
//        Employee empId1 = context.getBean("empId1", Employee.class);
//        System.out.println(empId1);

        Employee empId3 = context.getBean("empId3", Employee.class);
        System.out.println(empId3);

        System.out.println("======================");

        //测试list
        Dept dept1 = context.getBean("dept11", Dept.class);
        System.out.println(dept1);
        System.out.println("========================");
        //测试map
        Dept dept2 = context.getBean("dept22", Dept.class);
        System.out.println(dept2);
    }

导入Maven工程

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iyC7rnPj-1652445824678)(03Spring.assets\0RA`TTH3US@8{LKPSFO.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9FtltqEc-1652445824679)(03Spring.assets`FBC3I4DEPNTN{T5ABOQ8LJ.png)]

第六章 Spring管理第三方Bean【DruidDataSource】

6.1 使用Spring管理DruidDataSource步骤

  • 导jar包

     <!--导入spring-context-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.3.1</version>
            </dependency>
            <!--导入junit4.12-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
    
            <!--导入druid的jar包-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.10</version>
            </dependency>
            <!--导入mysql的jar包-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.26</version>
            </dependency>
    
  • 编写外部属性文件:db.properties

    db.driverClassName=com.mysql.cj.jdbc.Driver
    db.url=jdbc:mysql://localhost:3306/db220227?serverTimeZone=UTC
    db.username=root
    db.password=12356
    
  • 编写spring配置文件

    • 加载外部属性文件
    • 装配DruidDataSource
    <?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
           https://www.springframework.org/schema/context/spring-context.xsd">
        <!--    加载外部属性文件-->
        <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
        <!--    装配DruidDataSource-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${db.driverClassName}"></property>
            <property name="url" value="${db.url}"></property>
            <property name="username" value="${db.username}"></property>
            <property name="password" value="${db.password}"></property>
        </bean>
    </beans>
    

测试

    public void testDruid() throws SQLException {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext_druid.xml");
        DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
        DruidPooledConnection connection = dataSource.getConnection();
        System.out.println(connection);
    }

在这里插入图片描述

第七章 Spring中FactoryBean【工厂bean】

7.1 FactoryBean的概述

  • Spring中有两种类型的bean
    • 一种是普通bean
    • 另一种是工厂bean【可参与对象的创建过程中】

7.2 FactoryBean使用

public class MyFactoryBean implements FactoryBean<Dept> {
    /**
     * 参与对象创建过程中
     * @return
     * @throws Exception
     */
    @Override
    public Dept getObject() throws Exception {
        Dept dept = new Dept(101,"研发部门");
        return dept;
    }
    /**
     * 返回参与创建对象Class
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return Dept.class;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="dept" class="com.dudu.factory.MyFactoryBean"></bean>

</beans>
@Test
public void testFactoryBean(){
    //创建容器对象
    ApplicationContext context =
            new ClassPathXmlApplicationContext("applicationContext_factoryBean.xml");

    //获取对象
    Dept dept = context.getBean("dept", Dept.class);
    System.out.println("dept = " + dept);
}

第八章 Spring中bean的作用域【重点】

8.1 语法

  • 在bean标签中,添加scope属性,设置bean的作用域
  • 作用:决定这个bean是单实例的还是多实例的。

8.2 bean的四个作用域

类别说明
singleton在Spring的IOC容器中仅存在一个Bean实例
prototype每次调用getBean方法都返回一个新的Bean实例
request每次HTTP请求都会创建一个新的Bean实例,该作用域仅适用于WebApplicationContext环境
session同一个Session会话共享一个Bean实例,该作用域仅适用于WebApplicationContext环境
  • singleton:单例
    • 创建容器对象时,spring创建Dept对象
  • prototype:多例
    • 调用getBean()方法时,spring创建Dept对象
  • request作用域
    • 表示bean在当前请求中有效,离开当前请求失效
      • 如何高效区分是否为当前请求:看浏览器地址栏
        • 地址栏无变化:表示当前请求
        • 地址栏有变化:表示不在当前请求
  • session作用域
    • 表示bean在当前会话中有效,离开当前会话失效
      • 如何高效区分是否在当前会话:看浏览器
        • 如浏览器未关闭&未更换,默认即为当前会话
        • 如浏览器关闭或更换,不为当前会话
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
	   http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="dept" class="com.dudu.pojo.Dept" scope="prototype">
        <property name="deptId" value="101"></property>
        <property name="deptName" value="研发部门"></property>
    </bean>
</beans>

第九章 Spring中bean的生命周期&后置处理器

9.1 Spring中bean的生命周期

① 通过构造器或工厂方法创建bean实例

② 为bean的属性设置值和对其他bean的引用

调用bean的初始化方法

④ bean可以使用了

当容器关闭时,调用bean的销毁方法

Dept.java

省略了其他方法

public class Dept {
    private Integer deptId;
    private String deptName;
    private List<Employee> empList;
    private Map<Integer,Employee> empMap;
    public void init() {
        System.out.println("3、初始化");
    }
    public void destroy(){
        System.out.println("5、销毁");
    }
    public void setDeptId(Integer deptId) {
        System.out.println("2、设置bean");
        this.deptId = deptId;
    }
    public Dept() {
        System.out.println("1.无参构造");
    }
}

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--测试生命周期-->
    <bean id="lifeCycle" class="com.dudu.pojo.Dept" init-method="init" destroy-method="destroy">
        <property name="deptId" value="001"></property>
        <property name="deptName" value="教育部"></property>
    </bean>
    <!--测试后置处理器-->
    <bean class="com.dudu.processor.MyBeanPostProcessor"></bean>
</beans>

Test

    public void testLifeCycle(){
        ConfigurableApplicationContext context=new ClassPathXmlApplicationContext("applicationContext_lifecycle.xml");
        Dept lifeCycle = context.getBean("lifeCycle", Dept.class);
        System.out.println("4.使用  "+lifeCycle);
        context.close();
    }

在这里插入图片描述

9.2 设置生命周期的初始化&销毁bean对象语法

  • 在bean标签中添加以下两个属性即可
    • init-method:设置初始化方法
    • destroy-method:设置销毁方法

9.3 后置处理器【BeanPostProcessor】

  • 定义后置处理器
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Nullable
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean 初始化之前!!!");
        return bean;
    }
    @Nullable
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean 初始化之后!!!");
        return bean;
    }
}
  • 装配后置处理器
<!--    测试后置处理器-->
<bean class="com.dudu.processor.MyBeanPostProcessor"></bean>

第十章 Spring中基于xml方式自动&手动装配

10.1 手动装配

  • 手动装配:以value或ref的方式明确指定属性值都是手动装配。

10.2 自动装配

  • 自动装配:不需要明确指定,Spring自动将匹配的属性值注入bean中。

    • 语法:在bean标签中,添加autowire属性即可
  • autowire装配规则

    • byName:按照属性名称,与容器中id匹配

      • 如容器存在【属性名称=id】对象,则装配成功
    • byType:按照属性类型,与容器中class匹配

      • 如唯一匹配,则自动装配成功

      • 如匹配0个,不会报错,不会装配,默认值为:null

      • 如匹配多个,会报如下错误

        available: expected single matching bean but found 2: employeeDao,employeeDao2

  • 注意:

    • 基于xml自动装配,只能装配**【非字面量】**数值
    • 基于xml自动装配,底层使用setter注入
    • 最终不推荐使用基于xml方式的自动装配,推荐使用基于注解自动装配

第十一章 Spring中基于注解管理bean【非常重要】

约束>【注解>配置】>代码

Spring中使用注解步骤

  1. 导入aop的jar包
  2. 开启组件扫描

11.1 使用注解管理【装配】bean

  • 常用注解
    • @Component:标识一个普通组件
    • @Repository:标识一个持久化层组件
    • @Service:标识一个业务逻辑层组件
    • @Controller:标识一个【表示层、表述层、控制层】组件
  • 注意
    • Spring本质上无法区分不同层使用的注解,本质上:四个注解都是被@Component标识的,之所以拆分为四个注解的原因:提高代码可读性。
    • 默认类名首字母小写,作为bean的id
    • 使用注解的value属性可以定义bean的id,如只使用value属性时,value关键字可省略

11.2 使用注解管理【装配】bean中属性

  • @Autowired注解【重要】

    • 位置:

      • 属性上【装配方式:反射注入】
      • setXXX()方法上【装配方式:set注入】
      • 构造器上【装配方式:构造注入】
    • 作用:为对象中属性【非字面量】自动装配

    • @Autowired装配规则【原理】【面试题】:

      • 先按照byType匹配:

        • 如唯一匹配则,装配成功

        • 如匹配多个:再通过byName进行唯一筛选

          • 筛选成功【属性名=beanId】,则装配成功

          • 筛选失败【属性名!=beanId】,按照byType风格报错

            available: expected single matching bean but found 2: employeeDao,employeeDao2

        • 如匹配0个:

          • 注意required属性值问题
            • true:报如下错误
              • available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
            • false:不会报错,不会装配,默认值为:null【继续使用当前属性,会出现空指针异常】
    • 属性:required

      • 作用:表示当前注解标识的属性是否必须自动装配
        • true【默认值】:表示被@Autowired注解标识的属性,必须自动装配,如未装配数值,会报错
        • false:标识被@Autowired注解标识的属性,不必须自动装配,如未装配数值,不会报错,不会装配,默认值为:null【继续使用当前属性,会出现空指针异常】
  • @Qualifier注解

    • 作用:配合@Autowired注解使用,主要用于将容器中指定bean的Id设置到当前属性中
    • 注意:@Qualifier注解不能单独使用,需要配合@Autowired使用
  • @Value注解

    • 作用:为【字面量数值】属性,装配默认值

第十二章 组件扫描

使用注解必须,开启组件扫描

12.1 包含扫描

<!-- 假设当前项目中共有100个包,其中只想扫描dao层&service层-->
<context:component-scan base-package="com.dudu.annotation"
                        use-default-filters="false">

    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <context:include-filter type="assignable" expression="com.atguigu.annotation.controller.EmployeeController"/>
</context:component-scan>

12.2 排除扫描

<!-- 假设当前项目中共有100个包,其中不想扫描dao层&service层-->
<context:component-scan base-package="com.dudu.annotation">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

12.3 常用使用场景

<!--    
    开启组件扫描
       base-package:设置扫描当前包及其子包
-->
<context:component-scan base-package="com.dudu.annotation"></context:component-scan>

第十三章 完全注解开发【0xml配置文件】

完全注解开发:指的是使用配置类代替配置文件

  • 完全注解开发使用步骤
    1. 新建配置类
    2. 使用@Configuration注解标识当前类,是一个配置类
    3. 使用@ComponentScan标识组件扫描包
    4. 使用AnnotationConfigApplicationContext实现类代替【ClassPathXmlApplicationContext】
  • 示例代码

    //标识当前类是一个spring配置类
    @Configuration
    //开启组件扫描【base-package:设置扫描当前包及其子包】
    @ComponentScan(basePackages = "com.dudu.annotation")
    public class SpringConfig {
    }
    
    @Test
    public void test0Xml(){
        ApplicationContext context =
                new AnnotationConfigApplicationContext(SpringConfig.class);
    
        Employee employee = context.getBean("employee", Employee.class);
        System.out.println("employee = " + employee);
    
        EmployeeDaoImpl employeeDao = context.getBean("employeeDao", EmployeeDaoImpl.class);
        System.out.println("employeeDao = " + employeeDao);
    
        EmployeeServiceImpl employeeService = context.getBean("employeeService", EmployeeServiceImpl.class);
        System.out.println("employeeService = " + employeeService);
    
        EmployeeController employeeController = context.getBean("employeeController", EmployeeController.class);
        System.out.println("employeeController = " + employeeController);
    
    }
    

第十四章 Spring整合Junit4

14.1 为什么需要整合?

  • 因为节约两行代码

    //创建容器对象
    //从容器获取对象【EmployeeService】
    
14.2 Spring整合Junit4步骤
  1. 添加jar包支持【spring-test-5.3.1.jar】

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.1</version>
    </dependency>
    
  2. 指定Spring的配置文件的路径【Spring会为测试类自动底层创建容器对象】

  3. 指定Spring环境下运行Junit4的运行器

    @ContextConfiguration(classes = SpringConfig.class)
    @RunWith(SpringJUnit4ClassRunner.class)
	public class TestSpringAndJunit4 {
	    @Autowired
    	private EmployeeService employeeService;
    	@Test
    	public void testSpringAndJunit(){
        	System.out.println(employeeService);
        	employeeService.deleteEmployee(1);
    }

第十五章 代理模式

15.1 代理模式概述

  • 生活中

    • 房屋中介
    • 代驾

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O26wX9ER-1652519561982)(03Spring.assets\image-20220514161941459.png)]

  • 程序中代理

    • 实现思路

      • 实现Calc类基本方法【add()、sub()、mul()、div()】
      • 添加日志功能
    • 发现问题

      • 日志功能代码比较分散

        • 解决方案:将日志功能提取到工具类中【MyLogging】
      • 日志功能代码比较混乱

        • 解决方案:先将【非核心业务代码(日志功能)】横向提取,

          再动态织入回业务代码中

    • 解决方案

      • 为CalcImpl创建一个代理对象

15.2 手动实现动态代理思路

a) 基于接口实现动态代理: JDK动态代理

  • Proxy:JDK代理基类【类似Object】
    • Proxy.newProxyInstance():创建代理对象
  • InvocationHandler:使用当前接口实现【动态织入】效果,相当于使用InvocationHandler实现目标对象的相应功能
    • invoke()方法

b) 基于继承实现动态代理: Cglib、Javassist动态代理

15.3 实现JDK动态代理步骤

  • 创建类:MyProxy
  • 创建一个有参构造器
  • 创建一个目标对象
  • 创建一个方法【获取代理对象】

15.4 示例代码

package com.dudu.aopbefore;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyProxy {
    //创建一个目标对象
    private Object target;
    //创建一个有参构造器
    public MyProxy(Object target){
        this.target = target;
    }
    //创建一个方法【获取代理对象】
    public Object getProxyObject(){
        //代理对象
        Object obj = null;
        //获取目标对象类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        //获取目标对象【实现接口】
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //通过Proxy.newProxyInstance(),创建代理对象
        obj = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //获取方法名称
                String methodName = method.getName();
                //添加日志
                Mylogging.methodBefore(methodName,args);
                //触发目标对象的相应方法【核心业务方法(加减乘除方法)】
                Object rs = method.invoke(target,args);
                //添加日志
                Mylogging.methodAfter(methodName,rs);
                return rs;
            }
        });
        return obj;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VVr8a46s-1652699312529)(03Spring.assets\image-20220516101701759.png)]

  • 总结:代理对象不能转换为目标对象,因为代理对象与目标对象是兄弟关系,不能相互转换

第十六章 AOP框架AspectJ

16.1 AspectJ概述

  • AspectJ是Java社区里最完整最流行的AOP框架
  • 在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。

16.2 AspectJ使用步骤

  • 导入jar包

    <!--spirng-aspects的jar包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.1</version>
    </dependency>
    
  • 编写spring配置文件【applicationContext.xml】

    • 开启组件扫描
    • 开启AspectJ注解支持
    <!--    - 开启组件扫描-->
    <context:component-scan base-package="com.dudu.aopAspectJ"></context:component-scan>
    <!--    - 开启AspectJ注解支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  • 在【非核心业务】类中添加注解

    • 类上面添加【切面类】
      • @Component:标识当前类是一个普通组件
      • @Aspect:标识当前类是一个切面类
    • 方法上添加【通知】
      • @Before(value = “execution(public int com.dudu.spring.aop.Calculator.add(int , int ))”)
    @Component   //标识当前类是一个普通组件
    @Aspect     //标识当前类是一个**切面类**
    public class Mylogging {
    
        /**
         * 执行方法前代码【前置通知:在【目标方法】执行之前执行当前方法】
         */
        @Before(value = "execution(public int com.atguigu.aopAspectJ.CalcImpl.add(int, int))")
        public void methodBefore(JoinPoint joinPoint){
            //获取方法名称
            String methodName = joinPoint.getSignature().getName();
            //获取参数
            Object[] args = joinPoint.getArgs();
            System.out.println("==>执行"+methodName+"方法,参数:"+ Arrays.toString(args));
        }
        
        
    }
    
  • 总结

    • 开 头 对 象 , 是 代 理 对 象 【 c o m . s u n . p r o x y . 开头对象,是代理对象【com.sun.proxy. com.sun.proxy.Proxy13】

16.3 AOP概述

  • AOP(Aspect-Oriented Programming,面向切面编程)
    • AOP是OOP(Object-Oriented Programming,面向对象编程)的补充|扩展
  • AOP优势
    • 先横向提取,再动态织入
    • 解决了代码分散及代码混乱问题
    • 提高代码可读性及扩展性

16.4 AOP相关术语

  • 横切关注点
    • 非核心业务【如:日志功能对于计算器而言属于,非核心业务】
    • 提取之前,称之为:横切关注点
  • 切面(Aspect)
    • 封装【非核心业务】的类,称之为切面类
  • 通知(Advice)
    • 非核心业务【如:日志功能对于计算器而言属于,非核心业务】
    • 提取之后,称之为:通知
  • 目标(Target)
    • 被代理的对象,称之为目标对象
    • 如:CalcImpl【需要被帮助的对象,叫目标对象】
  • 代理(Proxy)
    • 可以代理目标对象的,称之为代理对象
    • 如:com.sun.proxy**.$Proxy6**
  • 连接点(Joinpoint)
    • 横切关注点在程序代码中的具体位置,称之为连接点【通知之前】
    • 如:被通知之前,之歌位置称之为连接点
  • 切入点(Pointcut)
    • 横切关注点在程序代码中的具体位置,称之为切入点【通知之后】
    • 如:被通知之后,之歌位置称之为切入点

16.5 AspectJ中切入点表达式

  • 语法:execution(权限修饰符 返回值类型 全类名.方法名(参数类型))

    • eg:
      • execution(public int com.dudu.aopAspectJ.CalcImpl.add(int, int))
      • execution(* com.dudu.aopAspectJ.CalcImpl.*(…))
  • 切入点表达式中的通配符

    【*】

    • 可以代表任意权限修饰符&返回值类型
    • 可以代表任意包及类名称
    • 可以代表任意方法名称

    【…】

    • 可以代表任意参数类型
  • 重用切入点表达式

    • 语法:@Pointcut()

      /**
       * 定义可重用的切入点表达式
       */
      @Pointcut(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))")
      public void myPointCut(){}
      
      
    • 重用

      /**
       * 执行方法前代码【前置通知:在【目标方法】执行之前执行当前方法】
       */
      @Before(value = "myPointCut()")
      /**
       * 后置通知【执行方法后代码】
       */
      @After("myPointCut()")
      

16.6 AspectJ中JoinPoint对象

  • 连接点对象【JoinPoint】
  • 常用API
    • Signature st = joinPoint.getSignature():获取方法签名
      • 方法签名:方法名+参数列表
      • st.getName():获取方法名称
    • Object[] args = joinPoint.getArgs(); 获取方法中参数

16.7 切面优先级

  • 语法:@Order(int index)

    • index建议使用正整数
    • 数值越小优先级越高
    • 默认值:default 2147483647
  • 示例代码

    @Component   //标识当前类是一个普通组件
    @Aspect     //标识当前类是一个**切面类**
    @Order(value = 1)
    public class Mylogging {}
    
    @Component
    @Aspect
    @Order(2)
    public class MyValidate {}
    

16.8 基于XML配置AOP框架AspectJ

<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置计算器实现类-->
    <bean id="calculator" class="com.dudu.spring.aop.xml.CalculatorImpl"></bean>

    <!--配置切面类-->
    <bean id="loggingAspect" class="com.dudu.spring.aop.xml.LoggingAspect"></bean>

    <!--AOP配置-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pointCut"
                      expression="execution(* com.dudu.spring.aop.xml.Calculator.*(..))"/>
        <!--配置切面-->
        <aop:aspect ref="loggingAspect">
            <!--前置通知-->
            <aop:before method="beforeAdvice" pointcut-ref="pointCut"></aop:before>
            <!--返回通知-->
            <aop:after-returning method="returningAdvice" pointcut-ref="pointCut" returning="result"></aop:after-returning>
            <!--异常通知-->
            <aop:after-throwing method="throwingAdvice" pointcut-ref="pointCut" throwing="e"></aop:after-throwing>
            <!--后置通知-->
            <aop:after method="afterAdvice" pointcut-ref="pointCut"></aop:after>
            <!--环绕通知-->
            <aop:around method="aroundAdvice" pointcut-ref="pointCut"></aop:around>
        </aop:aspect>
    </aop:config>
    
</beans>

第十七章 Aspect中五种通知

17.1 前置通知

  • 语法:@Before()

  • 执行时机:在目标方法执行之前执行标识方法

  • 注意事项:目标方法中存在异常【执行】

  • 示例代码

    /**
     * 执行方法前代码【前置通知:在【目标方法】执行之前执行当前方法】
     */
    @Before(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))")
    public void methodBefore(JoinPoint joinPoint){
        //获取方法名称
        String methodName = joinPoint.getSignature().getName();
        //获取参数
        Object[] args = joinPoint.getArgs();
        System.out.println("==>前置通知!执行"+methodName+"方法,参数:"+ Arrays.toString(args));
    }
    
    

17.2 后置通知

  • 语法:@After()

  • 执行时机:在目标方法执行之后执行标识方法【执行所有通知之后】

  • 注意事项:目标方法中存在异常【执行】

  • 示例代码

    /**
     * 后置通知【执行方法后代码】
     */
    @After(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))")
    public void methodAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("==>后置通知!执行"+methodName+"方法,参数:"+ Arrays.toString(args));
    }
    
    

17.3 返回通知

  • 语法:@AfterReturning(value=“切入点表达式”,returning=“rs”)

  • 执行时机:在目标方法返回结果后执行标识方法

  • 注意事项:

    • 目标方法中存在异常【不执行】
    • 返回通知要求returning的属性值与入参的参数名一致
  • 示例代码

    /**
     * 返回通知
     */
    @AfterReturning(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))",returning = "rs")
    public void afterReturning(JoinPoint joinPoint,Object rs){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("==>返回通知!执行"+methodName+"方法,结果:"+rs);
    }
    
    

17.4 异常通知

  • 语法:@AfterThrowing(value=“切入点表达式”,throwing=“ex”)

  • 执行时机:在目标方法出现异常后执行标识方法

  • 注意事项

    • 目标方法存在异常执行,不存在异常不执行
    • 异常通知要求:throwing中属性名与入参参数名一致
  • 示例代码

    /**
     * 异常通知
     */
    @AfterThrowing(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))" ,throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint,Exception ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("==>异常通知!执行"+methodName+"方法,出现异常:"+ex);
    }
    
    
  • 小结

    • 目标方法存在异常:前置通知->异常通知->后置通知
    • 目标方法不存在异常:前置通知->返回通知->后置通知

17.5 环绕通知

环绕通知:是前面四个通知结合

  • 语法:@Around

  • 注意事项

    • 使用环绕通知,参数中必须应用ProceedingJoinpoint对象
      • ProceedingJoinpoint是JoinPoint子类
      • ProceedingJoinpoint.proceed()可以调用目标对象的相应方法【加减乘除方法】
    • 使用环绕通知,必须为方法设置返回值类型,否则报错
  • 示例代码

    /**
     * 环绕通知
     */
    @Around(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))")
    public Object around(ProceedingJoinPoint pjp){
        Object rs = null;
        //获取方法名称
        String methodName = pjp.getSignature().getName();
        //获取参数
        Object[] args = pjp.getArgs();
        try {
            //前置通知
            System.out.println("==>前置通知!执行"+methodName+"方法,参数:"+ Arrays.toString(args));
            //触发目标对象的相应方法【加减乘除方法】
            rs = pjp.proceed();
            //返回通知
            System.out.println("==>返回通知!执行"+methodName+"方法,结果:"+rs);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            //异常通知
            System.out.println("==>异常通知!执行"+methodName+"方法,出现异常:"+throwable);
        } finally {
            //后置通知
            System.out.println("==>后置通知!执行"+methodName+"方法,参数:"+ Arrays.toString(args));
        }
        return rs;
    }
    
    

第十八章 Spring中JdbcTemplate

18.1 JdbcTemplate概述

  • JdbcTemplate是一个持久层框架
    • Mybatis是一个半自动化持久化层ORM框架

18.2 JdbcTemplate使用步骤

  • 导入jar包

    <!--spring-context-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!--spring-jdbc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!--spring-orm-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!--导入druid的jar包-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <!--导入mysql的jar包-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.37</version>
    </dependency>
    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
  • 编写配置文件

    • 加载外部属性文件
    • 配置DataSource数据源
    • 装配JdbcTemplate核心类库
    <!--    加载外部属性文件-->
    <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
    
    <!--    配置DataSource数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${db.driverClassName}"></property>
        <property name="url" value="${db.url}"></property>
        <property name="username" value="${db.username}"></property>
        <property name="password" value="${db.password}"></property>
    </bean>
    
    <!--    装配JdbcTemplate核心类库-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
  • 使用核心类库【JdbcTemplate】

    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    @ContextConfiguration(locations = "classpath:applicationContext_jdbcTemplate.xml")
    @RunWith(SpringJUnit4ClassRunner.class)
    public class TestJdbcTemplate {
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Test
        public void testJdbcTemplate(){
            System.out.println("jdbcTemplate = " + jdbcTemplate);
            //测试CRUD
            //添加员工信息【JdbcTemplate自动提交事务】
            String sql = "insert into tbl_dept(dept_id,dept_name) values(?,?)";
            jdbcTemplate.update(sql,4,"程序员鼓励师!");
    
        }
    }
    

18.3 JdbcTemplate常用API

  • update(String sql,Object… args):增删改通用方法
  • batchUpdate(String sql,List<Object[]> list):批量增删改通用方法
  • queryForObject(String sql,Class clazz,Object… args):查询单个数值
  • queryForObject(String sql,RowMapper rm,Object… args):查询单个对象
  • query(String sql,RowMapper rm,Object… args):查询多个对象

18.4 JdbcTemplate搭建三层

<!--    开启组件扫描-->
<context:component-scan base-package="com.dudu"></context:component-scan>

<!--    加载外部属性文件-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>

<!--    配置DataSource数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${db.driverClassName}"></property>
    <property name="url" value="${db.url}"></property>
    <property name="username" value="${db.username}"></property>
    <property name="password" value="${db.password}"></property>
</bean>

<!--    装配JdbcTemplate核心类库-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>
@Repository("deptDao")
public class DeptDaoImpl implements DeptDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void insertDept(Dept dept) {
        String sql = "insert into tbl_dept(dept_id,dept_name) values(?,?)";
        jdbcTemplate.update(sql,dept.getDeptId(),dept.getDeptName());
    }


}

第十九章 Spring声明式事务管理【非常重要】

19.1 回顾事务相关知识点

  • 事务四大特性【ACID】
    • 原子性【atomicity】
    • 一致性【consistency】
    • 隔离性【isolation】
    • 持久性【durability】
  • 事务三种行为【操作】
    • .0开0启事务:connection.setAutoCommit(false);
    • …提交事务:connection.commit();
    • 回滚事务:connection.rollback();

19.2 Spring中支持两种事务管理

  • 编程式事务管理【使用原生JDBC的API管理事务】

    1. 获取数据库连接Connection对象

    2. 取消事务的自动提交【开启事务】

    3. 执行操作【核心业务代码】

    4. 正常完成操作时手动【提交事务】

    5. 执行失败时【回滚事务】

    6. 关闭相关资源

    • 发现问题
      • 【核心业务代码】与事务管理代码相耦合,导致事务管理代码比较分散&比较混乱
    • 解决方案
      • 使用AOP思想【框架:AspectJ】,先将事务管理代码横向提取,再动态织入回核心业务代码中【称之为:声明式事务管理
  • 声明式事务管理

19.3 声明式事务管理使用

  1. 导入jar包【添加AspectJ支持】

    <!--spirng-aspects的jar包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.1</version>
    </dependency>
    
  2. 编写配置文件

    • 装配事务管理器【DataSourceTransactionManager】
    • 开启事务注解支持
     <!--    开启组件扫描-->
        <context:component-scan base-package="com.dudu"></context:component-scan>
    
        <!--    加载外部属性文件-->
        <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
    
        <!--    配置DataSource数据源-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${db.driverClassName}"></property>
            <property name="url" value="${db.url}"></property>
            <property name="username" value="${db.username}"></property>
            <property name="password" value="${db.password}"></property>
        </bean>
    
        <!--    装配JdbcTemplate核心类库-->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
    <!--    装配事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!--    开启事务注解支持-->
    <tx:annotation-driven></tx:annotation-driven>
    
  3. 在需要事务管理的业务上添加注解支持【@Transactional】

    @Transactional
    public void purchase(String username, String isbn) {
        //查询book价格
        Integer price = bookShopDao.findBookPriceByIsbn(isbn);
        //修改库存
        bookShopDao.updateBookStock(isbn);
        //修改余额
        bookShopDao.updateUserAccount(username, price);
    }
    
  • 注意:添加事务管理后,获取代理对象【代理对象不能转换为目标对象】

19.4 Spring中声明式事务管理属性

  • propagation【事务传播行为】

    • Propagation概述:当一个事务方法被另一个事务方法调用时,此时设置当前事务方法的传播行为。

      • 如:执行事务方法method1()【事务x】之后调用事务方法method2()【事务y】,此时需要设置method()2的执行事务【事务x或y】

    在这里插入图片描述

    • 类型:Propagation【枚举类型】

      • REQUIRED【默认值】

        传播属性描述
        REQUIRED如果有事务在运行,当前的方法就在这个事务内运行;否则就启动一个新的事务,并在自己的新事务内运行。
        REQUIRES_NEW当前的方法****必须****启动新事务,并在自己的事务内运行;如果有事务正在运行,应该将它挂起。
        SUPPORTS如果有事务在运行,当前的方法就在这个事务内运行,否则可以不运行在事务中。
        NOT_SUPPORTED当前的方法不应该运行在事务中,如果有运行的事务将它挂起
        MANDATORY当前的方法必须运行在事务中,如果没有正在运行的事务就抛出异常。
        NEVER当前的方法不应该运行在事务中,如果有正在运行的事务就抛出异常。
        NESTED如果有事务正在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行。
    • 事务传播行为应用场景

      @Service("cashierService")
      public class CashierServiceImpl implements CashierService {
      /**
       * 去结账
       *      结账时,账号余额支持买多少本书,就允许用户购买
       *          如账户余额不足,怎么办?
       *              老板1:不允许用户购买所有图书
       * @param username
       * @param isbns
       */
      @Transactional
      public void checkOut(String username, List<String> isbns) {
          for (String isbn : isbns) {
              bookShopService.purchase(username,isbn);
          }
      	}
      }
      
      @Service("bookShopService")
      public class BookShopServiceImpl implements BookShopService {
      
      /**
           * //买书->查询book价格->修改库存->修改余额
           *
           * @param username
           * @param isbn
           */
          @Transactional(propagation = Propagation.REQUIRED)
          public void purchase(String username, String isbn) {
              //查询book价格
              Integer price = bookShopDao.findBookPriceByIsbn(isbn);
              //修改库存
              bookShopDao.updateBookStock(isbn);
              //修改余额
              bookShopDao.updateUserAccount(username, price);
          }
      }
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gdwLyuEh-1652787842358)(03Spring.assets\image-20220517143630485.png)]

      @Service("cashierService")
      public class CashierServiceImpl implements CashierService {
      /**
       * 去结账
       *      结账时,账号余额支持买多少本书,就允许用户购买
       *          如账户余额不足,怎么办?
       *              老板2:不允许购买【导致余额不足的】最后一本书
           	解决思路:期望purchase()尽量多的执行【期望purchase()是一个独立事务】
       * @param username
       * @param isbns
       */
      @Transactional
      public void checkOut(String username, List<String> isbns) {
          for (String isbn : isbns) {
              bookShopService.purchase(username,isbn);
          }
      	}
      }
      
      @Service("bookShopService")
      public class BookShopServiceImpl implements BookShopService {
      
      /**
           * //买书->查询book价格->修改库存->修改余额
           *
           * @param username
           * @param isbn
           */
          @Transactional(propagation = Propagation.REQUIRES_NEW)
          public void purchase(String username, String isbn) {
              //查询book价格
              Integer price = bookShopDao.findBookPriceByIsbn(isbn);
              //修改库存
              bookShopDao.updateBookStock(isbn);
              //修改余额
              bookShopDao.updateUserAccount(username, price);
          }
      }
      

在这里插入图片描述

  • isolation【事务隔离级别】

    • 概述:事务隔离级别就是事务与事务之间的隔离等级

    • 隔离级别

      • READ UNCOMMITTED【读未提交】:1
      • READ COMMITTED【读已提交】:2
      • REPEATABLE READ【可重复读】:4
      • SERIALIZABLE【串行化】:8
    • 各种数据库产品对事务隔离级别的支持程度

      OracleMySQL
      READ UNCOMMITTED×
      READ COMMITTED√(默认)
      REPEATABLE READ×√(默认)
      SERIALIZABLE
    • 图解隔离级别

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Pg7WUTp-1652787842358)(03Spring.assets\image-20220517152908993.png)]

  • readonly【事务只读】

    • 默认值:false

    • 作用:设置事务是否为只读

      • 一般只查询数据时,会设置数据只读

      • 如需增删改操作,不能将事务设置为只读,否则会报错

        Connection is read-only. Queries leading to data modification are not allowed

    • 优势:提交一点性能

  • timeout【事务超时】

    • 作用:设置事务超时【超出指定时间,强制事务回滚】
    • 类型:int类型
    • 单位:秒
  • rollbackFor|noRollbackFor【事务回滚】

    • rollbackFor:设置回滚异常类型
    • noRollbackFor:设置不回滚异常类型
  • 示例代码

    @Transactional(propagation = Propagation.REQUIRES_NEW,
                    readOnly = false,
                    timeout = 3,
                    noRollbackFor = ArithmeticException.class)
    public void purchase(String username, String isbn) {
        //查询book价格
            Integer price = bookShopDao.findBookPriceByIsbn(isbn);
    
            try {
                int i = 1/0;
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    //        try {
    //            Thread.sleep(5000);
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }
    
            //修改库存
            bookShopDao.updateBookStock(isbn);
            //修改余额
            bookShopDao.updateUserAccount(username, price);
    
    }
    

19.5 基于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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:druid.properties"></context:property-placeholder>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="initialSize" value="${jdbc.initialSize}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>
    </bean>

    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--配置数据源属性-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置BookShopDaoImpl-->
    <bean id="bookShopDao" class="com.dudu.spring.tx.xml.BookShopDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

    <!--配置BookShopServiceImpl-->
    <bean id="bookShopService" class="com.dudu.spring.tx.xml.BookShopServiceImpl">
        <property name="bookShopDao" ref="bookShopDao"></property>
    </bean>

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

    <!--配置声明式事务-->
    <tx:advice id="tx" transaction-manager="transactionManager">
        <!--设置添加事务的方法-->
        <tx:attributes>
            <!--设置查询的方法的只读属性为true-->
            <tx:method name="find*" read-only="true"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED"></tx:method>
        </tx:attributes>
    </tx:advice>

    <!--AOP配置-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pointCut"
                      expression="execution(* com.dudu.spring.tx.xml.BookShopServiceImpl.purchase(..))"/>
        <!--将事务方法和切入点表达式关联起来-->
        <aop:advisor advice-ref="tx" pointcut-ref="pointCut"></aop:advisor>
    </aop:config>

</beans>

第二十章 Spring5新特性

20.1 Spring5新增注解

名称含义可标注位置
@Nullable可以为空@Target({ElementType.*METHOD*, ElementType.*PARAMETER*, ElementType.*FIELD*}
@NonNull不应为空@Target({ElementType.*METHOD*, ElementType.*PARAMETER*, ElementType.*FIELD*})
@NonNullFields在特定包下的字段不应为空@Target(ElementType.*PACKAGE*) @TypeQualifierDefault(ElementType.*FIELD*)
@NonNullApi参数和方法返回值不应为空@Target(ElementType.*PACKAGE*) @TypeQualifierDefault({ElementType.*METHOD*, ElementType.*PARAMETER*})
  • @Nullable注解为例
    • 位置:可以使用在方法上面&属性上面&参数前面
    • 作用:表示方法返回可以为空&属性可以为空&参数可以为空
      • 消除NullPointerException

20.2 Spring5整合Log4j2

  • 导入jar包

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.11.2</version>
        <scope>test</scope>
    </dependency>
    
  • 编写配置文件

    • 位置:src/main/resource

    • 名称:log4j2.xml

    • 示例代码

      <?xml version="1.0" encoding="UTF-8"?>
      <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
      <!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
      <configuration status="INFO">
          <!--先定义所有的appender-->
          <appenders>
              <!--输出日志信息到控制台-->
              <console name="Console" target="SYSTEM_OUT">
                  <!--控制日志输出的格式-->
                  <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
              </console>
          </appenders>
          <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
          <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
          <loggers>
              <root level="DEBUG">
                  <appender-ref ref="Console"/>
              </root>
          </loggers>
      </configuration>
      

20.3 Spring5整合Junit5

  • 导入junit5的相关jar包【并移除junit4的相关jar包】

    <!--spring-test-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.1</version>
    </dependency>
    
    <!--导入junit4.12-->
    <!--        <dependency>-->
    <!--            <groupId>junit</groupId>-->
    <!--            <artifactId>junit</artifactId>-->
    <!--            <version>4.12</version>-->
    <!--            <scope>test</scope>-->
    <!--        </dependency>-->
    
    <!--junit5-->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.7.2</version>
        <scope>test</scope>
    </dependency>
    
  • 在测试类上添加如下注解

    • 方式一

      //junit5
      @ContextConfiguration(locations = "classpath:applicationContext_transactionManager.xml")
      @ExtendWith(SpringExtension.class)
      public class TestTransactionManager {}
      
    • 方式二

      //junit5方式二
      @SpringJUnitConfig(locations="classpath:applicationContext_transactionManager.xml")
      public class TestTransactionManager {}
      

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值