Spring

参考:https://blog.csdn.net/weixin_45159265/article/details/106491435

一、Spring概述

1. 概述

它是一个容器.它是整合其它框架的框架.它的核心是IOC和AOP.它由20多个模块构成.它在很多领域都提供优秀的解决方案.
核心技术IoC、AOP,能使模块之间、类之间解耦合。
依赖:class A使用class B的属性或方法,称之为class A依赖class B。

2.Spring的特点

(1)轻量:Spring的所需要的jar包都非常小,一般1M以下,几百kb。核心功能所需要的jar包总共3M左右。
Spring框架运行占有资源少,运行效率高,不依赖其他jar。
(2) 针对接口编程,解耦合
(3) AOP 编程的支持
(4) 方便集成各种优秀框架

3.Spring 体系结构

请添加图片描述
Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、 Web、面向切面编程(AOP, Aspects)、提供JVM的代理 (Instrumentation)、消息发送(Messaging)、 核心容器(Core Container)和测试(Test)

二、IoC控制反转

2.1第一个Spring程序

IoC (Inversion of Control) : 控制反转, 是一个理论,概念,思想。
描述的:把对象的创建,赋值,管理工作都交给代码之外的容器实现, 也就是对象的创建是有其它外部资源完成。

控制: 创建对象,对象的属性赋值,对象之间的关系管理。
反转: 把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。 由容器代替开发人员管理对象。创建对象,
        给属性赋值。

正转:由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。
       public static void main(String args[]){
           Student student = new Student(); // 在代码中, 创建对象。--正转。
	}

容器:是一个服务器软件, 一个框架(spring)

为什么要使用 ioc : 目的就是减少对代码的改动, 也能实现不同的功能。 实现解耦合

java中创建对象有哪些方式:

  1. 构造方法 , new Student()
  2. 反射
  3. 序列化
  4. 克隆
  5. ioc :容器创建对象
  6. 动态代理
ioc的体现: 
   servlet  1: 创建类继承HttpServelt 
	        2:  在web.xml 注册servlet , 使用<servlet-name> myservlet </servlet-name>
				                            <servelt-class>com.bjpwernode.controller.MyServlet1</servlet-class>

            3. 没有创建 Servlet对象, 没有 MyServlet myservlet = new MyServlet()

			4. Servlet 是Tomcat服务器它能你创建的。 Tomcat也称为容器
				   Tomcat作为容器:里面存放的有Servlet对象, Listener , Filter对象


IoC的技术实现 , 
  DI 是ioc的技术实现,  
  DI(Dependency Injection) :依赖注入, 只需要在程序中提供要使用的对象名称就可以, 至于对象如何在容器中创建,
                              赋值,查找都由容器内部实现。

spring是使用的di实现了ioc的功能, spring底层创建对象,使用的是反射机制。

spring是一个容器,管理对象,给属性赋值, 底层是反射创建对象。

在这里插入图片描述

3)定义实体类

//接口的实现类
public class SomeServiceImpl implements SomeService {
    @Override
    public void dosome() {
        System.out.println("执行了SomeServiceImpl的dosome()方法");
    }
}
//接口
public interface SomeService {
    void dosome();
}

4)创建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创建对象
        声明bean ,就是告诉spring要创建某个类的对象
        id:对象的自定义名称,唯一值。spring通过这个名称找到对象
        class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类)

        spring就完成 Someservice someservice = new SomeserviceImpl()
        spring是把创建好的对象放入到map中, spring框架有一个map存放对象的。
        springMap.put(id的值,对象);
        例如springMap.put( "someservice" , new someserviceImpl());
        一个bean标签声明一个对象。
     -->

    <bean id="someService" class="com.smb.service.impl.SomeServiceImpl">
    </bean>
</beans>

<!--
    spring的配置文件
    1.beans :是根标签,spring把java对象成为bean。
    2.spring-beans.xsd 是约束文件,和mybatis指定dtd是一样的。
-->

5)创建测试类

public class MyTest {
    @Test
    public void test01(){
        //正射 直接new对象
        SomeService service = new SomeServiceImpl();
        service.dosome();
    }

    @Test
    public void test02(){
        //使用spring容器创建的对象
        // 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();
    }
}

2.2基于XML的DI

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

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

di的语法分类:

1.set注入(设置注入) : spring调用类的set方法,在set方法可以实现属性的赋值。80左右都是使用的set注入

 注入:就是赋值的意思
  简单类型: spring中规定java的基本数据类型和string都是简单类型。
  di:给属性赋值
  1. set注入(设置注入) : spring调用类的set方法,你可以在set方法中完成属性赋值
    1)简单类型的set注入
      <bean id="xx" class="yyy">
      <property name="属性名字" value="此属性的值"/>一个property只能给一个属性赋值
      <property...>
      </ bean>
    2)引用类型的set注入 : spring调用类的set方法
      <bean id="xxx" class="yyy">
         <property name="属性名称" ref="bean的id(对象的名称)"/>
      </bean>




 <bean id="mySchool" class="com.smb.ba02.School">
        <property name="name" value="ZJUT"></property>
        <property name="address" value="hangzhou"></property>
    </bean>

    <bean id="myStudent" class="com.smb.ba02.Student">
        <property name="age" value="18"></property>
        <property name="name" value="zhangsan"></property>

        <!-- 引用类型-->
        <property name="school" ref="mySchool"></property>
    </bean>

2.构造注入:spring调用类的有参数构造方法,创建对象。在构造方法中完成赋值。|

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


    -->


    <!--使用name属性实现构造注入-->
    <bean id="myStudent" class="com.smb.ba03.Student">
        <constructor-arg name="name" value="zhangsan"></constructor-arg>
        <constructor-arg name="age" value="21"></constructor-arg>
        <constructor-arg name="school" ref="myxuexiao"></constructor-arg>
    </bean>

    <!--使用index属性实现构造注入-->
    <bean id="myStudent2" class="com.smb.ba03.Student">
        <constructor-arg index="0" value="lisi"></constructor-arg>
        <constructor-arg index="1" value="22"></constructor-arg>
        <constructor-arg index="2" ref="myxuexiao"></constructor-arg>
    </bean>

    <!--省略index属性-->
    <bean id="myStudent3" class="com.smb.ba03.Student">
        <constructor-arg  value="lisi"></constructor-arg>
        <constructor-arg  value="22"></constructor-arg>
        <constructor-arg  ref="myxuexiao"></constructor-arg>
    </bean>


    <bean id="myxuexiao" class="com.smb.ba03.School">
        <property name="name" value="ZJUT"></property>
        <property name="address" value="hangzhou"></property>
    </bean>

3.引用类型的自动注入
对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为标签 设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属 性)。根据自动注入判断标准的不同,可以分为两种:

  • byName:根据名称自动注入
  • byType: 根据类型自动注入
<!--声明student对象
3.引用类型的自动注入: spring框架根据某些规则可以给引用类型赋值。
                    不用你在给引用类型赋值了使用的规则常用的是byName,byType.
    1.byName(按名称注入) : java类中引用类型的属性名和spring容器中(配置文件)<bean>的id名称一样,
                         且数据类型是一致的,这样的容器中的bean , spring能够赋值给引用类型
    语法:
        <bean id="xx" class="yyy" autowire="byName">
            简单类型属性赋值
        </bean
     2.byType(按类型注入) : java类中引用类型的数据类型和spring容器中(配置文件)<bean>class属性
                            是同源关系的,这样的bean能够赋值给引用类型
        同源就是一类的意思:
        1.java类中引用类型的数据类型和bean的class的值是一样的。
        2.java类中引用类型的数据类型和bean的class的值父子类关系的。
        3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的语法:
            <bean id="xx" class="yyy" autowire="byType">
                简单类型属性赋值
            </bean>

    注意:在byType中,在xml配置文件中声明bean只能有一个符合条件的,多余一个是错误的
	byType:根据属性的类型自动装配。这个理解起来有点费劲,不仔细研究的话,根本理解不了。
	这里的属性是什么?是我们需要注入的bean,也就是我们在一个Service实现类中定义的Dao接口对象。
	这个说的够具体了吧,我们还会给这个Dao接口提供setter方法以便注入。

	属性类型是什么呢?就是Dao的具体实现类,比如XxxDaoImpl.java。
	**加入我们配置了2XxxDaoImpl的bean,一个id是xxxDao1,一个id是xxxDao2,那么如果我们使用byType注入,就会报错。**
	因为Spring根据类型去查询,找到了2个。


    -->

    <bean id="myStudent" class="com.smb.ba05.Student" autowire="byType">
        <property name="age" value="18"></property>
        <property name="name" value="zhangsan"></property>

        <!-- 引用类型-->
        <!--<property name="school" ref="mySchool"></property>-->
    </bean>

     <!-- 声明school对象-->
    <!-- <bean id="mySchool" class="com.smb.ba05.School">
         <property name="name" value="ZJUT1"></property>
         <property name="address" value="hangzhou"></property>
     </bean>-->

    <!-- 声明school对象的子类-->
    <bean id="primarySchool" class="com.smb.ba05.PrimarySchool">
        <property name="name" value="xiaoxue"></property>
        <property name="address" value="hangzhou"></property>
    </bean>
  1. 多个配置优势
    1.每个文件的大小比一个文件要小很多。效率高
    2.避免多人竞争带来的冲突。

如果你的项目有多个模块(相关的功能在一起) ,一个模块一个配置文件。
学生考勤模块一个配置文件, 张三
学生成绩一个配置文件, 李四

多文件的分配方式:

  1. 按功能模块,一个模块一个配置文件
  2. 按类的功能,数据库相关的配置一个文件配置文件, 做事务的功能一个配置文件, 做service功能的一个配置文件等

2.3 基于注解的DI

基于注解的di: 通过注解完成java对象创建,属性赋值。
使用注解的步骤:
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

用户处理请求:
用户form ,参数name ,age-----Servlet(接收请求name,age)—Service类(处理name,age操作)—dao类(访问数据库的)—mysql

定义 Bean 的注解@Component(掌握)

简单类型属性注入@Value(掌握)

在类中添加注解

/**
 * @component:创建对象的,等同于<bean>的功能
        属性: 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是给项目的对象分层的。


 */


//使用value属性,指定对象名称
//@Component(value = "myStudent")

//省略value
@Component("myStudent")

//不指定对象名称,由spring提供默认名称:类名首字母小写
//@Component
public class Student {
    private String name;
    private Integer age;

    public Student() {
        System.out.println("无参构造方法调用!!!");
    }

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

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

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

声明组件扫描器。在Spring配置文件的标签下

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

    指定多个包的三种方式
    
    第一种方式:使用多次组件扫描器,指定不同的包
    <context:component-scan base-package="com.smb.ba01"></context:component-scan>
    <context:component-scan base-package="com.smb.ba02"></context:component-scan>

    第二种方式:使用分隔符(;或,)分隔多个包名
    <context:component-scan base-package="com.smb.ba02;com.smb.ba01"></context:component-scan>

    第三种方式:指定父包
    <context:component-scan base-package="com.smb"></context:component-scan>

简单类型属性注入@Value(掌握)

package com.bjpowernode.ba02;

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

@Component("myStudent")
public class Student {

    /**
     * @Value: 简单类型的属性赋值
     *   属性: value 是String类型的, 表示简单类型的属性值
     *   位置:
     *       1.在属性定义的上面, 无需set方法,常用的方式
     *       2.在set方法上面
     */
    @Value("张三01")
    private String name;


    private int age;

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

    @Value("28")
    public void setAge(int age) {
        System.out.println("setAge:"+age);
        this.age = age;
    }

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


byType 自动注入@Autowired(掌握)

byName 自动注入@Autowired 与@Qualifier(掌握)

package com.smb.ba05;

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;

//省略value
@Component("myStudent")
public class Student {
    @Value("张三")
    private String name;

    private Integer age;

    /**
     *引用类型
     * @Autowired: spring框架提供的注解,实现引用类型的赋值。
     * spring中通过注解给引用类型赋值,使用的是自动注入原理,支持byName,byType
     * @Autowired:默认使用的是byType自动注入。
     *
     *  属性: required ,是一个boolean类型的,默认true
     *        required=true :表示引用类型赋值失败,程序报错,并终止执行。
     *        required=false :引用类型如果赋值失败,程序正常执行,引用类型是null
     *
     * 位置:1)在属性定义的上面,无需set方法,推荐使用
     *      2)在set方法的上面
     *
     *
     *如果要使用byName方式,需要做的是:
     * 1.在属性上面加入@Autowired
     * 2.在属性上面加入@Qualifier(value="bean的id"):表示使用指定名称的bean完成赋值
     *
     */
    @Autowired(required = false)
    @Qualifier("myschool")
    private School school;

    public Student() {
        System.out.println("无参构造方法调用!!!");
    }

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

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

    @Value("19")
    public void setAge(Integer age) {
        this.age = age;
    }
}

JDK 注解@Resource 自动注入(掌握)

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

    //默认是byName :先使用byName自动注入,如果byName赋值失败,再使用byType
    @Resource(name = "myschool")
    private School school;

2.4 注解与 XML 的对比

注解优点是:

方便
直观
高效(代码少,没有配置文件的书写那么复杂)。
其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。

XML 方式优点是:
配置和代码是分离的,在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。
xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。

个人发现:xml与注解方式还有一个区别在于,xml方式可以通过bean标签创建一个类的多个对象,但注解方式并不能,因为一个类不能存在两个同名的注解。

三、 AOP面向切面编程

3.1 动态代理

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

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

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

3.2 AOP概述

AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。 AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理,AOP可以看作动态代理的规范化与标准化

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

oop: 面向对象编程

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

3.3 AOP的相关术语

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

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

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

3.4 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个注解。

3.5 AspectJ 对 AOP 的实现

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

   2)表示切面执行的位置,使用的是切入点表达式。

		com.service.impl
		com.bjpowrnode.service.impl
		cn.crm.bjpowernode.service


      execution(* *..service.*.*(..))
      
execution ( [modifiers-pattern]  访问权限类型
	 ret-type-pattern 返回值类型
	  [declaring-type-pattern]  全限定性类名 
	  name-pattern(param-pattern) 方法名(参数类型和参数个数) 
	 [throws-pattern]  抛出异常类型 ) 

AspectJ 基于注解的 AOP 实现步骤

1.定义业务接口与实现类

package com.bjpowernode.service;

public interface SomeService {
	void doSome(String name, int age);
}
package com.bjpowernode.service;

public class SomeServiceImpl implements SomeService {

	@Override
	public void doSome(String name,int age) {
    	System.out.println("SomeSeviceImpl的业务方法doSome");
	}
}

2.定义切面类

/**
 * @Aspect:是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    /**
     * 定义方法,方法是实现切面功能的。方法的定义要求;
     *  1.公共方法 public
     *  2.方法没有返回值
     *  3.方法名称自定义
     *  4.方法可以有参数,也可以没有参数。
     *      如果有参数,参数不是自定义的,有几个参数类型可以使用。
     */

    /**
     * @Before:前置通知注解
     *  属性: value,是切入点表达式,表示切面的功能执行的位置。
     *  位置:在方法的上面
     *  特点:
     *      1.在目标方法之前先执行的
     *      2.不会改变目标方法的执行结果
     *      3.不会影响目标方法的执行。
     */
    /*@Before(value = "execution(public void com.smb.ba01.SomeServiceImpl.doSome(String, Integer))")
    public void myBefore(){
        System.out.println("切面功能:在目标方法之前输出执行时间:"+new Date());
    }*/

    @Before(value = "execution(* *..SomeServiceImpl.do*(..))")
    public void myBefore(){
        System.out.println("切面功能:在目标方法之前输出执行时间:"+new Date());
    }
}

3.声明目标对象与切面类对象,注册AspectJ的自动代理

<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.smb.ba01.SomeServiceImpl"></bean>

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

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

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

aop:aspectj-autoproxy/通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。

@Before前置通知-方法有JoinPoint参数

在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参 数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、 目标对象等。

    /**
     * @Before:前置通知注解
     *  属性: value,是切入点表达式,表示切面的功能执行的位置。
     *  位置:在方法的上面
     *  特点:
     *      1.在目标方法之前先执行的
     *      2.不会改变目标方法的执行结果
     *      3.不会影响目标方法的执行。
     */
     
    /**
     * 指定通知方法中的参数: JoinPoint
     * JoinPoint:业务方法,要加入切面功能的业务方法
     *      作用是:可以在通知方法中获取方法执行时的信息,例如方法名称,方法的实参。
     *      如果你的切面功能中需要用到方法的信息,就加入JoinPoint.
     *      这个JoinPoint参数的值是由框架赋予,必须是第一个位置的参数
     */
    @Before(value = "execution(* *..SomeServiceImpl.do*(..))")
    public void myBefore(JoinPoint jp){
        //获取方法的完整定义
        System.out.println("方法的签名(定义)="+jp.getSignature());
        System.out.println("方法的名称="+jp.getSignature().getName());

        //获取方法的实参
        Object[] args = jp.getArgs();
        for (Object arg : args) {
            System.out.println("实参="+arg);
        }

        System.out.println("切面功能:在目标方法之前输出执行时间:"+new Date());
    }

@AfterReturning 后置通知-注解有 returning 属性

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

    /**
     * @AfterReturning:后置通知
     * 属性:
     *  1.value切入点表达式
     *  2.returning自定义的变量,表示目标方法的返回值的。
     *  自定义变量名必须和通知方法的形参名一样。
     * 位置:在方法定义的上面
     * 特点:
     *  1.在目标方法之后执行的。
     *  2.能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
     *  3.可以修改这个返回值
     */

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

        //修改目标方法的返回值,看一下是否会影响最后的方法调用结果
        if (res != null) {
            res="hello Aspectj";
        }
    }
}

@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数

在目标方法执行之前之后执行。
被注解为环绕增强的方法要有返回,Object 类型。并 且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口ProceedingJoinPoint 继承于JoinPoint,因此可以根据它获取方法的信息。其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

@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=null;
        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();
        }
        System.out.println("环绕通知:在目标方法之后,提交事务");
        //2.在目标方法的前或者后加入功能

        //修改目标方法的执行结果,影响方法最后的调用结果
        if (result != null) {
            result="Hello Aspectj AOP";
        }
        //返回目标方法的执行结果
        return result;
    }
}

@AfterThrowing 异常通知-注解中有 throwing 属性

在效果上相当于一个try…catch语句。目标方法的方法体在try语句块中,而切面方法的方法体放在了catch子句中。

@Aspect
public class MyAspect {
    /**
     * 异常通知方法的定义格式
     * 1.public
     * 2.没有返回值
     * 3.方法名称自定义
     * 4.方法有个一个Exception ,如果还有是JoinPoint
     */

    /**
     *@AfterThrowing:异常通知
     *  属性:1. value切入点表达式
     *       2. throwinng自定义的变量,表示目标方法抛出的异常对象。
     *           变量名必须和方法的参数名一样
     * 特点:
     *      1,在目标方法抛出异常时执行的
     *      2.可以做异常的监控程序,监控目标方法执行时是不是有异常。
     *          如果有异常,可以发送邮件,短信进行通知
     *
     *      执行就是:try{
     *                  someserviceImpl.dosecond(..)
     *              }catch(Exception e){
     *                  myAfterThrowing(e);
     *              }
     */
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
    public void myAfterThrowing(Exception ex){
        System.out.println("异常通知:方法发生异常,执行:"+ex.getMessage());
    }
}

@After最终通知

无论目标方法是否抛出异常,该增强均会被执行。
在执行效果上,相当于将切面方法的方法体放在了try…catch…finally…语句发finally子句中。

@Aspect
public class MyAspect {
    /**
     * 最终通知方法的定义格式
     * 1.public
     * 2.没有返回值
     * 3.方法名称自定义
     * 4.方法没有参数 ,如果有是JoinPoint
     */

    /**
     *@After:最终通知
     *  属性:value切入点表达式
     *  位置:在方法的上面
     * 特点:
     *   1.总是会执行
     *   2.在目标方法之后执行的
     *
     *  try{
     *      someserviceImpL.doThird( ..)
     *      }catch(Exception e){
     *
     *      }finally{
     *       myAfter()
     *      }
     */
     
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
        //一般做资源清除工作的
    }

@Pointcut 定义切入点

@Aspect
public class MyAspect {

    @After(value = "mypt()")
    public void myAfter(){
        System.out.println("执行最终通知,总是会被执行的代码");
        //一般做资源清除工作的。
    }

    @Before(value = "mypt()")
    public void myBefore(){
        System.out.println("执行前置通知,在目标方法之前执行的");
    }

    /**
     * @Pointcut: 定义和管理切入点,如果你的项目中有多个切人点表达式是重复的,可以复用的。
     *          可以使用@Pointcut
     *      属性: value切入点表达式
     *      位置:在自定义的方法上面
     *  特点:
     *    当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名。
     *    其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了
     */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
    private void mypt(){
        //无需代码 mypt()就是execution表达式的别名
    }
}

设置AspectJ 实现 AOP的方式

在Spring配置文件中,通过aop:aspectj-autoproxy/的proxy-target-class属性设置选择通过JDK动态代理还是cglib动态代理实现AOP。

<!--声明自动代理生成器:使用aspectj把spring容器中目标类对象生成代理
        proxy-target-class="true"表示使用cglib动态代理

        目标类有接口,默认使用jdk动态代理。
        目标类没有接口,默认时候cglib动态代理
        目标类有接口,也可以使用cglib动态代理,需要设置proxy-target-class="true"
    -->
    <!-- <aop:aspectj-autoproxy proxy-target-class="true" />-->
    <aop:aspectj-autoproxy/>

四、 Spring集成myBatis

把mybatis框架和spring集成在一起,向一个框架一样使用。

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


mybatis使用步骤,对象
1.定义dao接口 ,StudentDao
2.定义mapper文件 StudentDao.xml
3.定义mybatis的主配置文件 mybatis.xml
4.创建dao的代理对象, StudentDao dao = SqlSession.getMapper(StudentDao.class);

   List<Student> students  = dao.selectStudents();


要使用dao对象,需要使用getMapper()方法,
怎么能使用getMapper()方法,需要哪些条件
1.获取SqlSession对象, 需要使用SqlSessionFactory的openSession()方法。
2.创建SqlSessionFactory对象。 通过读取mybatis的主配置文件,能创建SqlSessionFactory对象

需要SqlSessionFactory对象, 使用Factory能获取SqlSession ,有了SqlSession就能有dao , 目的就是获取dao对象
Factory创建需要读取主配置文件

我们会使用独立的连接池类替换mybatis默认自己带的, 把连接池类也交给spring创建。


主配置文件:
 1.数据库信息
 <environment id="mydev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--数据库的驱动类名-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--连接数据库的url字符串-->
                <property name="url" value="jdbc:mysql://localhost:3306/springdb"/>
                <!--访问数据库的用户名-->
                <property name="username" value="root"/>
                <!--密码-->
                <property name="password" value="123456"/>
            </dataSource>
2. mapper文件的位置
   <mappers>
        <mapper resource="com/bjpowernode/dao/StudentDao.xml"/>
        <!--<mapper resource="com/bjpowernode/dao/SchoolDao.xml" />-->
    </mappers>


==============================================================
通过以上的说明,我们需要让spring创建以下对象
1.独立的连接池类的对象, 使用阿里的druid连接池
2.SqlSessionFactory对象
3.创建出dao对象

需要学习就是上面三个对象的创建语法,使用xml的bean标签。

在这里插入图片描述

1.加入依赖

1)spring依赖
2)mybatis的依赖
3)mybatis-spring依赖, 这个jar是从mybatis官网下载的, mybatis提供在spring中创建对象的类。
4)mysql的驱动
5)druid,数据库连接池的依赖

<dependencies>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.11</version>
  <scope>test</scope>
</dependency>
<!--spring-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.16.RELEASE</version>
</dependency>
<!--spring的事务-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>4.3.16.RELEASE</version>
</dependency>
<!--spring访问数据库-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>4.3.16.RELEASE</version>
</dependency>
<!--mybatis的依赖-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.4.5</version>
</dependency>
<!--mybatis整合spring的依赖:创建mybatis对象-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>1.3.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.16</version>
</dependency>
<!--数据库连接池-->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.12</version>
</dependency>

2.新建实体类User

package com.smb.domain;

public class User {
    private Integer id;
    private String name;
    private String password;
    private String email;

    public User() {
    }

    public User(Integer id, String name, String password, String email) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

3.新建Dao接口和sql映射文件

UserDao

package com.smb.dao;

import com.smb.domain.User;

import java.util.List;

public interface UserDao {
    int insertUser(User user);
    List<User> selectUser();
}

UserDao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smb.dao.UserDao">
    <insert id="insertUser">
        insert into users(id,name,password,email) values(#{id},#{name},#{password},#{email})
    </insert>
    <select id="selectUser" resultType="com.smb.domain.User">
        select id,name,password,email from users order by id desc
    </select>
</mapper>

4.新建mybatis主配置文件

mybatis.xml
由于使用阿里的数据库连接池,所以不需要 <environments/>标签:

<?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.smb.domain"/>
    </typeAliases>

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

5.新建Service接口和实现类, 在实现类中有Dao的属性

在实际项目中,我们在对数据库前需要一些其他的业务代码,例如逻辑判断、身份认证等,这些放在Service中:

package com.smb.service;
import com.smb.domain.User;
import java.util.List;

public interface UserService {
    int addUser(User user);
    List<User> queryUsers();
}
package com.smb.service.impl;
import com.smb.dao.UserDao;
import com.smb.domain.User;
import com.smb.service.UserService;
import java.util.ArrayList;
import java.util.List;

public class UserServiceImpl implements UserService {
    //引用类型
    private UserDao userDao;

    //使用set注入,赋值
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public int addUser(User user) {
        int nums = userDao.insertUser(user);
        return nums;
    }

    @Override
    public List<User> queryUsers() {
        List<User> users=userDao.selectUser();
        return users;
    }
}

6.新建spring的配置文件(重要)

1)声明数据源DataSource对象
2)声明SqlSessionFactoryBean,创建SqlSessionFactory对象
3)声明MyBatis的扫描器,创建Dao接口的实现类对象
4)声明自定义的Service ,把3)中的Dao对象注入赋值给Service的属性

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

    <!--
        把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
        spring知道jdbc.properties文件的位置
        使用属性配置文件中的数据,语法${key}
    -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--声明数据源DataSource,作用是连接数据库的-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--set注入给DruidDatasource提供连接数据库信息-->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="${jdbc.max}"/>
    </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 ) -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定sqlsessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

        <!--指定包名,包名是dao接口所在的包名。
            Mapperscannerconfigurer会扫描这个包中的所有接口,
            把每个接口都执行一次getMapper()方法,得到每个接口的dao对象。
            创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写 userDao
        -->
        <property name="basePackage" value="com.smb.dao"/>
    </bean>

    <!--声明service-->
    <bean id="userService" class="com.smb.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

7.新建测试类, 从spring容器中获取Service,调用Service的方法,完成数据库的操作

package com.smb;

import com.smb.dao.UserDao;
import com.smb.domain.User;
import com.smb.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class MyTest {
    @Test
    public void test01(){
        String config="applicationContext.xml";
        ApplicationContext ctx= new ClassPathXmlApplicationContext(config);
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println("容器中对象名称:"+name+ctx.getBean(name));
        }
    }

    @Test
    public void test02(){
        String config="applicationContext.xml";
        ApplicationContext ctx= new ClassPathXmlApplicationContext(config);
        UserDao dao = (UserDao) ctx.getBean("userDao");
        User user=new User();
        user.setId(10);
        user.setName("smb");
        user.setPassword("123456");
        user.setEmail("asdasd@123");
        int num = dao.insertUser(user);
        System.out.println("num="+num);
    }

    @Test
    public void testServiceInsert(){
        String config="applicationContext.xml";
        ApplicationContext ctx= new ClassPathXmlApplicationContext(config);
        UserService service = (UserService) ctx.getBean("userService");
        User user=new User();
        user.setId(11);
        user.setName("smb1");
        user.setPassword("123456");
        user.setEmail("asdasd@123");
        int num = service.addUser(user);
        //spring和mybatis整合在一起使用,事务是自动提交的。无需执行SqlSession.commit();
        System.out.println("num="+num);
    }

    @Test
    public void testServiceSelect(){
        String config="applicationContext.xml";
        ApplicationContext ctx= new ClassPathXmlApplicationContext(config);
        UserService service = (UserService) ctx.getBean("userService");
        List<User> userList = service.queryUsers();
        userList.forEach(user -> System.out.println(user));
    }
}

五、Spring与事务

5.1 理论知识

spring的事务处理
回答问题
1.什么是事务
  讲mysql的时候,提出了事务。 事务是指一组sql语句的集合, 集合中有多条sql语句
  可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,
  或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。


2.在什么时候想到使用事务
  当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。需要保证
  这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的。


  在java代码中写程序,控制事务,此时事务应该放在那里呢? 
     service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句


3.通常使用JDBC访问数据库, 还是mybatis访问数据库怎么处理事务
   jdbc访问数据库,处理事务  Connection conn ; conn.commit(); conn.rollback();
	mybatis访问数据库,处理事务, SqlSession.commit();  SqlSession.rollback();
	hibernate访问数据库,处理事务, Session.commit(); Session.rollback();


4.3问题中事务的处理方式,有什么不足
  1)不同的数据库访问技术,处理事务的对象,方法不同,
    需要了解不同数据库访问技术使用事务的原理
  2)掌握多种数据库中事务的处理逻辑。什么时候提交事务,什么时候回顾事务
  3)处理事务的多种方法。

  总结: 就是多种数据库的访问技术,有不同的事务处理的机制,对象,方法。


5.怎么解决不足
  spring提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。

  使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理
  使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。

6.处理事务,需要怎么做,做什么
  spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了

  1)事务内部提交,回滚事务,使用的事务管理器对象,代替你完成commit,rollback
     事务管理器是一个接口和他的众多实现类。
	  接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback
	  实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
	           mybatis访问数据库---spring创建好的是DataSourceTransactionManager
				  hibernate访问数据库----spring创建的是HibernateTransactionManager

     怎么使用:你需要告诉spring 你用是那种数据库的访问技术,怎么告诉spring呢?
	  声明数据库访问技术对于的事务管理器实现类, 在spring的配置文件中使用<bean>声明就可以了
	  例如,你要使用mybatis访问数据库,你应该在xml配置文件中
	  <bean id=“xxx" class="...DataSourceTransactionManager"> 


  2)你的业务方法需要什么样的事务,说明需要事务的类型。
     说明方法需要的事务:
	   1)事务的隔离级别:有4个值。
		DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
		➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
		➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
		➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
		➢ SERIALIZABLE:串行化。不存在并发问题。

      2) 事务的超时时间: 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。
		  单位是秒, 整数值, 默认是 -1. 

	  3)事务的传播行为 : 控制业务方法是不是有事务的, 是什么样的事务的。
		    7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的。

			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
	总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。


  3)事务提交事务,回滚事务的时机
     1)当你的业务方法,执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。事务管理器commit
	 
	 2)当你的业务方法抛出运行时异常或ERROR, spring执行回滚,调用事务管理器的rollback
	     运行时异常的定义: RuntimeException  和他的子类都是运行时异常, 例如NullPointException , NumberFormatException
	  
	 3) 当你的业务方法抛出非运行时异常, 主要是受查异常时,提交事务
        受查异常:在你写代码中,必须处理的异常。例如IOException, SQLException


总结spring的事务
  1.管理事务的是 事务管理和他的实现类
  2.spring的事务是一个统一模型
     1)指定要使用的事务管理器实现类,使用<bean>
	 2)指定哪些类,哪些方法需要加入事务的功能
	 3)指定方法需要的隔离级别,传播行为,超时

你需要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为。

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

spring框架中提供的事务处理方案
1.适合中小项目使用的, 注解方案。
  spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务。
  @Transactional注解是spring框架自己注解,放在public方法的上面,表示当前方法具有事务。
  可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等

  使用@Transactional的步骤:
  1.需要声明事务管理器对象
    <bean id="xx" class="DataSourceTransactionManager">

  2.开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务。
    spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。
    spring给业务方法加入事务:
	    在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知
       
		 @Around("你要增加的事务功能的业务方法名称")
		 Object myAround(){
           开启事务,spring给你开启
			  try{
			     buy(1001,10);
				 spring的事务管理器.commit();
			  }catch(Exception e){
             	 spring的事务管理器.rollback();
			  }
			 
		 }

  3.在你的方法的上面加入@Trancational

1.声明事务管理器
2.开启事务注解驱动

    <!--使用spring的事务处理-->
    <!--1.声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--连接的数据库,指定数据源-->
        <property name="dataSource" ref="myDataSource"/>
    </bean>

    <!--2,开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象
        transaction-manager:事务管理器对象的id
    -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

3.业务层 public 方法加入事务属性

    /*@Transactional(
            propagation = Propagation.REQUIRED,
            isolation = Isolation.DEFAULT,
            readOnly = false,
            rollbackFor = { // rollbackFor:表示发生指定的异常一定回滚
                    NullPointerException.class,NotEnoughException.class
            }
    )*/
    //使用的是事务控制的默认值,默认的传播行为是REQUIRED,默认的隔离级别DEFAULT
    //默认抛出运行时异常,回滚事务
    
 	/* rollbackFor处理逻辑是;
        1 ) spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中
    如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚。
        2)如果你的抛出的异常不在rollbackFor列表中,spring会判断异常是不是RuntimeException,
    如果是一定回滚。*/

    @Transactional
    @Override
    public void buy(Integer goodsid, Integer nums) {}

@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 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

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


2.适合大型项目,有很多的类,方法,需要大量的配置事务,使用aspectj框架功能,在spring配置文件中
  声明类,方法需要的事务。这种方式业务方法和事务配置完全分离。

  实现步骤: 都是在xml配置文件中实现。 
   1)要使用的是aspectj框架,需要加入依赖
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aspects</artifactId>
		<version>5.2.5.RELEASE</version>
	</dependency>

	2)声明事务管理器对象
	 
    <bean id="xx" class="DataSourceTransactionManager">
	
	3) 声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)

	4) 配置aop:指定哪些哪类要创建代理。

1.添加Maven依赖
2.添加事务管理器
3.配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。

<!--声明式事务处理:和源代码完全分离的-->
<!--1.声明事务管理对象-->
 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="dataSource" ref="myDataSource"/>
 </bean>

 <!--2.声明业务方法它的事务属性(隔离级别,传播行为,超时时间)
         id:自定义名称,表示<tx:advice></tx:advice>之间的配置内容的
         transaction-manager:事务管理器对象的id
 -->
 <tx:advice id="myAdvice" transaction-manager="transactionManager">
     <!--tx: attributes :配置事务属性-->
     <tx:attributes>
         <!--tx:method :给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
             name:方法名称,1)完整的方法名称,不带有包和类。
                          2)方法可以使用通配符,*表示任意字符
             propagation :传播行为,枚举值
             isolation:隔离级别
             rollback-for :你指定的异常类名,全限定类名。发生异常一定回滚
         -->
         <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
                    rollback-for="com.smb.excep.NotEnoughException.NotEnoughException,java.lang.NullPointerException"/>

         <!--使用通配符,指定很多的方法-->
         <tx:method name="add*" propagation="REQUIRES_NEW"/>

     </tx:attributes>
 </tx:advice>

4.配置增强器

<!--配置aop-->
<aop:config>
    <!--配置切入点表达式:指定哪些包中类,要使用事务
        id:切入点表达式的名称,唯―值
        expression :切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象
    -->
    <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>

    <!--配置增强器:关联adivce和pointcut
        advice-ref:通知,上面tx:advice哪里的配置
        pointcut-ref :切入点表达式的id
    -->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>


</aop:config>

六、Spring与Web

web项目中怎么使用容器对象。

1.做的是javase项目有main方法的,执行代码是执行main方法的,
在main里面创建的容器对象 ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);

2.web项目是在tomcat服务器上运行的。 tomcat一起动,项目一直运行的。

需求:
web项目中容器对象只需要创建一次,把容器对象放入到全局作用域ServletContext中。

怎么实现:
使用监听器 当全局作用域对象被创建时 创建容器存入ServletContext

监听器作用:
1)创建容器对象,执行 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
2)把容器对象放入到ServletContext, ServletContext.setAttribute(key,ctx)

监听器可以自己创建,也可以使用框架中提供好的ContextLoaderListener


 private WebApplicationContext context;
 public interface WebApplicationContext extends ApplicationContext


 ApplicationContext:javase项目中使用的容器对象
 WebApplicationContext:web项目中的使用的容器对象

把创建的容器对象,放入到全局作用域
 key: WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
       WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
 value:this.context

 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

1.添加Maven依赖

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

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

 <!--注册监听器contextLoaderListener
     监听器被创建对象后,会读取/wEB-INF/applicationcontext.xml
     为什么要读取文件:因为在监听器中要创建Applicationcontext对象,需要加载配置文件。
     /WEB-INF/applicationcontext.xml就是监听器默认读取的spring配置文件路径

     可以修改默认的文件位置,使用context-param重新指定文件的位置

     配置监听器:目的是创建容器对象,创建了容器对象,就能把spring.xml配置文件中的所有对象都创建好。
     用户发起请求就可以接使用对象了。
 -->

 <context-param>
     <param-name>contextConfigLocation</param-name>
     <!--自定义配置文件的路径-->
     <param-value>classpath:applicationContext.xml</param-value>
 </context-param>
 <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

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

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

 //获取servletcontext中的容器对象,创建好的容器对象,拿来就用
 WebApplicationContext ctx=null;
 String key=WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
 Object attr = getServletContext().getAttribute(key);
 if(attr!=null){
     ctx= (WebApplicationContext) attr;
 }

 //使用框架中的方法,获取容器对象
 ServletContext sc=getServletContext();
 ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值