Spring

概述

1、Spring 全家桶:Spring、SpringMVC、Spring Boot、Spring Cloud。
2、Spring:出现是在2002左右,解决企业开发的难度。减轻对项目模块之间的管理,类和类之间的管理, 帮助开发人员创建对象,管理对象之间的关系。
3、Spring 核心技术:IoC、AOP。能实现模块之间,类之间的解耦合。
(什么是依赖 class a 中使用 class b 的属性或者方法,叫做 class a 依赖 class b。)

框架怎么学习

框架是一个软件,其它人写好的软件。
1、知道框架能做什么,MyBatis 访问数据库,对表中的数据执行增删改查。
2、框架的语法,框架要完成一个功能,需要一定的步骤支持。
3、框架的内部实现,框架内部怎么做。原理是什么。
4、通过学习,可以实现一个框架。

Spring 中的 IoC

概述

1、IoC (Inversion of Control) : 控制反转,是一个理论,概念,思想。
2、把对象的创建,赋值,管理工作都交给代码之外的容器实现,也就是对象的创建是有其它外部资源完成。
3、控制: 创建对象,对象的属性赋值,对象之间的关系管理。
4、反转: 把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。由容器代替开发人员管理对象、创建对象、给属性赋值。(容器是一个服务器软件,一个框架---Spring)
5、解耦合
ioc能够实现业务对象之间的解耦合,例如 service 和 dao 对象之间的解耦合。

正转是什么

由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。

public static void main(String args[]){
	Student student = new Student(); // 在代码中,创建对象。---正转
}
为什么要使用 IoC

目的就是减少对代码的改动, 也能实现不同的功能。 实现解耦合。
java中创建对象有哪些方式:
1、构造方法,new Student()
2、反射
3、序列化
4、克隆
5、IoC:容器创建对象
6、动态代理

IoC 的体现

Servlet:
1、创建类继承 HttpServlet。
2、在 web.xml 注册Servlet。
3、没有创建 Servlet 对象, 没有 MyServlet myservlet = new MyServlet()。
4、Servlet 是 Tomcat 服务器创建的。Tomcat也称为容器。
Tomcat 作为容器:里面存放的有 Servlet 对象,Listener 对象,Filter 对象。

IoC 的技术实现

1、DI 是 IoC 的技术实现,DI(Dependency Injection):依赖注入。
2、只需要在程序中提供要使用的对象名称就可以,至于对象如何在容器中创建,赋值,查找都由容器内部实现。
3、Spring是使用的 DI 实现了 IoC 的功能,Spring底层创建对象,使用的是反射机制。
4、Spring 是一个容器,管理对象,给属性赋值,底层是反射创建对象。
5、Spring默认创建对象的时间:在创建Spring的容器时,会创建配置文件中的所有的对象。
6、Spring创建对象:默认调用的是无参数构造方法(注解的是有参)。

实现步骤:

1、创建 Maven 项目;
2、加入 Spring 的依赖、Junit 依赖;
3、创建类(接口和实现类);
4、创建 Spring 需要使用的配置文件声明类的信息,这些类由 Spring 创建和管理;
5、测试 Spring 创建的类。

代码示例

spring-conetxt 和 spring-webmvc 是 Spring 中的两个模块。
1、spring-context:是 IoC 功能的,创建对象的。
2、spring-webmvc:做 web 开发使用的,是 Servlet 的升级。其中也会用到 spring-context 中创建对象的功能。
在这里插入图片描述

package com.yu.service;

public interface OneService {
    void dosome();
}
package com.yu.service.Impl;

import com.yu.service.OneService;

public class OneServiceImpl implements OneService {

    @Override
    public void dosome() {
        System.out.println("dosome!!!");
    }
}
<?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对象
    声明bean:就是告诉Spring要创建某个类的对象
    id:对象的自定义名称,唯一值。Spring通过这个名称找到对象
    class:类的全限定名称,不能是接口(因为Spring是反射机制创建对象,必须使用类)
    -->
    <bean id="oneServiceImpl" class="com.yu.service.Impl.OneServiceImpl"/>
    <!--
    Spring就完成 OneService oneServiceImpl = new OneServiceImpl();
    Spring是把创建好的对象放入到map中,Spring框架有一个map存放对象的。
    springMap.put(id的值, 对象);
    例如springMap.put("oneServiceImpl", new OneServiceImpl());
    一个bean标签声明一个对象。
    -->
</beans>
<!--
    Spring的配置文件
    1、beans:是根标签,Spring 把 java 对象称为:bean
    2、Spring-beans.xsd 是约束文件,和MyBatis 指定 dtd 是一样的。
-->
package com.yu;

import com.yu.service.OneService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring {

    @Test
    public void oneService(){
        //使用spring容器创建的对象
        //1、指定spring配置文件的名称(在类路径下)
        String configPath = "beans.xml";
        //2、创建spring容器对象
        //ApplicationContext:表示spring容器,通过容器获取对象
        //ClassPathXmlApplicationContext:表示从类路径中加载spring的配置文件
        
        //此时容器中所有对象均已装配完毕
        ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
        //从容器中获取某个对象,使用getBean("配置文件中的bean的id值")
        OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");
        oneServiceImpl.dosome();
                
        //获取容器中对象数量
        int beansNumber = context.getBeanDefinitionCount();
        System.out.println(beansNumber);
        //获取容器中所有对象的名字
        String[] beanNames = context.getBeanDefinitionNames();
        for (String beanName:
             beanNames) {
            System.out.println(beanName);
        }
    }
}

在这里插入图片描述

ApplicationContext 容器中对象的装配时机

1、ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。
2、以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。

Junit

单元测试, 一个工具类库,做测试方法使用的。
单元:指定的是方法, 一个类中有很多方法,一个方法称为单元。

单元测试的使用
  • 加入junit依赖。
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.11</version>
	<scope>test</scope>
</dependency>
  • 创建测试作用的类
    叫做测试类,在 src/test/java 目录中创建类。
  • 创建测试方法
    1)public 方法;
    2)没有返回值,使用 void ;
    3)方法名称自定义,建议名称是test + 要测试方法名称;
    4)方法没有参数;
    5)方法的上面加入 @Test,这样的方法是可以单独执行的。不用使用main方法。

基于 XML 的 DI

1、bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化是由容器自动完成的,称为注入。
2、根据注入方式的不同,常用的有两类:set 注入、构造注入。

set 注入

1、set 注入也叫设值注入,是指通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在 Spring 的依赖注入中大量使用。
2、设值注入只是使用 set 方法。比如:
在没有属性 email 的情况下,可以对其进行设值注入,但是对象里没有 email
的内容。在这里插入图片描述

  • 简单类型
<bean id="xx" class="xx">
    <property name="属性名" value="属性值"/>
    一个 property 只能给一个属性赋值
</bean>

代码示例:

package com.yu.domain;

public class User {

    private String name;
    private int age;
    
    //有参无参
    //get、set
    //toString
}
    <bean id="user" class="com.yu.domain.User">
        <property name="name" value="zhangsan"/>
        <property name="age" value="20"/>
    </bean>
    
    <bean id="myDate" class="java.util.Date">
        <!--1970年加1588800000000毫秒-->
        <property name="time" value="1588800000000"/>
    </bean>
    @Test
    public void user(){
        String configPath = "beans.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
        User user = (User) context.getBean("user");
        Date myDate = (Date) context.getBean("myDate");
        System.out.println(user);
        System.out.println(myDate);
    }

在这里插入图片描述

  • 引用类型
    当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的值必须为某 bean 的 id 值。
<bean id="xx" class="yy">
	<property name="属性名" ref="bean的id(对象的名称)"/>
</bean>

代码示例

public class Student {

    private Integer id;
    private String sex;

    //有参无参
    //get、set
    //toString
}
public class User {

    private String name;
    private int age;
    private Student student;

    //有参无参
    //get、set
    //toString
}
<bean id="user" class="com.yu.domain.User">
	<property name="name" value="zhangsan"/>
	<property name="age" value="20"/>
	<!--使用 ref 指定它们间的引用关系-->
	<property name="student" ref="student"/>
</bean>

<bean id="student" class="com.yu.domain.Student">
	<property name="id" value="1"/>
	<property name="sex" value="男"/>
</bean>
@Test
public void user(){
    String configPath = "beans.xml";
    ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
    User user = (User) context.getBean("user");
    System.out.println(user);
}

在这里插入图片描述

构造注入

1、构造注入是指,在构造调用者实例的同时,完成被调用者的实例化(给属性赋值)。即使用构造器设置依赖关系。
2、<constructorarg /> 标签中用于指定参数的属性有:
name :指定参数名称。
index :指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。
value:构造方法的形参类型是简单类型的,使用 value。
ref:构造方法的形参类型是引用类型的,使用 ref。

  • 代码示例
public class Student {

    private Integer id;
    private String sex;

    public Student(Integer id, String sex) {
        this.id = id;
        this.sex = sex;
        System.out.println("Student 有参构造");
    }
    //无参
    //get、set
    //toString
<bean id="student" class="com.yu.domain.Student">
	<!--<property name="id" value="1"/>
	<property name="sex" value="男"/>-->
<!--  或  -->
	<constructor-arg name="id" value="111"/>
	<constructor-arg name="sex" value="女"/>
<!--  或  -->
	<!--<constructor-arg index="0" value="111"/>
	<constructor-arg index="1" value="女"/>-->
<!--  或(参数顺序要一致)  -->
    <!--<constructor-arg value="111"/>
    <constructor-arg value="女"/>-->
</bean>
@Test
public void user(){
    String configPath = "beans.xml";
    ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
    Student user = (Student) context.getBean("student");
    System.out.println(user);
}

在这里插入图片描述

  • 构造注入创建文件对象
<bean name="myFile" class="java.io.File">
    <constructor-arg index="0" value="C:\Users\lenovo\Desktop\spring"/>
    <constructor-arg index="1" value="spring课堂笔记"/>
</bean>
@Test
public void myFile(){
    String configPath = "beans.xml";
    ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
    //User user = (User) context.getBean("user");
    File myFile = (File) context.getBean("myFile");
    System.out.println(myFile.getName());
}

在这里插入图片描述

引用类型属性自动注入

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

  • byName 方式自动注入
    1、当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用 byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。
    2、容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
    3、图示
    在这里插入图片描述
    这里的 student 属性名、类型和配置文件中的 Student bean 的 id 值对应的属性名、类型一致,使用autowire="byName"可实现自动注入。
    在这里插入图片描述
    autowire="byName"表示:
    给 User 类中的所有引用类型按照 byName 规则让 Spring 完成赋值。
    4、代码示例
public class Student {

    private Integer id;
    private String sex;

    //有参无参
    //get、set
    //toString
}
public class User {

    private String name;
    private int age;
    private Student student;

    //有参无参
    //get、set
    //toString
}
<bean id="user" class="com.yu.domain.User" autowire="byName">
    <property name="name" value="zhangsan"/>
    <property name="age" value="20"/>

    <!--<property name="student" ref="student"/>-->
</bean>

<bean id="student" class="com.yu.domain.Student">
    <property name="id" value="1"/>
    <property name="sex" value="男"/>
</bean>
@Test
public void user(){
    String configPath = "beans.xml";
    ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
    User user = (User) context.getBean("user");
    System.out.println(user);
}

在这里插入图片描述

  • byType 方式自动注入
    1、使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。
    2、即要么相同,要么有 is a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了
    3、java 类中引用类型的数据类型和 Spring 容器中(配置文件)<bean/>的 class 属性是同源关系的,这样的 bean 能够赋值给引用类型。
    4、什么是同源?
    就是一类的意思,包括以下:
    ① java 类中引用类型的数据类型和 bean 的 class 的值是一样的;
    ② 或父子类关系的;
    ③ 或接口和实现类关系的。
    5、图例:
    在这里插入图片描述
    Student 类型对应 com.yu.domain.Student,是一样的。
    在这里插入图片描述
    这里避免 bean 的 id 与 属性名相同,将其 id 改为 myStudent。
    6、代码示例
    在上面的代码中将 xml 进行改动即可:
<bean id="user" class="com.yu.domain.User" autowire="byType">
    <property name="name" value="zhangsan"/>
    <property name="age" value="20"/>

    <!--<property name="student" ref="student"/>-->
</bean>

<bean id="myStudent" class="com.yu.domain.Student">
    <property name="id" value="1"/>
    <property name="sex" value="男"/>
</bean>

这里只是写了一个(java 类中引用类型的数据类型和 bean 的 class 的值是一样的)例子,还有两个例子(父子类关系的;接口和实现类关系的)也是类似的。

为应用指定多个 Spring 配置文件
  • 概述
    1、在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。
    2、包含关系的配置文件:
    多个配置文件中有一个总文件,总配置文件将各其它子文件通过<import/>引入。在 java 代码中只需要使用总配置文件对容器进行初始化即可。
  • 多个配置优势
    1、每个文件的大小比一个文件要小很多,效率高。
    2、避免多人竞争带来的冲突。
    3、如果你的项目有多个模块(相关的功能在一起),一个模块一个配置文件。比如:学生考勤模块一个配置文件,给张三;学生成绩一个配置文件,给李四。
  • 多文件的分配方式:
    1、按功能模块,一个模块一个配置文件。
    2、按类的功能,数据库相关的一个配置文件,做事务功能的一个配置文件,做 Service 功能的一个配置文件等。
  • 例子
    在这里插入图片描述
    Spring 主配置文件(beans.xml):
<import resource="spring-student.xml"/>
<import resource="spring-user.xml"/>

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

也可使用通配符*。但此时要求父配置文件名不能满足*所能匹配的格式,否则将出现循环递归包含。就本例而言,父配置文件不能匹配 spring-*.xml 的格式,即不能起名为 spring-total.xml
(其中<import resource="classpath:com/yu/.../spring-student.xml"/>这种方式表示类路径,即 class 文件所在的目录)
测试代码:

@Test
public void user(){
    String configPath = "beans.xml";
    ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
    User user = (User) context.getBean("user");
    System.out.println(user);
}

在这里插入图片描述

基于注解的 DI

基于注解的 di:通过注解完成 java 对象创建,属性赋值。

使用注解的步骤:

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

组件扫描器

需要在 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"
       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):指定注解所在的包名-->
    <context:component-scan base-package="com.yu.domain"/>
</beans>
  • 指定多个包的三种方式
    1、使用多个 context:component-scan 指定不同的包路径。
<!--声明组件扫描器(component-scan):指定注解所在的包名-->
<context:component-scan base-package="com.yu.domain"/>
<context:component-scan base-package="com.yu.utils"/>

2、指定 base-package 的值使用分隔符。分隔符可以使用逗号(,)、分号(;)、还可以使用空格,但不建议使用空格。
逗号分隔:

<context:component-scan base-package="com.yu.domain, com.yu.utils"/>

分号分隔:

<context:component-scan base-package="com.yu.domain; com.yu.utils"/>

3、base-package 是指定到父包名。base-package 的值表示基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。所以 base-package 可以指定一个父包就可以。

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

或者最顶级的父包

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

但不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢。
指定到目标包和合适的。也就是注解所在包全路径。例如注解的类在 com.yu.domain 包中。

<context:component-scan base-package="com.yu.domain"/>
7个注解

1、@Component
2、@Respository
3、@Service
4、@Controller
5、@Value
6、@Autowired
7、@Resource

@Component

用来定义 Bean 的注解,需要在类上使用注解@Component。该注解的 value 属性用于指定该 bean 的 id 值。

  • 第一种方式
@Component(value = "myStudent")
/**
 * @Component:创建对象的,等同于 <bean> 的功能。
 * 属性:value 就是对象的名称,也就是 bean 的 id 值,
 *      value 的值是唯一的,创建的对象在整个 spring 容器中就一个。
 * 位置:在类的上面。
 * @Component(value = "myStudent")
 * 等同于
 * <bean id="myStudent" class="com.yu.domain.Student" />
 */
@Component(value = "myStudent")
public class Student {

    private Integer id;
    private String sex;

    //有参无参
    //get、set
    //toString
}
  • 第二种方式
//使用省略 value 的写法
@Component("myStudent")
  • 第三种方式
    @Component 不指定 value 属性,bean 的 id 是类名的首字母小写。
    在这里插入图片描述
@Component 注解的细化

1、另外,Spring 还提供了3个创建对象的注解(使用语法一样,但是功能不一):
@Repository 用于对 DAO 实现类进行注解。
@Service 用于对 Service 实现类进行注解。
@Controller 用于对 Controller 实现类进行注解。
2、这三个注解与 @Component 都可以创建对象,但这三个注解还有其他的 含义 。@Service 创建业务层对象,业务层对象可以加入事务功能。@Controller 注解创建的对象可以作为处理器接收用户的请求。
3、@Repository、@Service、@Controller 是对 @Component 注解的细化,标注不同层的对象。 即持久层对象,业务层对象,控制层对象。

@Value

1、用于简单类型属性注入,需要在属性上使用注解 @Value,该注解的 value 属性用于指定要注入的值(value 是 String 类型的)。
2、使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
3、代码示例

package com.yu.domain;

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

@Component
public class Student {

    @Value(value = "123")
    //@Value("123")
    private Integer id;

    //@Value(value = "男")
    @Value("男")
    private String sex;

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", sex='" + sex + '\'' +
                '}';
    }
}
<context:component-scan base-package="com.yu.domain"/>
@Test
public void user(){
    String configPath = "applicationContext.xml";
    ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
    Student student = (Student) context.getBean("student");
    System.out.println(student);
}

在这里插入图片描述
4、还可以加在 set 方法上:

    @Value("男")
    private String sex;

@Value("123")
public void setId(Integer id) {
    this.id = id;
}

@Value("男")
public void setSex(String sex) {
    this.sex = sex;
}

5、如果在属性和 set 方法上同时赋值 value,最终显示的只是 set 赋的值。

@Value("123")
private Integer id;
@Value("男")
private String sex;

@Value("456")
public void setId(Integer id) {
    this.id = id;
}
@Value("女")
public void setSex(String sex) {
    this.sex = sex;
}

在这里插入图片描述

@Autowired

1、在引用属性上使用注解 @Autowired。
2、Autowired:spring 框架提供的注解,实现引用类型的赋值。spring 中通过注解给引用类型赋值,使用的是自动注入原理,支持 byName、byType。
3、@Autowired 默认使用的是 byType 自动注入。
4、使用位置:
1)在属性定义的上面,无需 set 方法,推荐使用。
2)在 set 方法的上面。

  • byType 方式
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • byName 方式
    1、需要在引用属性上联合使用注解 @Autowired 与 @Qualifier。@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
    2、在属性上面加入 @Qualifier(value="bean的id"):表示使用指定名称的 bean 完成赋值。
    3、示例:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • required 属性
    @Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
    1、是一个 boolean 类型,默认为 true。
    2、required = true:表示引用类型赋值失败,程序报错,并终止执行。
    3、required = false:引用类型如果赋值失败,程序正常执行,引用类型是 null。
    在这里插入图片描述
    使用 true:
    在这里插入图片描述
    报错:
    在这里插入图片描述
    使用 false:
    在这里插入图片描述
    不报错,student 为 null:
    在这里插入图片描述
    注意:@Autowired 的 required 属性推荐使用 true。这样更好调试,并且避免了空指针异常
@Resource

1、Spring 提供了对 jdk 中 @Resource 注解的支持。
2、@Resource 注解既可以按名称匹配 Bean 也可以按类型匹配 Bean。 默认是按名称注入使用该注解。
3、要求 JDK 必须是 6 及以上版本。@Resource 可在属性上,也可在 set 方法上。

  • byType 注入引用类型属性
    @Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean 则会按照类型进行 Bean 的匹配注入。
    在这里插入图片描述
    在这里插入图片描述
  • byName 注入引用类型属性(默认的)
    @Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
    在这里插入图片描述
    在这里插入图片描述

注解与 XML 的对比

注解优缺点
  • 优点
    1、方便。
    2、直观。
    3、高效(代码少,没有配置文件的书写那么复杂)。
  • 缺点
    其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。
XML 方式优缺点
  • 优点
    1、配置和代码是分离的(不够直观)。
    2、在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。
  • 缺点
    xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。
总结

1、经常改的用 XML,不经常改的用注解。
2、注解为主,XML 为辅。

使用配置文件的方式赋值来解耦合

在这里插入图片描述
配置 xml:

<context:property-placeholder location="test.properties"/>

使用@Value("${ }")的方式进行赋值:

package com.yu.domain;

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

@Component("oneStudent")
public class Student {

    //@Value("123")
    @Value("${myId}")
    private Integer id;

    //@Value("男")
    @Value("${mySex}")
    private String sex;

    //有参无参
    //get、set
    //toString
}

在这里插入图片描述

动态代理

  • 实现方式
    1、jdk 动态代理,使用 jdk 中的 Proxy,Method,InvocaitonHanderl 创建代理对象。jdk 动态代理要求目标类必须实现接口。
    2、cglib 动态代理:第三方的工具库,创建代理对象,原理是继承。通过继承目标类,创建子类。子类就是代理对象。 要求目标类不能是 final 的,方法也不能是 final 的。
  • 动态代理的作用:
    1、在目标类源代码不改变的情况下,增加功能;
    2、减少代码的重复;
    3、专注业务逻辑代码;
    4、解耦合,让业务功能和日志,事务非业务功能分离。
  • 代码示例

接口:

package com.yu.service;

public interface OneService {

    public void doSome();
    public void doOther();
}

实现类:

package com.yu.service.impl;

import com.yu.service.OneService;

public class OneServiceImpl implements OneService {

    @Override
    public void doSome() {
        System.out.println("这是doSome方法");
    }

    @Override
    public void doOther() {
        System.out.println("这是doOther方法");
    }
}

非业务方法工具类:

package com.yu.utils;

public class ServiceUtils {

    public static void dobefore(){
        System.out.println("111111执行目标方法前执行的!");
    }

    public static void doafter(){
        System.out.println("222222执行目标方法后执行的!");
    }

}

MyInvocationHandler:

package com.yu.handler;

import com.yu.utils.ServiceUtils;

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

public class MyInvocationHandler implements InvocationHandler {

    //目标对象
    private  Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //目标方法执行结果
        Object result = null;

        //doSome才能执行非业务方法
        if ("doSome".equals(method.getName())){
            //非业务方法
            ServiceUtils.dobefore();
            //执行目标方法返回结果
            result = method.invoke(target, args);
            //非业务方法
            ServiceUtils.doafter();
        }else {
            //其它的方法没有非业务方法
            result = method.invoke(target, args);
        }

        return result;
    }
}

测试:

package com.yu;

import com.yu.handler.MyInvocationHandler;
import com.yu.service.OneService;
import com.yu.service.impl.OneServiceImpl;
import org.junit.Test;

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

public class testProxy {
    @Test
    public void test(){

        //创建目标对象
        OneService target = new OneServiceImpl();
        //将目标对象交给InvocationHandler
        InvocationHandler myHandler = new MyInvocationHandler(target);
        //创建代理对象
        OneService oneService = (OneService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                myHandler);
        //代理对象执行方法
        oneService.doSome();
        System.out.println("============================");
        oneService.doOther();
    }
}

在这里插入图片描述

AOP

概述

1、AOP(Aspect Orient Programming)
面向切面编程。面向切面编程是从动态角度考虑程序运行过程,可通过运行期动态代理实现程序功能的统一维护的一种技术。
Aspect:切面,给目标类增加的功能,就是切面。 像上面用的 ServiceUtils 工具类中的 dobefore()、doafter() 方法都是切面。切面的特点:一般都是非业务方法,独立使用的。
Orient:面向。
Programming:编程。

2、AOP 底层是采用动态代理模式实现的
采用了两种代理:JDK 的动态代理与 CGLIB 的动态代理 。AOP 就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理。

3、降低耦合度
利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

4、将交叉业务逻辑封装成切面
面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志 、缓存等。若不使用 AOP ,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样会使主业务逻辑变的混杂不清。

5、例如:转账
在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑转账。

面向切面编程的好处

1、减少重复。
2、专注业务。
注意:面向切面编程只是面向对象编程的一种补充。
使用 AOP 减少重复代码,专注业务实现。
在这里插入图片描述

面向切面编程的使用

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

相关术语

1、切面(Aspect)
切面泛指交叉业务逻辑。上例中的 ServiceUtils 工具类中的 dobefore()、doafter() 方法就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。用来完成一些非业务功能,常见的切面功能有日志、事务、统计信息、参数检查、权限验证等。

2、连接点(JoinPoint)
连接点指可以被切面织入的具体方法,通常业务接口中的方法均为连接点。
就是连接业务方法和切面的位置,例如某类中的业务方法。

3、切入点(Pointcut)
切入点指声明的一个或多个连接点的集合(多个方法)。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

4、目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。给哪个类的方法增加功能,这个类就是目标对象。

5、通知(Advice)
通知表示切面功能执行的时间,Advice 也叫增强。
上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。
通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。

切面的三个关键要素

1、切面的功能代码,表明切面干什么。
2、切面的执行位置,使用 Pointcut 表示切面执行的位置。
3、切面的执行时间,使用 Advice 表示时间,在目标方法之前,还是目标方法之后。

AOP 的实现
  • AOP 是一个规范,是动态的一个规范化,一个标准。
  • AOP 的技术实现框架
    1、Spring:Spring 在内部实现了 AOP 规范,能做 AOP 的工作。Spring 主要在事务处理时使用 AOP。项目开发中很少使用 Spring 的 AOP 实现。因为 Spring 的 AOP 比较笨重。
    2、AspectJ:一个开源的专门做 AOP 的框架。Spring 框架中集成了 AspectJ 框架,通过 Spring 就能使用 AspectJ 的功能。

AspectJ

概述

AspectJ 框架实现 AOP 有两种方式:
1、使用 xml 的配置文件:配置全局事务。
2、使用注解,我们在项目中要做 AOP 功能,一般都使用注解,AspectJ 有5个注解。

通知类型

切面的执行时间。这个执行时间在规范中叫做 Advice(通知,增强),在 AspectJ 框架中使用注解表示的。也可以使用 xml 配置文件中的标签。
1、@Before(前置通知)
2、@AfterReturning(后置通知)
3、@Around(环绕通知)
4、@AfterThrowing(异常通知)
5、@After(最终通知)

切入点表达式

表示切面执行的位置,使用的是切入点表达式。
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:

execution(modifiers-pattern? ret-type-pattern 
		declaring-type-pattern?name-pattern(param-pattern) 
		throws-pattern?)
  • 解释
    modifiers-pattern:访问权限类型;
    ret-type-pattern:返回值类型;
    declaring-type-pattern:包名 类名;
    name-pattern(param-pattern):方法名 参数 类型和参数个数;
    throws-pattern:抛出异常类型;
    ?:表示可选的部分。
    以上表达式共4 个部分。
    execution( 访问权限 方法返回值 方法声明(参数) 异常类型)
  • 表达式中可使用的符号
    1、切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。
    2、注意,表达式中(访问权限、异常类型)可省略,各部分间用空格分开。在其中可以使用以下符号:
    在这里插入图片描述
示例

1、execution(public * *(..))
指定切入点为:任意公共方法。
2、execution(* set*(..))
指定切入点为:任何一个以 “set” 开始的方法。
3、execution(* com.xyz.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。
4、execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..” 出现在类名中时,后面必须跟 “*”,表示包、子包下的所有类。
5、execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点。
6、execution(* *.service.*.*(..))
指定只有一级包下的 serivce 子包下所有类 (接口)中所有方法为切入点。
7、execution(* *.ISomeService.*(..))
指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点。
8、execution(* *..ISomeService.*(..))
指定所有包下的 ISomeSerivce 接口中所有方法为切入点
9、execution(* com.xyz.service.IAccountService.*(..))
指定切入点为:IAccountService 接口中的任意方法。
10、execution(* com.xyz.service.IAccountService+.*(..))
指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
11、execution(* joke(String, int))
指定切入点为:所有的 joke(String, int) 方法,且 joke() 方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int) 。
12、execution(* joke(String, *))
指定切入点为:所有的 joke() 方法,该方法第一个参数为 String ,第二个参数可以是任意类型,如 joke(String s1,String s2) 和 joke(String s1,double d2) 都是,但 joke(String s1,double d2,Strings3) 不是。
13、execution(* joke(String, ..))
指定切入点为:所有的 joke() 方法,该方法第一个参数为 String ,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1, Strings2) 和 joke(String s1, double d2, String s3) 都是。
14、execution(* joke(Object))
指定切入点为:所有的 joke() 方法,方法拥有一个参数,且参数是 Object 类型。 joke( Object ob)是,但, joke(String s) 与 joke(User u) 均不是。
15、execution(* joke(Object+))
指定切入点为:所有的 joke() 方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob) 是,joke(String s) 和 joke(User u) 也是。

使用 AspectJ 的基本步骤
  • 新建 Maven 项目
  • 加入依赖
    spring 依赖、aspectj 依赖、junit 单元测试依赖
  • 创建目标类
    接口和它的实现类。因为要做的是给类中的方法增加功能。
  • 创建切面类
    这是一个普通类,需要在类的上面加入 @Aspect;
    在类中定义方法,方法就是切面要执行的功能代码;
    在方法的上面加入 AspectJ 中的通知注解,例如 @Before;
    有需要的话指定切入点表达式 execution()。
  • 创建 Spring 的配置文件
    声明对象,把对象交给容器统一管理;
    声明对象可以使用注解或者 xml 配置文件的<bean>
    1、声明目标对象;
    2、声明切面类对象;
    3、声明 AspectJ 框架中的自动代理生成器标签。自动代理生成器:用来完成代理对象的自动创建功能的。
  • 创建测试类
    从 Spring 容器中获取目标对象(实际就是代理对象)。通过代理执行方法,实现 AOP 的功能增强。
  • <aop:aspectj-autoproxy/>
    1、底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。
    2、其工作原理是,<aop:aspectj-autoproxy/>通过扫描找到 @Aspect 定义的切面类(一次性全部生成代理对象),再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
代码示例(前置通知-注解版)

接口:

package com.yu.service;

public interface OneService {

    void doSome(String name, int age);
    void doOther();

}

实现类:

package com.yu.service.impl;

import com.yu.service.OneService;
import org.springframework.stereotype.Component;

@Component
public class OneServiceImpl implements OneService {

    @Override
    public void doSome(String name, int age) {
        System.out.println("这是doSome方法");
    }

    @Override
    public void doOther() {
        System.out.println("这是doOther方法");
    }

}

普通类(切面):

package com.yu.aspects;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.lang.reflect.Modifier;


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

    /*
    * @Before:前置通知注解
    * 属性:value,是切入点表达式,表示切面的功能执行的位置。
    * 位置:在方法的上面
    * 特点:在目标方法之前先执行,不会改变、影响目标方法的执行。
    * */
    @Before(value = "execution(* com.yu.service.impl.OneServiceImpl.doSome())")
    public void before(JoinPoint joinPoint){
        System.out.println("这是前置通知,在目标方法前执行,例如输出日志");
    }
}

xml 配置:

<!--声明组件扫描器(component-scan):指定注解所在的包名-->
<context:component-scan base-package="com.yu.domain, com.yu.service, com.yu.aspects"/>

<!--声明自动代理生成器,创建代理-->
<aop:aspectj-autoproxy/>

测试类:

package com.yu;

import com.yu.service.OneService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class testProxy {
    @Test
    public void test(){

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");
        //JDK动态代理
        System.out.println(oneServiceImpl.getClass().getName());
        oneServiceImpl.doSome("zahngsan", 20);
        System.out.println("==============");
        //doOther方法没有设置切入点
        oneServiceImpl.doOther();
    }
}

在这里插入图片描述

基于 XML 配置的 AOP

待完善。。。

JoinPoint

1、JoinPoint 对象封装了 Spring Aop 中切面方法的信息,在切面方法中添加 JoinPoint 参数,就可以获取到封装了该方法信息的 JoinPoint 对象。
2、在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个
JoinPoint 类型参数。这个 JoinPoint 参数的值是由框架赋予,必须是第一个位置的参数
3、该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象、方法名称、方法的实参等。
4、不光前置通知的方法可以包含一个 JoinPoint 类型参数 ,所有的通知方法均可包含该参数,如果切面功能中需要用到方法的信息,就加入 JoinPoint。

方法名 功能
Signature getSignature(); 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs(); 获取传入目标方法的参数对象
Object getTarget(); 获取被代理的对象
Object getThis(); 获取代理对象
  • 代码示例
package com.yu.aspects;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.lang.reflect.Modifier;

@Aspect
@Component
public class OneAspect {
    @Before(value = "execution(* com.yu.service.impl.OneServiceImpl.doSome(..))")
    public void before(JoinPoint joinPoint){
        System.out.println("目标方法签名为:" + joinPoint.getSignature());
        System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
        System.out.println("目标方法所属类的简单类名:" +        joinPoint.getSignature().getDeclaringType().getSimpleName());
        System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
        System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
        //获取传入目标方法的参数
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            System.out.println("第" + (i+1) + "个参数为:" + args[i]);
        }
        System.out.println("被代理的对象:" + joinPoint.getTarget());
        System.out.println("代理对象自己:" + joinPoint.getThis());
        System.out.println("这是前置通知,在目标方法前执行,例如输出日志");
    }
}

在这里插入图片描述

后置通知

1、在目标方法执行之后执行。
2、由于是目标方法之后执行,所以可以获取到目标方法的返回值。
3、该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。
4、该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

  • 代码示例
    接口:
package com.yu.service;

import com.yu.domain.Student;

public interface OneService {

    void doSome(String name, int age);
    String doOther();
    Student doStudent(int id, String sex);

}

实现类:

package com.yu.service.impl;

import com.yu.domain.Student;
import com.yu.service.OneService;
import org.springframework.stereotype.Component;

@Component
public class OneServiceImpl implements OneService {

    @Override
    public void doSome(String name, int age) {
        System.out.println("这是doSome方法");
    }

    @Override
    public String doOther() {
        System.out.println("这是doOther方法");
        return "qweasd";
    }

    @Override
    public Student  doStudent(int id, String sex) {
        System.out.println("这是doStudent方法");
        Student student = new Student();
        student.setId(id);
        student.setSex(sex);
        return  student;
    }
}

普通类(切面):

package com.yu.aspects;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class OneAspect {
    /* 方法的定义要求:
     * 1、公共方法(public)
     * 2、方法没有返回值
     * 3、方法名称自定义
     * 4、方法有参数,推荐Object,参数名自定义
     * */

    /*
    * @AfterReturning:前置通知注解
    * 属性:value---是切入点表达式,表示切面的功能执行的位置;
    *       returning---自定义的变量,表示目标方法的返回值;
    *       自定义变量名必须和通知方法的形参名一样。
    * 位置:在方法的上面
    * 特点:在目标方法之后执行;
    *       能够获取到目标方法的返回值,可以根据返回值做不同的处理功能;
    *       可以修改这个返回值。
    * */
    @AfterReturning(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))",
            returning = "result")
    public void before(Object result){
        /* Object result
        * 是目标方法执行后的返回值,根据返回值做切面的功能处理
        * */

        /* 修改目标方法的返回值
         *
         * 相当于先执行Object result = doOther()
         * 再执行before(Object result)
         * */
        if( result != null){
            String st = (String)result;
            result = st.toUpperCase();
        }
        
        System.out.println("这是后置通知,在目标方法后执行,获取的result是:" + result);
        
        if("qweasd".equals(result)) {
            System.out.println("做一些操作");
        }else {
            System.out.println("做另一些操作");
        }
    }

}

xml 配置:

<!--声明组件扫描器(component-scan):指定注解所在的包名-->
<context:component-scan base-package="com.yu.domain, com.yu.service, com.yu.aspects"/>

<!--声明自动代理生成器,创建代理-->
<aop:aspectj-autoproxy/>

测试类:

@Test
    public void test(){

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");

        oneServiceImpl.doSome("zahngsan", 20);
        System.out.println("==============");

        //doOther方法没有设置切入点
        String result = oneServiceImpl.doOther();
        System.out.println("result======" + result);

    }

在这里插入图片描述

  • 修改 result 的内容,属性值等
    @AfterReturning(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))",
            returning = "result")
    public void before(Object result){
        
        Student student = (Student) result;
        student.setId(333);
        student.setSex("men");
        
        System.out.println("这是后置通知,在目标方法后执行,获取的result是:" + result);
        
    }
@Test
    public void test(){

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");

        Student student = oneServiceImpl.doStudent(22,"women");
        System.out.println(student);
    }

在这里插入图片描述
调用 doOther 方法,修改返回的 String 类型的值:

@AfterReturning(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))",
            returning = "result")
public void before(Object result){

    String res = result.toString()+"aaaaaaa";
    System.out.println("这是后置通知,在目标方法后执行,获取的result是:" + res);

}
String res = oneServiceImpl.doOther();
System.out.println(res);

在这里插入图片描述
不会改变。

环绕通知

1、在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。
2、接口 ProceedingJoinPoint 有一个 proceed() 方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。
3、最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

  • 代码示例
    接口:
package com.yu.service;

public interface OneService {

    String  doOne(String school, String address);

}

实现类:

package com.yu.service.impl;

import com.yu.service.OneService;
import org.springframework.stereotype.Component;

@Component
public class OneServiceImpl implements OneService {

    @Override
    public String doOne(String school, String address) {
        System.out.println("这是doOne方法");
        return "beida";
    }
    
}

切面类:

package com.yu.aspects;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class OneAspect {

    /**
     * 环绕通知方法定义格式
     * 1、public。
     * 2、必须有一个返回值,推荐使用Object。
     * 3、方法名称自定义。
     * 4、方法有参数,固定参数ProceedingJoinPoint,
     *      等同于动态代理中的Method,用于执行目标方法;
     *      其返回值就是目标方法的执行结果,可以被修改。
     * 5、是功能最强的通知,在目标方法前后都能增强功能。
     * 6、控制目标方法是否被调用执行。
     * 7、可以改变原来目标方法的执行结果,影响最后的调用结果。
     * 8、等同于JDK动态代理中的InvocationHandler接口。
     * */
    @Around(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        //获取第一个参数值
        String arg1 = null;
        Object[] args = pjp.getArgs();
        if (args != null && args.length > 0){
            arg1 = args[0].toString();
        }

        Object obj = null;
        //增强功能
        System.out.println("环绕通知: 在目标方法前执行,例如输出日志");
        
        //执行目标方法的调用(必须),等同于method.invoke(target, args)
        if ("xx".equals(arg1)){
            //如果符合条件才执行目标方法
            obj = pjp.proceed();
        }
        
        //增强功能
        System.out.println("环绕通知: 在目标方法后执行,例如处理事务");

        //返回目标方法的执行结果
        return obj;

    }

}

测试:

public class testProxy {
    @Test
    public void test(){

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");

        String doOne = oneServiceImpl.doOne("xx","bj");
        System.out.println(doOne);
    }
}

在这里插入图片描述

  • 修改目标方法的返回结果
    在这里插入图片描述
    在这里插入图片描述
  • 用于事务
    环绕通知经常用于事务,在目标方法执行之前开启事务,在目标方法执行之后提交事务。
异常通知

在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable ,参数名称为 throwing 指定的名称,表示发生的异常对象。

  • 代码示例
    业务方法的实现:
@Override
public void doTwo() {
    System.out.println("这是doTwo方法" + 1/0);
}

切面类:

package com.yu.aspects;

import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class OneAspect {

    /** 异常通知方法定义格式
     * 1、public。
     * 2、没有返回值。
     * 3、方法名称自定义。
     * 4、方法有参数Throwable或者Exception,
     *      如果还有就是JoinPoint。
     * */

    /** @AfterThrowing:异常通知
     * 属性:
     * 1、value:切入点表达式
     * 2、throwing:自定义的变量,表示目标方法抛出的异常对象。
     *      变量名必须和方法的参数名一样。
     *
     * 特点:
     * 1、在目标方法抛出异常时执行。
     * 2、可以做异常的监控程序,监控目标方法执行时是否有异常。
     *      如果有异常,可以发送邮件、短信进行通知。
     * */
    @AfterThrowing(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))", throwing = "ex")
    public void around(Throwable ex) {
        //把异常发生的时间、位置、原因记录到数据库,日志文件等等。
        //可以在异常发生时,把异常信息通过短信、邮件发送给开发人员。
        System.out.println("异常通知:在目标方法抛出异常时执行的,异常原因:" + ex.getMessage());

    }

}

测试:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");

oneServiceImpl.doTwo();

在这里插入图片描述

最终通知

无论目标方法是否抛出异常,该增强均会被执行。

  • 代码示例
    业务方法的实现:
@Override
public void doThird() {
    System.out.println("这是doThird方法");
}

切面类:

package com.yu.aspects;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class OneAspect {

    /** 最终通知方法定义格式
     * 1、public。
     * 2、没有返回值。
     * 3、方法名称自定义。
     * 4、方法没有参数。
     * */

    /** @AfterThrowing:异常通知
     * 属性:
     * 1、value:切入点表达式
     *
     * 特点:
     * 1、在目标方法之后执行。
     * 2、总是会执行。
     * */
    @After(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))")
    public void around() {
        //一般做资源清理工作
        System.out.println("执行最终通知,总是会被执行的代码。");
    }

}

测试:

oneServiceImpl.doThird();

在这里插入图片描述

  • 加上异常:
    在这里插入图片描述
    还是会执行:
    在这里插入图片描述
@Pointcut 定义切入点

1、当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。
2、AspectJ 提供了 @Pointcut 注解,用于定义 execution 切入点表达式。其用法是,将 @Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。
3、代表的就是 @Pointcut 定义的切入点。这个使用 @Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

  • 代码示例
package com.yu.aspects;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class OneAspect {

    /**
     * 用来定义和管理切面点,简化切入点的定义。
     * 便于复用。
     * */
    @Pointcut(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))")
    private void myPc(){
        //无需代码
    }

    @After(value = "myPc()")
    public void after() {
        System.out.println("执行最终通知,总是会被执行的代码。");
    }

}
设置 AspectJ 实现 AOP的方式

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

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

        1、目标类有接口,默认使用jdk动态代理。
        2、目标类没有接口,默认时候cglib动态代理
        3、目标类有接口,也可以使用cglib动态代理,需要设置proxy-target-class="true"
    -->
    <!-- <aop:aspectj-autoproxy proxy-target-class="true" />-->
    <aop:aspectj-autoproxy/>
AspectJ 基于 XML 的 AOP 实现

待完善。。。

Spring 实现 AOP

待完善。。。

集成 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>
  1. mapper文件的位置
<mappers>
<mapper resource="com/bjpowernode/dao/StudentDao.xml"/>
<!--<mapper resource="com/bjpowernode/dao/SchoolDao.xml" />-->
</mappers>
代码示例

1、新建 t_student 表。
在这里插入图片描述
2、加入 pom 依赖。
Junit 依赖;
spring 依赖;
mybatis 的依赖;
mybatis-spring 依赖;
mysql 的驱动;
spring-jdbc 依赖;
druid,数据库连接池的依赖;
资源插件。

<?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.yu</groupId>
  <artifactId>demo</artifactId>
  <version>1.0</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!-- 告诉 maven 在 jdk1.8 上编译 -->
    <maven.compiler.source>1.8</maven.compiler.source>
    <!-- 在 jdk1.8 上运行 -->
    <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>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.22</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>

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

    <!--mybatis整合spring的依赖:创建mybatis对象-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.5</version>
    </dependency>

    <!--事务-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.3.5</version>
    </dependency>

    <!--spring访问数据库-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.5</version>
    </dependency>

    <!--数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.5</version>
    </dependency>

  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>src/main</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <!-- filtering 选项 false 不启用过滤器, *.property 已经起到过滤的作用了 -->
        <filtering>false</filtering>
      </resource>
    </resources>

    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

3、实体类 Student。

package com.yu.domain;

import org.springframework.stereotype.Component;

@Component
public class Student {

    private Integer id;

    private Integer stuNo;

    private String stuName;

    private Integer classNo;

	//有参无参
	//set、get
	//toString
}

4、新建 Dao 接口和 sql 映射文件。

package com.yu.dao;

import com.yu.domain.Student;

import java.util.List;

public interface StudentDao {

    int insertStudent(Student stu);
    List<Student> selectStudent();

}
<?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.yu.dao.StudentDao">

    <select id="selectStudent" resultType="com.yu.domain.Student">
        select id,stuno,stuname,classno from t_student
    </select>

    <insert id="insertStudent">
        insert into t_student values(#{id},#{stuNo},#{stuName},#{classNo})
    </insert>

</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>
        <!--配置日志功能-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

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

    <mappers>
        <!--
        一次加载包中的所有mapper.xml文件
        条件:
        1、sql 映射文件名和 Dao 接口名一致;
        2、sql 映射文件和 Dao 接口在同一目录。
        -->
        <mapper resource="java/com/yu/dao/StudentDao.xml"/>
    </mappers>
</configuration>

6、新建 Service 接口和实现类,在实现类中有 Dao 的属性。

package com.yu.service;

import com.yu.domain.Student;

import java.util.List;

public interface StudentService {

    int addStudent(Student student);
    List<Student> queryStudent();
}
package com.yu.service.impl;

import com.yu.dao.StudentDao;
import com.yu.domain.Student;
import com.yu.service.StudentService;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class StudentServiceImpl implements StudentService {

    private StudentDao studentDao;

    public StudentServiceImpl(StudentDao studentDao) {
    }

    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> queryStudent() {
        List<Student> students = studentDao.selectStudent();
        return students;
    }
}

7、spring 配置文件。
声明数据源 DataSource 对象;
声明 SqlSessionFactoryBean,创建 SqlSessionFactory 对象;
声明 MyBatis 的 MapperScannerConfigurer 扫描器,创建 Dao 接口的实现类对象;
声明自定义的 Service ,把 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">

    <!--声明组件扫描器(component-scan):指定注解所在的包名-->
    <context:component-scan base-package="com.yu"/>

    <!--读取配置文件
        location:指定属性配置文件的路径
        "classpath:":关键字表示类文件,也就是class文件所在的目录
    -->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!--声明数据源DataSource-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--读取属性配置文件的key的值,使用 ${key}-->
        <!--数据库的uri-->
        <property name="url" value="${url}"/> <!--setUrl()-->
        <!--数据库的用户名-->
        <property name="username" value="${user}"/> <!--setUser()-->
        <!--访问密码-->
        <property name="password" value="${password}" /><!--setPassoword()-->
        <!--最大连接数-->
        <property name="maxActive" value="${max}"/>
    </bean>
    <!--
        DruidDataSource myDataSource = new DruidDataSource();
        myDataSource.setUrl();
        myDataSource.setUsername();
        myDataSource.setPassword();
        myDataSource.init();
    -->

    <!--声明SqlSessionFactoryBean,创建SqlSessionFactory对象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--数据源-->
        <property name="dataSource" ref="myDataSource" />
        <!--指定mybatis的主配置文件-->
        <property name="configLocation" value="mybatis-config.xml" />
    </bean>

    <!--声明MyBatis的扫描器,创建Dao接口的实现类对象-->
    <bean  class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象,能获取SqlSession-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--指定Dao接口的包名,框架会把这个包中的所有接口一次创建出Dao对象-->
        <property name="basePackage" value="com.yu.dao" />
    </bean>
    <!--
        从spring中获取SqlSessionFacotory,因为spring是一个容器(Map)
        SqlSessionFactory factory  = map.get("sqlSessionFactory");
        SqlSession session = factory.openSession();

        for(接口:com.bjpowernode.dao)
        {
            Dao对象 =  session.getMapper(接口)
            //把创建好的对象放入到spring容器中
            spring的Map.put( 接口名的首字母小写, Dao对象 )

        }

    -->
    <!--声明Service-->
    <bean id="studentService" class="com.yu.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao" />
    </bean>

</beans>

jdbc.properties:

url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
user=root
password=?
max=20

8、测试

public class TestSpring {

    @Test
    public void test1(){

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        StudentService studentService = (StudentService) context.getBean("studentService");
        List<Student> studentList = studentService.queryStudent();
        System.out.println(studentList);
    }

    @Test
    public void test2(){

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        StudentService studentService = (StudentService) context.getBean("studentService");
        Student student = new Student(9, 20211, "罗十一", 202);
        int result = studentService.addStudent(student);
        /*spring整合mybatis默认提交事务*/
        System.out.println(result);
    }
}

test1:
在这里插入图片描述
test2:
在这里插入图片描述
在这里插入图片描述
spring 整合 mybatis 默认提交事务。

总结

通过以上的说明,我们需要让spring创建以下对象
1.独立的连接池类的对象, 使用阿里的druid连接池
2.SqlSessionFactory对象
3.创建出dao对象
需要学习就是上面三个对象的创建语法,使用xml的bean标签。

连接池

待完善。。。

多个连接 Connection 对象的集合,List<Connection> connlist,connList 就是连接池。
1、通常使用Connection访问数据库

Connection conn =DriverManger.getConnection(url,username,password);
Statemenet stmt = conn.createStatement(sql);
stmt.executeQuery();
conn.close();

2、使用连接池
在程序启动的时候,先创建一些 Connection

Connection c1 = ...
Connection c2 = ...
Connection c3 = ...
List<Connection>  connlist = new ArrayLits();
connList.add(c1);
connList.add(c2);
connList.add(c3);

Connection conn = connList.get(0);
Statemenet stmt = conn.createStatement(sql);
stmt.executeQuery();
把使用过的connection放回到连接池
connList.add(conn);

Connection conn1 = connList.get(1);
Statemenet stmt = conn1.createStatement(sql);
stmt.executeQuery();
把使用过的connection放回到连接池
connList.add(conn1);

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的配置文件中使用声明就可以了
例如,你要使用mybatis访问数据库,你应该在xml配置文件中
<bean id=“xxx" class="...DataSourceTransactionManager">

7、你的业务方法需要什么样的事务,说明需要事务的类型。
说明方法需要的事务:
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

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

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

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

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

使用@Transactional的步骤:
1.需要声明事务管理器对象

2.开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务。
spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。
spring给业务方法加入事务:
在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知

@Around("你要增加的事务功能的业务方法名称")
Object myAround(){
开启事务,spring给你开启
try{
buy(1001,10);
spring的事务管理器.commit();
}catch(Exception e){
spring的事务管理器.rollback();
}

}

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

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

实现步骤: 都是在xml配置文件中实现。
1)要使用的是aspectj框架,需要加入依赖

org.springframework
spring-aspects
5.2.5.RELEASE

2)声明事务管理器对象

  1. 声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)

  2. 配置aop:指定哪些哪类要创建代理。

代码示例

配置 xml:

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

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

在选择 annotation-driven 时选择下面这个:
在这里插入图片描述
在公共业务方法上面加上注解:
在这里插入图片描述

@Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.DEFAULT,
        readOnly = false,
        /*rollbackFor 表示发生指定的异常一定回滚*/
        /*
            * 处理逻辑是:
            *   1、spring 会首先检查方法抛出的异常是不是在 rollbackFor 的属性值中,
            *   如果异常在 rollbackFor 列表中,不管是什么类型的异常,一定回滚。
            *   2、如果抛出的异常不在 rollbackFor 列表中,
            *   spring 会判断异常是不是 RuntimeException,如果是一定回滚。
            * */
        rollbackFor = {
                NullPointerException.class, 
                ArrayIndexOutOfBoundsException.class
                /*...*/
        }
)

可以直接使用 @Transactional,不用添加属性。使用的是事务控制的默认值。默认的传播行为是 REQUIRED;默认的隔离级别 DEFAULT;默认抛出运行时异常,回滚事务。
在这里插入图片描述

================================================================================
第六章: 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);

posted @ 2022-05-17 21:46  YU_UY  阅读(29)  评论(0编辑  收藏  举报