一、第一个例子

image.png

1.引入 maven 依赖 pom.xml

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-context</artifactId>
  4. <version>5.2.5.RELEASE</version>
  5. </dependency>

2.定义接口与实体类

public interface SomeService {

    void doSome();
}
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
        System.out.println("doSome方法执行了");
    }
}

3.创建Spring配置文件
在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名 称为applicationContext.xml。 spring 配置中需要加入约束文件才能正常使用,约束文件是 xsd 扩展名。

<?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="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"/>

</beans>

注意事项:
:用于定义一个实例对象。一个实例对应一个 bean 元素。
id:该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean,Bean 与 Bean 间的依
赖关系也是通过 id 属性关联的。
class:指定该 Bean 所属的类,注意这里只能是类,不能是接口。

4.定义测试类

package com.bjpowernode;

import com.bjpowernode.service.SomeService;
import com.bjpowernode.service.impl.SomeServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
        @Test
    public void test02(){
        //1.指定spring配置文件的名称
            String config="beans.xml";

           // 2.创建表示spring容器的对象,Applicationcontext
           // Applicationcontext就是表示Spring容器,通过容器获取对象了
           // ClassPathxmlApplicationContext:表示从类路径中加载spring的配置文件
            ApplicationContext ac= new ClassPathXmlApplicationContext(config);

            //从容器中获取某个对象,你要调用对象的方法
            // getBean("配置文件中的bean的id值")
           SomeService service=(SomeService) ac.getBean("someService");

           //使用spring创建好的对象
            service.doSome();

        }

    }

注意三点:
1.spring默认创建对象的时间:在创建spring的容器时,会创建配置文件中所有对象
2.spring创建对象:默认调用的是无参数构造方法
3.使用 spring 创建非自定义类对象
spring 配置文件加入 java.util.Date 定义:

MyTest 测试类中:
调用 getBean(“myDate”); 获取日期类对象。


二、注入

di :依赖注入,表示创建对象,给属性赋值。

di的实现有两种:
1.在spring的配置文件中,使用标签和属性完成,叫做基于XML的di实现
2.使用spring中的注解,完成属性赋值,叫做基于注解的id实现

di的语法分类:

  1. set注入(设置注入) : spring调用类的set方法,在set方法可以实现属性的赋值。(大多数)
  2. 构造注入,spring调用类的有参数构造方法,创建对象。在构造方法中完成赋值。

Set注入

1)简单类型的set注入(一个property只能给一个属性赋值)
注意:只要有set方法就行,跟属性没关系


2)引用类型的set注入


public class School {
    private String  name;
    private String  address;
}
//set
public class Student {
    private String name;
    private int age;

    private School school;
}
//set
<bean id = "myStudent" class="com.bjpowernode.ba02.Student">
        <property name="name" value="李四"/>
        <property name="age" value="22"/>
         <property name="school" ref="mySchool"/>
    </bean>

    <bean id ="mySchool" class="com.bjpowernode.ba02.School">
            <property name="name" value="北京大学"/>
            <property name="address" value="北京的海淀区"/>
    </bean>

构造注入

构造注入:spring调用类有参数构造方法,在创建对象的同时,在构造方法中给属性赋值。
构造注入使用标签
标签:一个表示构造方法一个参数。
标签属性:
name :表示构造方法的形参名
index:表示构造方法的参数的位置,参数从左往右位置是0, 1 ,2的顺序
value :构造方法的形 参类型是简单类型 的使用value
ref :构造方法的形参类型是引用类型的,使用ref

使用name属性完成构造注入

    <!--使用name属性完成构造注入-->
    <bean id = "myStudent" class="com.bjpowernode.ba03.Student">
        <constructor-arg name="myname" value="李四"/>
        <constructor-arg name="myage" value="22"/>
         <constructor-arg name="myschool" ref="mySchool"/>
    </bean>


  <!--声明School对象-->
    <bean id ="mySchool" class="com.bjpowernode.ba03.School">
            <property name="name" value="北京大学"/>
            <property name="address" value="北京的海淀区"/>
    </bean>

使用index属性

<!--使用index属性-->
    <bean id ="myStudent2" class="com.bjpowernode.ba03.Student">
        <constructor-arg index="0" value="李四"/>
        <constructor-arg index="1" value="22"/>
        <constructor-arg index="2" ref="mySchool"/>
    </bean>


    <!--声明School对象-->
    <bean id ="mySchool" class="com.bjpowernode.ba03.School">
            <property name="name" value="北京大学"/>
            <property name="address" value="北京的海淀区"/>
    </bean>

自动注入byName


引用类型的自动注入: spring框架根据某些规则可以给引用类型赋值。·不用你在给引用类型赋值了
使用的规则常用的是byName,byType.
1.byName(按名称注入) : java类中引用类型的属性名和spring容器中(配置文件)的id名称一样,
且数据类型是一致的,这样的容器中的bean , spring能够赋值给引用类型。

语法:<br /><bean id="xx" class="yyy" autowire="byName"><br />    简单类型属性赋值<br /></bean
 <!--声明student对象-->
    <bean id = "myStudent" class="com.bjpowernode.ba04.Student" autowire="byName">
        <property name="name" value="李四"/>
        <property name="age" value="22"/>
        <!--引用类型-->
        <!--
         <property name="school" ref="mySchool"/>
         -->
    </bean>

    <bean id ="school" class="com.bjpowernode.ba04.School">
            <property name="name" value="清华大学"/>
            <property name="address" value="北京的海淀区"/>
    </bean>

自动注入byType

2.byType(按类型注入) : java类中引用类型的数据类型和spring容器中(配置文件)的class属性
是同源关系的,这样的bean能够赋值给引用类型

同源就是一类的意思:
1.java类中引用类型的数据类型和bean的class的值是―样的。
2.java类中引用类型的数据类型和bean的class的值父子类关系的。
3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的

语法:

简单类型属性赋值

注意:在byType中,在xml配置文件中声明bean只能有一个符合条件的,
多余一个是错误的

<!--声明student对象-->
    <bean id = "myStudent" class="com.bjpowernode.ba04.Student" autowire="byType">
        <property name="name" value="李四"/>
        <property name="age" value="22"/>
        <!--引用类型-->
        <!--
         <property name="school" ref="mySchool"/>
         -->
    </bean>
                            //java中引用类型的数据类型和这个class的类型一样
    <bean id ="school" class="com.bjpowernode.ba04.School">
            <property name="name" value="清华大学"/>
            <property name="address" value="北京的海淀区"/>
    </bean>

多个spring配置文件

主的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- spring-total 表示主配置文件: 包含其他的配置文件的,住配置文件一般是不定义对象的

    语法:<import resource="其他配置文件的路径"

    关键字: "classpath:"  表示类路径(class所在的路径),在spring配置中,要指定其他 文件的位置
            需要使用classpath,告诉spring到哪去加载读取文件。
    -->

    <!--加载的是文件列表-->
    <import resource="classpath:ba06/spring-school.xml" />
    <import resource="classpath:ba06/spring-student.xml" />

    <!--  <import resource="classpath:ba06/spring-*.xml" /> -->

</beans>

spring-school配置文件

<?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 ="school" class="com.bjpowernode.ba06.School">
            <property name="name" value="清华大学"/>
            <property name="address" value="北京的海淀区"/>
    </bean>

</beans>

`spring-student配置文件

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


    <!--声明student对象-->
    <bean id = "myStudent" class="com.bjpowernode.ba06.Student" autowire="byType">
        <property name="name" value="李四"/>
        <property name="age" value="22"/>
        <!--引用类型-->
        <!--
         <property name="school" ref="mySchool"/>
         -->
    </bean>

</beans>

三、注解

使用注解的步骤:
1.加入maven的依赖spring-context ,在你加入spring-context的同时,间接加入spring-aop的依赖。使用注解必须使用spring-aop依赖
2.在类中加入spring的注解(多个不同功能的注解)
3.在spring的配置文件中,加入一个组件扫描器的标签,说明注解在你的项目中的位置

学习的注解:
1.@Component
2.@Respotory
3.@Service
4.@Controller
5.@Value
6.@Autowired
7.@Resource

1.创建对象的

@component: 创建对象的,等同于的功能
属性: value就是对象的名称,也就是bean的id值,
value的值是唯一的,创建的对象在整个spring容器中就一个
位置:在类的上面

@component(vaLue =”mystudent”)等同于
<bean id=”mystudent” class=”com.bjpowernode.ba01.Student”/>

注意:
spring中和@component功能一致,创建对象的注解还有:
1.@Repository (用在持久层类的上面):放在dao的实现类上面,
表示创建dao对象,dao对象是能访问数据库的。
2.@Service(用在业务层类的上面)∶放在service的实现类上面,
创建service对象,service对象是做业务处理,可以有事务等功能的。
3.@ControlLer(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,
控制器对象,能够接受用户提交的参数,显示请求的处理结果。
以上三个注解的使用语法和@component一样的。都能创建对象,但是这三个注解还有额外的功能。
@Repository , @Service , @controller是给项目的对象分层的。

最简单的方式

package com.bjpowernode.ba01;

import org.springframework.stereotype.Component;
@Component(value="myStudent")

//@Component("myStudent")

//不指定对象名称,由spring提供默认名称:类名的首字母小写
//@Component

public class Student {
    private String name;
    private Integer age;

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

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
<?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">

    <!--声明组件扫描器(component-scan) ,组件就是java对象
        base-package :指定注解在你的项目中的包名。
        component-scan工作方式: spring会扫描遍历base-package指定的包,
        把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。  <-->

    <context:component-scan base-package="com.bjpowernode.ba01"/>

</beans>

image.png

扫描多个包的方式

 <!--指定多个包的三种方式-->
<!--第一种方式:使用多次组件扫描器,指定不同的包-->
    <context:component-scan base-package="com.bjpowernode.ba01"/>
    <context:component-scan base-package="com.bjpowernode.ba02"/>
<!--第二种方式:使用分隔符(;或,) 分隔多个包名-->
    <context:component-scan base-package="com.bjpowernode.ba01;com.bjpowernode.ba02"/>
    <!--第三种方式: 指定父包-->
    <context:component-scan base-package="com.bjpowernode"/>

2.简单的类型属性赋值

@VaLue:简单类型的属性赋值
属性: value 是string类型的,表示简单类型的属性值位置:1.在属性定义的上面,无需set方法,推荐使用。
2.在set方法的上面

@Component(value="myStudent")
public class Student {
    @Value(value="张飞")
    private String name;
    @Value(value  ="29")
    private Integer age;

引用类型的赋值

引用类型
@Autowired: spring框架提供的注解,实现引用类型的赋值。
spring中通过注解给引用类型赋值,使用的是自动注入原理,支持byName,byType
@Autowired:默认使用的是byType自动注入。

位置:1)在属性定义的上面,无需set方法,推荐使用
2)在set方法的上面

package com.bjpowernode.ba03;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("mySchool")
public class School {
    @Value("北京大学")
    private String name;
    @Value("北京的海淀区")
    private String  address;

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

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
package com.bjpowernode.ba03;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value="myStudent")
public class Student {
    @Value(value="张飞")
    private String name;
    @Value(value  ="29")
    private Integer age;

    @Autowired
    private School school;

    public Student() {
        System.out.println("===student无参构造方法==");
    }

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

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}
<?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:component-scan base-package="com.bjpowernode.ba03"/>


</beans>

image.png

Autowired byName方式

如果要使用byName方式,需要做的是:
1.在属性上面加入@Autowired
2.在属性上面加入@Qualifier(value=”bean的id”):表示使用指定名称的bean完成赋值

package com.bjpowernode.ba03;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("mySchool")
public class School {
    @Value("北京大学")
    private String name;
    @Value("北京的海淀区")
    private String  address;

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

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
package com.bjpowernode.ba03;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value="myStudent")
public class Student {
    @Value(value="张飞")
    private String name;
    @Value(value  ="29")
    private Integer age;

    @Autowired
    @Qualifier("mySchool")
    private School school;

    public Student() {
        System.out.println("===student无参构造方法==");
    }

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

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

Autowired的required属性

属性:required,是一个boolean类型的,默认true
required=true :表示引用类型赋值失败,程序报错,并终止执行。
required=false ;引用类型如果赋值失败,程序正常执行,引用类型是null

@Autowired(required =.false)

Autowired的resource属性

引用类型@Resource:
来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值使用的也是自动注入原理,支持 byName , byType .默认是byName
位置:
1.在属性定义的上面,无需set方法,推荐使用。
2.在set方法的上面

注意:
@Resource只使byName方式,需要增加一个属性name
name的值是bean的id(名称)

package com.bjpowernode.ba03;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component(value="myStudent")
public class Student {
    @Value(value="张飞")
    private String name;
    @Value(value  ="29")
    private Integer age;

   @Resource  //默认nyName :先使用byName自动注入,如果byName复制失败,在使用byType
    private School school;

    public Student() {
        System.out.println("===student无参构造方法==");
    }

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

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}
package com.bjpowernode.ba03;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("mySchool")
public class School {
    @Value("北京大学")
    private String name;
    @Value("北京的海淀区")
    private String  address;

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

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

image.png

四、AOP面向切面编程

4.1aop的理解

1.动态代理
实现方式:jdk动态代理,使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。
jdk动态代理要求目标类必须实现接口

cglib动态代理:第三方的工具库,创建代理对象,原理是继承。 通过继承目标类,创建子类。
子类就是代理对象。 要求目标类不能是final的, 方法也不能是final的

2.动态代理的作用:
1)在目标类源代码不改变的情况下,增加功能。
2)减少代码的重复
3)专注业务逻辑代码
4)解耦合,让你的业务功能和日志,事务非业务功能分离。

3.Aop:面向切面编程, 基于动态代理的,可以使用jdk,cglib两种代理方式。
Aop就是动态代理的规范化, 把动态代理的实现步骤,方式都定义好了,
让开发人员用一种统一的方式,使用动态代理。

  1. AOP(Aspect Orient Programming)面向切面编程
    Aspect: 切面,给你的目标类增加的功能,就是切面。 像上面用的日志,事务都是切面。
    切面的特点: 一般都是非业务方法,独立使用的。
    Orient:面向, 对着。
    Programming:编程

    oop: 面向对象编程

    怎么理解面向切面编程 ?
    1)需要在分析项目功能时,找出切面。
    2)合理的安排切面的执行时间(在目标方法前, 还是目标方法后)
    3)合理的安全切面执行的位置,在哪个类,哪个方法增加增强功能

    术语:
    1)Aspect:切面,表示增强的功能, 就是一堆代码,完成某个一个功能。非业务功能,
    常见的切面功能有日志, 事务, 统计信息, 参数检查, 权限验证。

    2)JoinPoint:连接点 ,连接业务方法和切面的位置。 就某类中的业务方法
    3)Pointcut : 切入点 ,指多个连接点方法的集合。多个方法
    4)目标对象: 给哪个类的方法增加功能, 这个类就是目标对象
    5)Advice:通知,通知表示切面功能执行的时间。

    说一个切面有三个关键的要素:
    1)切面的功能代码,切面干什么
    2)切面的执行位置,使用Pointcut表示切面执行的位置
    3)切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。

4.2 aop的实现

5.aop的实现
aop是一个规范,是动态的一个规范化,一个标准
aop的技术实现框架:
1.spring:spring在内部实现了aop规范,能做aop的工作。
spring主要在事务处理时使用aop。
我们项目开发中很少使用spring的aop实现。 因为spring的aop比较笨重。

2.aspectJ: 一个开源的专门做aop的框架。spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。
aspectJ框架实现aop有两种方式:
1.使用xml的配置文件 : 配置全局事务
2.使用注解,我们在项目中要做aop功能,一般都使用注解, aspectj有5个注解。

6.学习aspectj框架的使用。
1)切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强)
在aspectj框架中使用注解表示的。也可以使用xml配置文件中的标签
1)@Before
2)@AfterReturning
3)@Around
4)@AfterThrowing
5)@After


image.png

以上表达式共 4 个部分。
execution(访问权限 方法返回值 方法声明(参数) 异常类型)

image.png

举例:

execution(public (..))
指定切入点为:任意公共方法。
execution( set(..))
指定切入点为:任何一个以“set”开始的方法。
execution( com.xyz.service..(..))
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(
com.xyz.service...(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后
面必须跟“”,表示包、子包下的所有类。
execution(
..service..*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点

4.3使用aspectj框架实现aop。

ch06-aop-aspectj:使用aspectj框架实现aop。
使用aop:目的是给已经存在的一些类和方法,增加额外的功能。前提是不改变原来的类的代码。
使用aspectj实现aop的基本步骤:
1.新建maven项目
2.加入依赖
    1) spring依赖2 ) aspectj依赖3 ) junit单元测试
3.创建目标类:接口和他的实现类。要做的是给类中的方法增加功能
4.创建切面类:普通类
    1)在类的上面加入@Aspect
    2)在类中定义方法,方法就是切面要执行的功能代码在方法的上面加入aspectj中的通知注解﹐
        例如@Before有需要指定切入点表达式execution()

5.创建spring的配置文件:声明对象,把对象交给容器统―管理声明对象你可以使用注解或者xml配置文件<bean>
  1)声明目标对象
  2)声明切面类对象
  3)声明aspectj框架中的自动代理生成器标签。
        自动代理生成器:用来完成代理对象的自动创建功能的。
6.创建测试类,从spring容器中获取目标对象(实际就是代理对象)。
通过代理执行方法,实现aop的功能增强。

image.png

package com.bjpowernode.ba01;

public interface  SomeService {

    void doSome(String name,Integer age);
}
package com.bjpowernode.ba01;


//目标类
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, Integer age) {
        //给doSome方法增加一个功能,在doSome()执行之前,输出方法的执行时间
        System.out.println("===目标方法doSome===");
    }
}
package com.bjpowernode.ba01;


import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;


/*@Aspect:是aspectj框架中的注解.
        作用:表示当前类是切面类。
        切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 */
@Aspect
public class MyAspect {

    /*定义方法,方法是实现切面功能的。
  方法的定义要求:
  1.公共方法public
  2.方法没有返回值
  3.方法名称自定义
  4.方法可以有参数,也可以没有参数,
        如果有参数,参数不是自定义的,有几个参数类型可以使用。
     */

   /* @Before:前置通知注解
    属性:vaLue,是切入点表达式,表示切面的功能执行的位置。
    位置:在方法的上面

    特点:
            1.在目标方法之前先执行的
            2.不会改变目标方法的执行结果
            3.不会影响目标方法的执行。
    */

  /*  指定通知方法中的参数: JoinPoint
    JoinPoint:业务方法,要加人切面功能的业务方法
作用是:可以在通知方法中获取方法执行时的信息,例如方法名称,方法的实参。如果你的切面功能中需要用到方法的信息,就加入JoinPoint.
       这个JoinPoint参数的值是由框架赋予,必须是第一个位置的参数  */

    @Before(value = "execution(public void com.bjpowernode.ba01.SomeServiceImpl.doSome(String,Integer))")
    /*切入点表达式多种写法
    @Before(value = "execution(void com.bjpowernode.ba01.SomeServiceImpl.doSome(String,Integer))")

    @Before(value = "execution(void  *..SomeServiceImpl.doSome(String,Integer))")

    @Before(value = "execution(* *..SomeServiceImpl.doSome(..))")

     @Before(value = "execution(* *..SomeServiceImpl.do*(..))")



    */
    public void myBefore(JoinPoint jp){
        //就是你切面要执行的功能代码
        System.out.println("前置通知,切面功能:在目标方法之前输出执行时间:"+new Date());


        //获取方法的完整定义
        System.out.println("方法的签名(定义)+"+jp.getSignature());
        System.out.println("方法的签名(定义)+"+jp.getSignature().getName());
        //获取方法的实参
        Object args[]=jp.getArgs();
        for(Object arg:args){
            System.out.println("参数"+arg);
        }
    }
}
<?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">


    <!-- 把对象交给spring容器,由spring容器统一创建,管理对象-->
    <!--声明目标对象-->
    <bean id="someService" class="com.bjpowernode.ba01.SomeServiceImpl"/>

    <!--声明切面类对象-->
    <bean id="myAspect" class="com.bjpowernode.ba01.MyAspect"/>

    <!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象
        创建代理对象是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象
        所以目标对象就是被修改后的代理对象

        aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象

    注意:
        目标类没有接口,使用cglib动态代理,spring框架会自动应用cglib
        com.bjpowernode. ba07.SomeServiceImpl$$EnhancerBySpringcGLIB$$575c8b90

        如果你期望目标类有接口,使用cglib代理
        proxy-target-class="true":告诉框架,要使用cglib动态代理

        -->

    <aop:aspectj-autoproxy/>
</beans>
package com.bjpowernode;

import com.bjpowernode.ba01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest01 {

    @Test
    public void test01(){

        String config="applicationContext.xml";

        ApplicationContext ctx=new ClassPathXmlApplicationContext(config);

        //从容器中获取目标对象

        SomeService proxy = (SomeService) ctx.getBean("someService");

        //通过代理的对象执行方法,实现目标方法执行时,增强了功能
        proxy.doSome("lisi",20);

    }

}

4.3.1后置通知

package com.bjpowernode.ba02;

public interface  SomeService {

    void doSome(String name, Integer age);

    String doOther(String name,Integer age);
}
package com.bjpowernode.ba02;


//目标类
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, Integer age) {
        //给doSome方法增加一个功能,在doSome()执行之前,输出方法的执行时间
        System.out.println("===目标方法doSome===");
    }

    @Override
    public String doOther(String name, Integer age) {
        System.out.println("===目标方法doSome===");
        return "abcd";
    }
}
package com.bjpowernode.ba02;


import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MyAspect {
/**
    后置通知定义方法,方法是实现切面功能的。
    方法的定义要求:
        1.公共方法 public
        2.方法没有返回值
        3.方法名称自定义
        4.方法有参数的,推荐是object ,参数名自定义
*/

/**
 * @AfterReturning:后置通知
 * 属性:1.value切入点表达式
 *      2.returning自定义的变量,表示目标方法的返回值的。
 *         自定义变量名必须和通知方法的形参名一样。
 *      位置:在方法定义的上面
 * 特点:
 *  1。在目标方法之后执行的。
 *  2.能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
 *      Object res=doOther();
 *  3,可以修改这个返回值
 *
 *  后置通知的执行
 *  object res = doOther();
 *      参数传递: 传值,传引用
 *  myAfterReturing(res);
 *  System.out.println("res="+res)
 */

@AfterReturning(value="execution(* *..SomeServiceImpl.doOther(..))",
                returning = "res")
public void myAfterReturing(Object res){
    //Object res:是目标方法执行后的返回值,根据返回值做你的却面功能处理.
    System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);

}

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


    <!-- 把对象交给spring容器,由spring容器统一创建,管理对象-->
    <!--声明目标对象-->
    <bean id="someService" class="com.bjpowernode.ba02.SomeServiceImpl"/>

    <!--声明切面类对象-->
    <bean id="myAspect" class="com.bjpowernode.ba02.MyAspect"/>

    <!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象
        创建代理对象是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象
        所以目标对象就是被修改后的代理对象

        aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象

        -->

    <aop:aspectj-autoproxy/>
</beans>
package com.bjpowernode;

import com.bjpowernode.ba02.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest02 {
    @Test
    public void test01(){

        String config="applicationContext.xml";

        ApplicationContext ctx=new ClassPathXmlApplicationContext(config);

        //从容器中获取目标对象

        SomeService proxy = (SomeService) ctx.getBean("someService");

        //通过代理的对象执行方法,实现目标方法执行时,增强了功能
        String  str = proxy.doOther("zx",20);

    }
}

image.png

4.3.2环绕通知

image.png

package com.bjpowernode.ba03;

public interface  SomeService {


    String doFirst(String name,Integer age);
}
package com.bjpowernode.ba03;


//目标类
public class SomeServiceImpl implements SomeService {


    @Override
    public String doFirst(String name, Integer age) {
        System.out.println("=====业务方法doFirst===");
        return "doFirst";
    }
}
package com.bjpowernode.ba03;


import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

@Aspect
public class MyAspect {
/**
 *   环绕通知方法的定义格式
 *   1.public
 *   2.必须有一个返回值,推荐使用object
 *   3.方法名称自定义
 *   4.方法有参数,固定的参数ProceedingJoinpoint
 *
 */

/**
 *  @Around:环绕通知
 *      属性: value   切入点表达式
 *      位置:在方法的定义什么
 * 特点:
 *   1.它是功能最强的通知
 *   2.在目标方法的前和后都能增强功能。
 *   3.控制目标方法是否被调用执行
 *   4.修改原来的目标方法的执行结果。影响最后的调用结果
 *
 *   环绕通知,等同于jdk动态代理的,InvocationHandLer接口
 *
 *   参数: ProceedingJoinPoint就等同于Method
 *      作用:执行目标方法的
 * 返回值:就是目标方法的执行结果,可以被修改。
 *
 * 环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法,在目标方法之后提交事务。
 */
@Around(value="execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
        String name="";
        //获取第一个参数值
    Object args[] =pjp.getArgs();

    if(args!=null && args.length >1){
            Object arg=args[0];
            name=(String)arg;
    }

        //实现环绕通知
        Object result = null;
        System.out.println("环绕通知:在目标方法之前,输出时间:"+new Date());

        //1.目标方法调用
    if("zhangsan".equals(name)){
        //符合条件,调用目标方法
        result = pjp.proceed();//method.invoke(); object result = doFirst();
    }

        System.out.println("环绕通知:在目标方法之后,提交事务");
        //2.在目标方法的前或者后加入功能
            //修改目标方法执行结果,影响方法最后的调用结果
    if(result!=null){
        result="Hello Aspectj AOP";
    }
            //返回目标方法执行结果
        return  result;

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


    <!-- 把对象交给spring容器,由spring容器统一创建,管理对象-->
    <!--声明目标对象-->
    <bean id="someService" class="com.bjpowernode.ba03.SomeServiceImpl"/>

    <!--声明切面类对象-->
    <bean id="myAspect" class="com.bjpowernode.ba03.MyAspect"/>

    <!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象
        创建代理对象是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象
        所以目标对象就是被修改后的代理对象

        aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象

        -->

    <aop:aspectj-autoproxy/>
</beans>
package com.bjpowernode;

import com.bjpowernode.ba03.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest03 {
    @Test
    public void test01(){

        String config="applicationContext.xml";

        ApplicationContext ctx=new ClassPathXmlApplicationContext(config);

        //从容器中获取目标对象

        SomeService proxy = (SomeService) ctx.getBean("someService");

        //通过代理的对象执行方法,实现目标方法执行时,增强了功能
        String  str = proxy.doFirst("zhangsan",20);//myAround()
        System.out.println("str======"+str);


    }
}

image.png

4.4Pointcut

@Pointcut:定义和管理切入点,如果你的项目中有多个切入点表达式是重复的,可以复用的。
可以使用@Pointcut
属性: value切入点表达式
位置:在自定义的方法上面
特点;
当使用@Paintcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名。
其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了

@Aspect
public class MyAspect {

    @After(value = "mypt()")
    public void myAfter(){
        System.out.println("执行最终结果,总是会被执行的代码");
        //一般用于资源清楚工作
    }
    @Before(value = "mypt()")
    public void myBefore(){
        System.out.println("前置通知结果,在目标方法之前先执行");

    @Pointcut(value = "execution(* *..SomeServiceImpl.dothird(..))")
            public void mypt(){
                //无需代码
        }

五、Spring集成Mybatis

概念:
用的技术是:ioc .
为什么ioc:能把mybatis和spring集成在一起,像一个框架,是因为ioc能创建对象。可以把mybatis框架中的对象交给spring统一创建,开发人员从spring中获取对象。开发人员就不用同时面对两个或多个框架了,就面对一个spring

image.png

5.1步骤

步骤:
1.新建maven项目
2.加入maven的依赖
    1 ) spring依赖
    2 ) mybatis依赖
    3 ) mysql驱动
    4 ) spring的事务的依赖
    5 ) mybatis和spring集成的依赖:mybatis官方体用的,用来在spring项目中创建mybatis
        的sqlsesissonFactory , dao对象的
3.创建实体类
4.创建dao接口和mapper文件
5.创建mybatis主配置文件
6.创建service接口和实现类,属性是dao。
7.创建spring的配置文件:声明mybatis的对象交给spring创建
        1)数据源
        2 ) sqlsesJionFactory
        3) Dao对象
        4)声明自定义的service

8.创建测试类,获取Service对象,通过service调用dao完成数据库的访问

2.加入maven的依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.bjpowernode</groupId>
  <artifactId>ch07-spring-mybatis</artifactId>
  <version>1.0-SNAPSHOT</version>



  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <!--单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!--spring核心ioc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
  </dependency>
    <!--做spring事务用到的-->
    <dependency>
    <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
  </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
  </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
  </dependency>
    <!--mybatis和spring集成的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
  </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.9</version>
  </dependency>
    <!--阿里公司数据连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
  </dependency>
  </dependencies>



  <build>
    <!--目的是把src/main/java目录中的xml文件包含到输出结果中。输出到classes目录中-->
    <resources>
      <resource>
        <directory>src/main/java</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>

      <!--指定JDK的版本-->
    </resources>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

3.创建实体类

package com.bjpowernode.domain;

public class Student {
    //属性名和列名一样。
    private Integer id;
    private String name;
    private String email;
    private Integer age;

//无参,有参构造。 set和get。  toString

4.创建dao接口和mapper文件

package com.bjpowernode.dao;

import com.bjpowernode.domain.Student;

import java.util.List;

public interface StudentDao {

    int insert(Student student);
    List<Student> selectStudents();
}
<?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="com.bjpowernode.dao.StudentDao">
    <insert id="insertStudent"  >
            insert into student values(#{id},#{name},#{email},#{age})
    </insert>

    <select id="selectStudents" resultType="com.bjpowernode.domain.Student">
        select id,name,email,age from student order by id desc
     </select>
</mapper>

5.创建mybatis主配置文件

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

    <!--settings:控制mybatis全局行为-->
    <settings>
            <!--设置mybatis输出日志-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--设置别名-->
    <typeAliases>
            <!--name:实体类所在的包名-->
                <package name="com.bjpowernode.domain"/>
    </typeAliases>


    <!--sql  mapper(sql映射)位置-->
    <mappers>
        <!--name :是包名,这个包中的所有mapper.xml一次都能加载-->
        <package name="com.bjpowernode.dao"/>
    </mappers>
</configuration>

6.创建service接口和实现类,属性是dao。

package com.bjpowernode.service;

import com.bjpowernode.domain.Student;

import java.util.List;

public interface StudentService {
    int addStudent(Student student);
    List<Student> queryStudents();
}
package com.bjpowernode.service.impl;

import com.bjpowernode.dao.StudentDao;
import com.bjpowernode.domain.Student;
import com.bjpowernode.service.StudentService;

import java.util.List;

public class StudentServiceImpl implements StudentService {

    //引用类型
    private StudentDao studentDao;
    //使用set注入,赋值

    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }


    @Override
    public int addStudent(Student student) {
        int nums=studentDao.insertStudent(student);

        return nums;
    }

    @Override
    public List<Student> queryStudents() {
        List<Student> students = studentDao.selectStudents();
        return students;
    }
}

7.创建spring的配置文件:声明mybatis的对象交给spring创建

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


    <!--声明数据源DataSource,作用是连接数据库的-->

    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
            init-method="init" destroy-method="close">

    <!--set注入给DruidDataSource提供连接数据库信息-->
        <property name="url" value="jdbc:mysql://localhost:3306/springdb"/> <!--setUrl()-->
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    <property name="maxActive" value="20"/>

    </bean>

    <!--声明的是mybatis中提供的sqlsessionFactoryBean类,这个类内部创建sqlsessionFactory的-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="myDataSource"/>
        <!--mybatis主配置文件的位置
            configLocation属性是Resource类型,读取配置文件
            他的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
         -->
        <property name="configLocation" value="classpath:mybatis.xml"/>

    </bean>

    <!--创建dao对象,使用SqlSession的getMapper ( StudentDao.class)
         MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!--指定SqlSessionFacory对象的id-->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!--
        指定包名,包名是dao接口所在的包名。
        MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
        一 次getMapper()方法,得到每个接口的dao对象。
        创建好的dao对象放入到spring的容器中的。dao对象的默认名称是 接口名首字母小写
        -->

        <property name="basePackage" value="com.bjpowernode.dao"/>
    </bean>

    <!--声明service-->
    <bean id="studentService" class="com.bjpowernode.service.impl.StudentServiceImpl">

        <property name="studentDao" ref="studentDao"/>
    </bean>
</beans>

8.创建测试类,获取Service对象,通过service调用dao完成数据库的访问

package com.bjpowernode;

import com.bjpowernode.domain.Student;
import com.bjpowernode.service.StudentService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {


        @Test
        public void test01(){
            String config="applicationContext.xml";

            ApplicationContext ctx=new ClassPathXmlApplicationContext(config);

          //获取spring容器中的dao对象

            StudentService dao =(StudentService) ctx.getBean("studentService");
            Student student = new Student();
            student.setId(1015);
            student.setName("周峰");
            student.setEmail("zhoufeng@qq.com");
            student.setAge(26);
            int nums = dao.addStudent(student);
            //spring和mybatis整合在一起使用,事务是自动提交的。无需执行Sqlsession.commit();
            System.out.println("nums="+nums);

    }
}

六、事务

image.png

6.1 理论知识

5.1.1 事务管理器接口
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
PlatformTransactionManager 接口有两个常用的实现类:
➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。

5.1.2 Spring 的回滚方式
Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时 提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。

运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如, NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于运 行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代码 编写足够仔细,程序足够健壮,运行时异常是可以避免的。

受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。如 SQLException,ClassNotFoundException,IOException 等都属于受查异常。 RuntimeException 及其子类以外的异常,均属于受查异常。

5.1.3 事务定义接口
事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作。

定义了五个事务隔离级别常量
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。 ➢ DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默 认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读 。
➢ SERIALIZABLE:串行化。不存在并发问题。

定义了七个事务传播行为常量
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情 况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的 维护情况,就称为事务传播行为。事务传播行为是加在方法上的。 事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。

PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED

1) PROPAGATION_REQUIRED:
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事 务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。 如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。

2) PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。

3)PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

6.2 使用 Spring 的事务注解管理事务

1.开启注解驱动

 <tx:annotation-driven transaction-manager="transactionManager" />

2.声明事务管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="myDataSource" />
    </bean>

可视为固定写法,其中property标签的ref是配置文件中数据源对象的id属性值。
3.业务层 public 方法加入事务属性

//    @Transactional(propagation = Propagation.REQUIRED,
//            isolation = Isolation.DEFAULT, timeout = 20,
//            rollbackFor = {NullPointerException.class,NotEnoughException.class})
    @Transactional

@Transactional 的所有可选属性如下所示:
➢ propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED。
➢ isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。
➢ readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。
➢ timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。在实际业务开发中一般不设置。
➢ rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有 一个异常类时,可以不使用数组。
➢ rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。
➢ noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。
➢ noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空 数组。当然,若只有一个异常类时,可以不使用数组。

需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该 方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。 若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

6.3 使用 AspectJ 的 AOP 配置管理事务

一般大型项目使用。在不更改源代码的条件下管理事务。
1.添加Maven依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>    
    <version>4.3.16.RELEASE</version>
</dependency>   
<dependency> 
   <groupId>org.springframework</groupId>
   <artifactId>spring-aspects</artifactId>
    <version>4.3.16.RELEASE</version>   
 </dependency>

2.添加事务管理器
与Spring的注解方式配置内容相同。
3.配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。

<!--声明事务的通知
        指定业务方法的事务属性(传播行为,隔离级别,超时,回滚等)
    -->
    <tx:advice id="buyAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--给指定的业务方法,设置事务属性
                name:业务方法的名称,可以使用通配符(*:任意字符)
            -->
            <tx:method name="buyGoods" propagation="REQUIRED" isolation="DEFAULT"
                       rollback-for="java.lang.NullPointerException,com.bjpowernode.excep.NotEnoughException"/>
            <!--设置addXXX方法的事务-->
            <tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
            <!--设置updateXXX方法的事务-->
            <tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
            <!--设置removeXXX方法的事务-->
            <tx:method name="remove*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>

            <!--其他方法的事务-->
            <tx:method name="*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>

    </tx:advice>

4.配置增强器
指定将配置好的事务通知,织入给谁。 在第三步中我们指定了事务管理的方法。现在来指定这些方法所在的类

<aop:config>
        <!--声明切入点表达式:指定一些类和方法要加入切面的功能-->
        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))" />

        <!--声明增强器对象(通知+切入点)
            advice-ref:事务通知对象的id
            pointcut-ref:切入点表达式
        -->
        <aop:advisor advice-ref="buyAdvice" pointcut-ref="servicePt"/>

    </aop:config>

七、Spring与Web

这一章主要介绍了一个核心知识点:解决不同Servlet中重复创建ApplicationContext对象,造成内存浪费的问题。

解决这个问题的一个思路是,创建一个ServletContextListener,在ServletContext初始化的时候创建ApplicationContext对象,并将它保存在ServletContext中。
这样,在每个servlet中,只要调用当前servlet的ServletContext对象getAttribute方法就可以获取这个webapp中共享的一个ApplicationContext对象。

spring-web框架已经帮我们创建好了这样一个监听器。我们只需要在web.xml注册这个监听器就可以使用了。

1.添加Maven依赖

<!--spring-web依赖:有监听器-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>4.3.16.RELEASE</version>
</dependency>

2.注册监听器在注册监听器时需要为监听器提供Spring的配置文件路径信息。

<!--注册spring框架提供的监听器
    在监听器启动的时候,会寻找/WEB-INF/applicatoinContext.xml,为什么找这个文件?
    在监听器的初始方法中,会创建spring的容器对象, 在创建容器对象时,需要读取配置文件
    监听器默认是找/WEB-INF/applicatoinContext.xml。
-->
<!--自定义spring配置文件的位置和名称-->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

4.在Sevlet中获取ApplicationContext对象

WebApplicationContext ctx = null;
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
Object attr = getServletContext().getAttribute(key);
if( attr != null){
    ctx = (WebApplicationContext)attr;
}

webApplicationContext是ApplicationContext的子类,是在web项目中使用的Spring容器对象。

为了不使用框架给出的难记的key值获取webApplicationContext,这个框架还提供了一个工具类。使用工具类获取webApplicationContext:

//获取ServletContext中的容器对象,spring提供了一个方法,获取容器对象
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(
getServletContext());