Spring

Spring

Spring概述

Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。

Spring 是分层的 JavaSE/EE 应用 full-statck 轻量级开源框架,以 IOC(Inverse Of Control控制反 转) 和 AOP(Aspcet Oriented Programming 面向切面编程) 为内核,提供了表现层 SpringMVC 和持久层 Spring JDBC 以及业务层事物管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 JavaEE 企业应用开源框架。

Spring 全家桶: Spring , Springmvc , Spring boot , Spring cloud 等等。

spring官网

下面主要学习pring Framework

spring体系结构

xYAKXQ.png

spring作用

1、方便解耦简化开发

spirng的IOC容器,将对象之间的依赖关系由spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

2、AOP编程的支持

spring通过AOP功能,方便进行面向切片编程。很多不方便不容易用OOP(面向对象编程)通过AOP轻松实现。

3、声明式事务的支持

可以将我们从单调烦闷的事物管理代码中解脱出来,通过声明式方式(配置)灵活的进行事务的管理,提高开发效率和质量。

4、方便程序的测试

可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

5、方便集成各种框架

Spring 可以降低各种框架的使用难度,提供了对各种优秀框架( Struts 、 Hibernate 、 Hessian 、 Quartz 等)的直接支持。

7、降低 JavaEE API 的使用难度

Spring 对 JavaEE API (如 JDBC 、 JavaMail 、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。

spring在SSM框架中的地位

xYEigU.png

表现层spirngMVC负责接受请求,控制业务跳转,利用mybatis框架对持久层作为支持,spring作管理,管理springMVC和mybatis。

三层架构:

表现层(controller)、业务逻辑层(service)、持久层(数据访问层)dao。

  • 表现层:处理用户的请求,以及数据的返回。
  • 业务逻辑层:主要是对数据层的操作,对数据层的操作进行封装。
  • 持久层:对数据库进行操作

三层关系以上图所示。

MVC模式:

模型(Model)、视图(View)和控制器(controller)。

  • 模型(Model):用于封装应用程序业务逻辑相关的数据以及对数据的处理方法。
  • 视图(View):呈现给用户的部分,是用户和程序的接口。
  • 控制器(controller):处理用户请求,调用模型和视图。

MVC和三层架构的关系

xYE5MF.png

总结:

spring使用IOC 降低业务对象之间耦合度。 IOC 使得主业务在相互调用过程
中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring 容器统一管理,自动“注入”,注入即赋值。 而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混 杂”到主业务逻辑中了,而是由Spring 容器统一完成“织入”。

Spring 核心技术就是 IOC , DI , AOP 。能实现模块之间,类之间的解耦合。

IOC控制反转

控制反转:将对象的创建、赋值、管理的所有工作都交给代码之处的容器去实现,将对象的创建交给spring容器管理,将对象的初始化交给 Spring 来处理,将对象与对象之间的关系交给Spring容器管理。

将对对象的依赖完全解除,可以将对象通过配置的方式( properties , xml , 注解)告诉 Spring 容器你需要创建什么对象,并且对象的赋值也交给 Spring 容器,对象和对象之间的依赖关系也交给 Spring 容器,从而解耦,可以在不修改源代码的基础上创建,赋值,管理对象。

spring入门案例

1、导依赖

导spring-context依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.18</version>
</dependency>

2、创建spring配置文件spirng-config.xml

选择创建xml,创建SpringConfig

xYeigf.png

注:这个选项要导入spring-context依赖才有的,如果导了依旧没有可以先刷新pom.xml文件。

里面文件内容:

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

</beans>

配置文件解读:

  1. spring-beans.xsd是spring配置文件约束文件。
  2. spring的核心配置文件的根标签是
  3. 标签用于配置多个
  4. spring核心配置文件用来配置对象

3、创建UserService类以测试类

xYmYy8.png

4、在spring核心配置文件配置bean

<bean id="userServiec" class="com.lwg.serviec.impl.UserService"/>

bean标签:声明bean,用来告诉spring容器要创建哪个类的对象。

常见属性:

  • id:对象的自定义名称,spring通过这个名称查找对象。
  • class:需要创建对象所对应的类的全限定类名(这里不可以是使用接口,spring底层创建对象是通过反射创建的,必须使用类)。

一个 bean 标签声明一个对象,也就是说如果声明了多个 bean 标签,那么相当于向 Spring 容器中注册了多个对象。

底层分析:

  1. Spring 通过解析 beans.xml 配置文件。
  2. 读取 bean 标签的 class 属性值,获取到类的全限定名称,通过反射创建对象。
  3. 最后将对象保存到 Spring 容器中, 这里的 Spring 容器本质就是一个 map ,键是 id 的值,值是反射创
    建的对象。

5、创建测试类,通过spring容器获取对象

@Test
public void helloWorldTest(){
    //1、指定配置文件名字
    String configPath="spring-config.xml";
    //2、获取spirng容器对象
    ApplicationContext ctx=new ClassPathXmlApplicationContext(configPath);
    //3、从容器获取指定对象
    UserService userServiec = (UserService) ctx.getBean("userServiec");
    //4、使用对象
    userServiec.helloWorld();
}

相关知识:

  • 这里的配置文件是相对类路径下的,也就是是 target 目录下面的 classes 目录下面,所以我们只需要把配置文件放在 resources 目录下即可。
  • ApplicationContext是spirng容器,但该类是接口,要通过实现类来创建对象。

ApplicationContext继承关系:

xYtvqA.png

获取Spring容器对象的几种方式

ClassPathXmlApplicationContext:该类是通过XML配置文件往spring容器中注册bean对象,需要传入xml文件路径,路径是相对是类路径下。

FileSystemXmlApplicationContext :该类是通过 XML 配置文件的方式往 Spring 容器中注册 bean对象,需要传入 XML 文件路径, XML 文件的路径是绝对路径,即磁盘位置。如:

String configPath="D:\\springProject\\springDemo_01\\src\\main\\resources\\spring-config.xml";
ApplicationContext ctx=new FileSystemXmlApplicationContext(configPath);
//3、从容器获取指定对象
UserService userServiec = (UserService) ctx.getBean("userServiec");
//4、使用对象
userServiec.helloWorld();

AnnotationConfigApplicationContext : 该类是通过注解配置的方式往 Spring 容器中注册 bean 对 象,需要传入 bean 对象所对应的 Class 对象。

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);

从容器中获取Bean对象的方式

1、通过id属性的值获取
UserService userServiec = (UserService) ctx.getBean("userServiec");
2、通过Class类型获取
UserServiceImpl userServiceImpl = ctx.getBean(UserServiceImpl.class);

注:当spring核心配置里面配置了两个或者更多的此类型的bean就不可以这种方式了,会报错。

<bean id="userService" class="com.lwg.service.impl.UserServiceImpl"/>
<bean id="userService1" class="com.lwg.service.impl.UserServiceImpl" />

这样在核心配置文件注册了两个相同类型的bean使用Class类型获取就会报错。

会抛出下面这异常:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lwg.service.impl.UserServiceImpl' available: expected single matching bean but found 2: userService,userService1
	at com.lwg.UserSerrviceTest.helloWorldTest(UserSerrviceTest.java:28)

使用Class类型获取bean只能用于在核心配置文件里只注册了一个这种类型的bean。

3、同时通过id和class类型获取
UserServiceImpl userService = ctx.getBean("userService", UserServiceImpl.class);

这种方式既可以避免强制类型转换,也可以防止容器中存在多个类型 bean 的情况,推荐使用这种方式。

4、通过name属性值获取
UserService userService = (UserService) ctx.getBean("service");

这种方式和通过id属性值获取是一样的,只不过id只可以设置一个值,而name可以设置多个值,中间用逗号分隔来。

spring容器对象装配时间

ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好,生命周期跟随着容器,在创建 Spring 的容器时,会创建配置文件中声明的所有对象,容器销毁对象也销毁了。

spring容器创建对象是通过反射调用无参构造方法创建的,如果要创建对象的所对应的类没有无参构造方法会报错。

spring容器相关API

//        获取容器创建时间
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(ctx.getStartupDate())));
//        获取容器里配置的bean的数量
        System.out.println(ctx.getBeanDefinitionCount());
//        获取容器里所有bean的名字
        System.out.println(Arrays.toString(ctx.getBeanDefinitionNames()));
//        获取相同类型的所有bean对象的名字
        System.out.println(Arrays.toString(ctx.getBeanNamesForType(UserServiceImpl.class)));

spring创建非自定义对象

1、在spirng核心配置文件spring-config.xml注册bean

当这个对象所对应的类有构造函数时,只需要知道对象所对应的类的全限定类名即可。

spirng核心配置文件中配置bean:

<!--    配置非自定义的bean-->
    <bean id="date" class="java.util.Date" />

测试类:

@Test
public void dateTest(){
    ApplicationContext ctx=new ClassPathXmlApplicationContext("spring-config.xml");
    Date date = ctx.getBean("date", Date.class);
    System.out.println("date = " + date);
}
2、实例工厂构建对象

有时候我们创建的对象可能来自于某个 Jar 包中的某个类中方法的返回值,而方法里面创建对象的过程相对复杂,我们也不可能修改Jar中的源码来获取对象,这个时候我们可以考虑使用工厂方法创建对象。

工厂类代码:

public class InstanceFactory {
    public UserService getUserService(){
        System.out.println("实例工厂生效了");
        return new UserServiceImpl();
    }
}

spring核心配置文件

<!--    1、注册工厂bean-->
    <bean id="instanceFactory" class="com.lwg.util.InstanceFactory"/>
<!--    注册要用通过工厂创建的对象bean-->
    <bean id="userServiceF" factory-bean="instanceFactory" factory-method="getUserService"/>

属性解释:

  • factory-bean:实例工厂注册bean的id
  • factory-method:实例工厂返回对象的方法名

测试

@Test
public void instanceFactoryTest(){
    ApplicationContext ctx=new ClassPathXmlApplicationContext("spring-config.xml");
    UserService userService = ctx.getBean("userServiceF", UserService.class);
    System.out.println("userService = " + userService);
}

总结:实例工厂创建对象的方式本质其实就是通过反射创建 instatnceFactory 对象,然后读取factory-method 属性值,再通过反射调用 getUserService 方法,然后使用变量 userService 接收返回的对象即可。

3、静态工厂创建对象

当这个对象需要使用某个类的静态方法获取,这时就需要使用静态工厂创建对象。

例如:Calendar 的 getInstance 方法

静态工厂代码:

public class StaticFactory {
    public static UserService getUserSerice(){
        System.out.println("静态工厂生效");
        return new UserServiceImpl();
    }
}

spring核心配置文件

<!--        静态工厂    -->
    <bean id="userServiceS" class="com.lwg.util.StaticFactory" factory-method="getUserSerice" />

属性解释:

class:静态工厂类的全限定类名

factory-method:静态工厂类中返回对象的方法名

测试类

@Test
public void StaticFactoryTest(){
    ApplicationContext ctx=new ClassPathXmlApplicationContext("spring-config.xml");
    UserService userService = ctx.getBean("userServiceS", UserService.class);
    System.out.println("userService = " + userService);
}

总结:静态工厂方法创建对象的本质其实就是通过 class 属性值获取 StaticFactory 类的 Class 对象,然后通过方法名 getUserService 获取 Method 对象,然后通过反射调用静态方法返回结果,通过变量名 userService 接受即可。

bean的作用范围

使用 ClassPathXmlApplicationContext 创建对象默认是单例的,创建的方式也是立即加载的,立即加载适用单例模型。

可以通过bean标签的scope属性设置bean的作用范围。

scope属性取值:

singleton 、 prototype 、 request 、 session 、 application 、 global-session 。

  • singleton:单例模式,默认配置

spring核心配置文件:

<!--    单例模式-->
    <bean id="userServiceOne" class="com.lwg.service.impl.UserServiceImpl" scope="singleton"/>

测试类

@Test
public void scopeTest(){
    ApplicationContext ctx=new ClassPathXmlApplicationContext("spring-config.xml");
    UserServiceImpl userService1 = ctx.getBean("userServiceOne", UserServiceImpl.class);
    System.out.println("userService1 = " + userService1);
    UserServiceImpl userService2 = ctx.getBean("userServiceOne", UserServiceImpl.class);
    System.out.println("userService2 = " + userService2);
    System.out.println(userService1 == userService2);
}

运行结果

xtpdN8.png

获取的对象是同一个。

  • prototype:多例模式

配置如下

<bean id="userServiceAll" class="com.lwg.service.impl.UserServiceImpl" scope="prototype" />

测试类

@Test
public void scopeTest2(){
    ApplicationContext ctx=new ClassPathXmlApplicationContext("spring-config.xml");
    UserServiceImpl userServiceAll1 = ctx.getBean("userServiceAll", UserServiceImpl.class);
    System.out.println("userServiceAll1 = " + userServiceAll1);
    UserServiceImpl userServiceAll2 = ctx.getBean("userServiceAll", UserServiceImpl.class);
    System.out.println("userServiceAll2 = " + userServiceAll2);
    System.out.println(userServiceAll1 == userServiceAll2);
}

运行结果:

xt9kUf.png

每次的对象都不相同

  • request : 作用于Web应用的请求范围。

该属性值的对象,能够作用在一次请求中,一次请求包括浏览器请求开始到服务器响应结束的全过程。

  • session :作用于Web应用的会话范围。

该属性值的对象作用在一次会话中,一次会话包括打开浏览器访问服务器到浏览器和服务器断开连接(即关闭浏览器)的全过程。

  • global-session :作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session 。

在spring中主要是使用单例和多例。

bean的生命周期

单例对象: 随着 Spring 容器的创建而创建,随着 Spring 容器的销毁而销毁,生命周期和 Spring 容器相同。

bean设置生命周期方法:

UserServiceImpl代码:

@Override
public void init() {
    System.out.println("创建bean");
}

@Override
public void destroy() {
    System.out.println("开始销毁bean");
}

spring核心配置文件

<bean id="userService"  class="com.lwg.service.impl.UserServiceImpl" init-method="init" destroy-method="destroy"/>

属性解释:

  • init-method:绑定创建bean执行的方法名
  • destroy-method:绑定销毁bean执行的方法名

单例模式的bean在容器创建时,先执行构造方法创建对象,然后再执行配置文件配置的 init-method 所指定的方法; 当容器销毁之前会先调用配置文件配置的destroy-method 属性所指定的方法。

总结:

单例对象生命周期是随着 Spring 容器的创建而创建,随着 Spring 容器的销毁而销毁,生命周期和 Spring 容器相同。

单例模式bean设置懒加载

当我们不想对象在容器创建时就创建了,想在使用到他才创建,这时可以在设置bean标签的的 lazy-init=“true” 设置懒加载。

总结:

多例对象在容器创建的时候并不会立刻创建,而是当我们使用对象时,由Spring 容器为我们创建,但是当 Spring 容器销毁时,并不会销毁对象,而是当对象长时间不用,并且没有更多对象引用时,由 Java 的垃圾回收器回收。

依赖注入(DI)

依赖注入就是让 bean 与 bean 之间以配置文件组织在一起,而不是以硬编码的方式耦合在一起。

依赖注入 (Dependency Injection) 和控制反转 (Inversion of Control) 是同一个概念。具体含义是:当某个角色(可能是一个 Java 实例,调用者)需要另一个角色(另一个 Java 实例,被调用者)的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在 Spring 里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者实例的工作通常由 Spring 容器来完成,然后注入调用者,因此也称为依赖注入。

依赖:

简而言之就是 classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即classA 对 classB 有依赖关系。代码解释:

class A { // A类依赖B类 
    B b; 
    public void method() {
        b.show(); } 
}
class B { 
    public void show() {} 
}

注入:

注入其实就是赋值的意思。

依赖注入 DI 是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而依赖于外部容器,由外部容器创建后传递给程序。

依赖注入按照实现方式分为以下两种:

  1. 在 Spring 的配置文件中,使用标签和属性完成,叫做基于 XML 的 DI 实现。
  2. 使用 Spring 注解的方式完成属性赋值,叫做基于注解的 DI 实现。

依赖注入按照注入的数据类型分类分为以下三种:

  1. 简单类型注入:基本数据类型和特殊类型 String 类型,
  2. 简单引用数据类型,例如 Date 、 User 。
  3. 复杂容器类型,例如数组、集合等等

基于XML实现的set方法注入

简单类型注入

注入基本类型(基本类型的包装类)和String类

set 注入(设置注入): Spring 调用类的 set 的方法,通过 set 方法实现属性的赋值,开发中常用。

 <bean id="自定义Bean名字" class="bean所对应的类的全限定类名" >
        <property name="bean对应类的属性名" value=""/>
     .....
    </bean>

注:

property标签里的value值无论类属性的类型是什么类型,这里都写字符串。

一个property标签给一个属性赋值。

Student类代码:

package com.lwg.domain;

/**
 * @author lwg
 * @title
 * @create 2022/10/10 17:04
 */
public class Student {
    private Integer sNo;
    private String name;
    private Integer age;

    public Student() {
    }

    public Integer getsNo() {
        return sNo;
    }

    public void setsNo(Integer sNo) {
        this.sNo = sNo;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

spring核心配置:

<bean id="student" class="com.lwg.domain.Student" >
    <property name="age" value="19"/>
    <property name="name" value="劣露露" />
    <property name="sNo" value="201911333" />
</bean>

测试类

@Test
public void diTest1(){
    Student student = ctx.getBean("student", Student.class);
    System.out.println("student = " + student);
}

运行结果:

xtV19x.png

注:

set注入底层是通过反射调用setXXXX方法赋值的,所有要注入的bean对应的类必须要提供要赋值的属性的set方法,没有set方法会注入失败。

如果对应类中有set方法,但没有这个成员变量,也不会报错,也是可以注入的。

student类增加set方法

public void setShool(String shool){
    System.out.println("shool = " + shool);
}

bean添加配置

<bean id="student" class="com.lwg.domain.Student" >
    <property name="age" value="19"/>
    <property name="name" value="劣露露" />
    <property name="sNo" value="201911333" />
    <property name="shool" value="新增加的set" />
</bean>

运行结果

xtZJGq.png

引用类型注入
<bean id="创建对象的名称" class="对象所对应的全限定类名"> <property name="属性名字" ref="bean标签id属性的值(对象的名称)"/> </bean>

创建一个学校类、学生类

学校类代码:

package com.lwg.domain;

/**
 * @author lwg
 * @title
 * @create 2022/10/10 17:26
 */
public class School {
    private Integer no;
    private String name;

    public School() {
    }

    public Integer getNo() {
        return no;
    }

    public void setNo(Integer no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

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

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

学生类代码

package com.lwg.domain;

/**
 * @author lwg
 * @title
 * @create 2022/10/10 17:04
 */
public class Student {
    private Integer sNo;
    private String name;
    private Integer age;
    private School school;

    public Student() {
    }

    public Integer getsNo() {
        return sNo;
    }

    public void setsNo(Integer sNo) {
        this.sNo = sNo;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public School getSchool() {
        return school;
    }

    public void setSchool(School school) {
        this.school = school;
    }

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

spirng核心配置

<bean id="school" class="com.lwg.domain.School">
    <property name="name" value="清华大学" />
    <property name="no" value="11" />
</bean>
<bean id="student" class="com.lwg.domain.Student" >
        <property name="age" value="19"/>
        <property name="name" value="劣露露" />
        <property name="sNo" value="201911333" />
        <property name="school" ref="school"/>
    </bean>

测试代码:

@Test
public void diTest1(){
    Student student = ctx.getBean("student", Student.class);
    System.out.println("student = " + student);
}

运行结果:

xteFyT.png

基于XML实现的构造方法注入

构造方法注入: Spring 调用类的有参构造方法,创建对象,通过构造方法完成属性的赋值。

<bean id="创建对象的名称" class="对象所对应的全限定类名"> <constructor-arg name="构造方法形参名称" value="构造方法实参值"/> <constructor-arg name="构造方法形参名称" value="构造方法实参值"/> <constructor-arg name="构造方法形参名称" ref="构造方法实参值(bean的id)"/> 
</bean>

构造方法注入使用constructor-arg标签

一个constructor-arg标签只能注入构造方法的一个参数

constructor-arg标签相关属性:

  • name:用于指定构造函数中指定名称的参数赋值
  • index : 用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引从0开始
  • type : 用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或者某些参数的类型
  • value : 构造方法的形参类型是简单类型,采用 value 注入。
  • ref:构造方法的形参类型是引用类型,采用ref注入

Student类添加全参数的构造方法

public Student(Integer sNo, String name, Integer age, School school) {
    this.sNo = sNo;
    this.name = name;
    this.age = age;
  

spring核心配置

  <bean id="school" class="com.lwg.domain.School">
        <property name="name" value="清华大学" />
        <property name="no" value="11" />
    </bean>

<!--    使用name属性注入,通过构造函数形参名注入-->
    <bean id="student1" class="com.lwg.domain.Student">
        <constructor-arg name="age" value="34"/>
        <constructor-arg name="name" value="张三" />
        <constructor-arg name="sNo" value="201913150" />
        <constructor-arg name="school" ref="school" />
    </bean>
<!--    使用index属性注入,通过构造函数形参顺序注入-->
    <bean id="student2" class="com.lwg.domain.Student">
        <constructor-arg index="0" value="202201903"/>
        <constructor-arg index="1" value="李四"/>
        <constructor-arg index="2" value="30" />
        <constructor-arg index="3" ref="school"/>
    </bean>
<!--    使用type属性,根据构造函数形参类型注入-->
    <bean id="student3" class="com.lwg.domain.Student">
        <constructor-arg type="java.lang.Integer"  value="20320877"/>
        <constructor-arg type="java.lang.String" value="一旦是" />
        <constructor-arg type="java.lang.Integer" value="37" />
        <constructor-arg type="com.lwg.domain.School" ref="school" />
    </bean>
<!--    省略name、index、type,要和构造函数形参顺序一样-->
    <bean id="student4" class="com.lwg.domain.Student">
        <constructor-arg value="2034758"/>
        <constructor-arg value="韩鸡" />
        <constructor-arg value="89" />
        <constructor-arg ref="school"/>
    </bean>

基于XML实现复杂容器类型注入

容器类型:数组、集合、列表等。

User类

package com.lwg.domain;

import java.util.*;

/**
 * @author lwg
 * @title
 * @create 2022/10/10 20:21
 */
public class User {
    private String[] address;
    private List<Dao> daoList;
    private Set<String> hobbset;
    private Map<String,Dao> daoMap;
    private Properties process;

    public User() {
    }

    public String[] getAddress() {
        return address;
    }

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

    public List<Dao> getDaoList() {
        return daoList;
    }

    public void setDaoList(List<Dao> daoList) {
        this.daoList = daoList;
    }

    public Set<String> getHobbset() {
        return hobbset;
    }

    public void setHobbset(Set<String> hobbset) {
        this.hobbset = hobbset;
    }

    public Map<String, Dao> getDaoMap() {
        return daoMap;
    }

    public void setDaoMap(Map<String, Dao> daoMap) {
        this.daoMap = daoMap;
    }

    public Properties getProcess() {
        return process;
    }

    public void setProcess(Properties process) {
        this.process = process;
    }

    @Override
    public String toString() {
        return "User{" +
                "address=" + Arrays.toString(address) +
                ", daoList=" + daoList +
                ", hobbset=" + hobbset +
                ", daoMap=" + daoMap +
                ", process=" + process +
                '}';
    }
}

Dao类

package com.lwg.domain;

/**
 * @author lwg
 * @title
 * @create 2022/10/10 20:22
 */
public class Dao {
    private String daoName;
    private Integer daoAge;

    public Dao() {
    }

    public String getDaoName() {
        return daoName;
    }

    public void setDaoName(String daoName) {
        this.daoName = daoName;
    }

    public Integer getDaoAge() {
        return daoAge;
    }

    public void setDaoAge(Integer daoAge) {
        this.daoAge = daoAge;
    }

    @Override
    public String toString() {
        return "Dao{" +
                "daoName='" + daoName + '\'' +
                ", daoAge=" + daoAge +
                '}';
    }
}

spring核心配置

<bean id="dao1" class="com.lwg.domain.Dao">
        <property name="daoName" value="金毛"/>
        <property name="daoAge" value="12"/>
    </bean>
    <bean id="dao2" class="com.lwg.domain.Dao">
        <property name="daoName" value="泰迪"/>
        <property name="daoAge" value="2"/>
    </bean>
<!--    复杂容器类型注入-->
    <bean id="user" class="com.lwg.domain.User">
<!--        注入数组-->
        <property name="address">
            <array>
                <value>北京北路</value>
                <value>北京东路</value>
                <value>北京西路</value>
                <value>北京南路</value>
                <value>后路</value>
            </array>
        </property>
<!--        注入列表-->
        <property name="daoList">
            <list>
                <ref bean="dao1" />
                <ref bean="dao2" />
                <bean id="dao3" class="com.lwg.domain.Dao">
                    <property name="daoAge" value="10"/>
                    <property name="daoName" value="牛街"/>
                </bean>
                <bean id="dao4" class="com.lwg.domain.Dao">
                    <property name="daoName" value="平关"/>
                    <property name="daoAge" value="9" />
                </bean>
            </list>
        </property>
<!--        注入set集合-->
        <property name="hobbset">
            <set>
                <value>打球</value>
                <value>跑步</value>
                <value>游泳</value>
            </set>
        </property>
<!--        注入map-->
<!--        当键是基本类型使用key属性,如果是引用类型使用key-ref
        当值是基本类型使用value属性,如果是引用类型使用value-ref-->
        <property name="daoMap">
            <map>
                <entry key="小鸡"  value-ref="dao1"/>
                <entry key="大鸡" value-ref="dao2"/>
            </map>
        </property>
       <!--        注入Properties-->
        <!--  键写在key属性里,值写标签内-->
        <property name="process">
            <props>
                <prop key="userName">张三</prop>
                <prop key="password">123456</prop>
                <prop key="nick">惠惠一</prop>
            </props>
        </property>
    </bean>
  • 其中 property 标签下常用的子标签有 array 、 list 、 set 、 map 、 props
  • 用于单列集合类型的标签有 array 、 list 、 set ,它们结构相同,标签可以互换,但是鉴于程序的可读性,建议使用对应类型的对应标签。
  • 用于双列集合类型的标签有 map 、 props ,它们结构相同,当键值是String类型的时候,标签可以互换,但是键值是其它类型的时候不可以互换,但是鉴于程序的可读性,建议使用对应类型的对应标签。
单列集合注入

1、如果值是基本类型或者是String类型,使用value标签

<!--        注入数组-->
        <property name="address">
            <array>
                <value>北京北路</value>
                <value>北京东路</value>
                <value>北京西路</value>
                <value>北京南路</value>
                <value>后路</value>
            </array>
        </property>
        <!--        注入set集合-->
        <property name="hobbset">
            <set>
                <value>打球</value>
                <value>跑步</value>
                <value>游泳</value>
            </set>
        </property>

2、如果值是引用类型嵌套使用bean标签,bean标签的ref属性必须是标签外配置的bean的id。

  <bean id="dao1" class="com.lwg.domain.Dao">
        <property name="daoName" value="金毛"/>
        <property name="daoAge" value="12"/>
    </bean>
    <bean id="dao2" class="com.lwg.domain.Dao">
        <property name="daoName" value="泰迪"/>
        <property name="daoAge" value="2"/>
    </bean>
<!--        注入列表-->
        <property name="daoList">
            <list>
                <ref bean="dao1" />
                <ref bean="dao2" />
                <bean id="dao3" class="com.lwg.domain.Dao">
                    <property name="daoAge" value="10"/>
                    <property name="daoName" value="牛街"/>
                </bean>
                <bean id="dao4" class="com.lwg.domain.Dao">
                    <property name="daoName" value="平关"/>
                    <property name="daoAge" value="9" />
                </bean>
            </list>
        </property>
双列集合注入

1、取值标签使用entry标签

2、如果键是基本类型或者String类型使用key属性设置,如果值是基本类型或者String类型使用value属性设置。

<property name="keyValue"> 
    <map>
        <entry key="username" value="amdin"/> 
        <entry key="password" value="123456"/> 
        <entry key="nickName">老司机</entry> 
    </map> 
</property>

3、如果键是引用类型使用key-ref属性设置,如果值是引用类型使用value-ref属性设置。

<property name="dogMap"> 
 <map>
    <entry key="1001" value-ref="myDog"/> 
    <entry key="1002"> <ref bean="myDog2"/> </entry> 
    <entry key="1003"> 
        <bean id="dog3" class="com.xyr.bean.Dog"/> 
    </entry> 
 </map> 
</property> 
<property name="userDogMap"> 
    <map>
        <entry key-ref="user1" value-ref="myDog"/> 
        <entry key-ref="user2" value-ref="myDog2"/> 
    </map> 
</property

4、如果是 props 标签,那么属性只有 key ,值必须写在标签内部,表示字符串类型。

<!--        注入Properties-->
        <property name="process">
            <props>
                <prop key="userName" >张三</prop>
                <prop key="password">123456</prop>
                <prop key="nick">惠惠一</prop>
            </props>
        </property>

基于XML实现引用类型自动注入

如果一个类中依赖多个引用类型的成员变量,那么配置文件会比较繁琐。

引用类型属性的引入,可不在配置文件中显示的引入,可以通过配置autowire 属
性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。

自动注入分了两种

  • byName:按名称自动注入
  • byType:按类型自动注入
byName 按名称自动注入

需要注入引用类型的那个属性名和spring容器注册的bean的id一样且数据类型是一样的,这样spirng容器才能自动注入。

<bean id="对象名称" class="对象所对应类的全限定名称" autowire="byName"> 
    简单类型属性赋值 
    引用类型属性赋值可以不配置 
</bean>

编写对应的java类

School类:

package com.lwg.domain;

/**
 * @author lwg
 * @title
 * @create 2022/10/11 15:25
 */
public class School {
    private String schoolName;
    private String address;

    public School() {
    }

    public String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }

    public String getAddress() {
        return address;
    }

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

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

Student类

package com.lwg.domain;

/**
 * @author lwg
 * @title
 * @create 2022/10/11 15:26
 */
public class Student {
    private String name;
    private Integer age;
    private School school;

    public Student() {
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public School getSchool() {
        return school;
    }

    public void setSchool(School school) {
        this.school = school;
    }

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

编写spirng核心配置文件

    <bean id="school" class="com.lwg.domain.School">
        <property name="schoolName" value="清华大学"/>
        <property name="address" value="北京"/>
    </bean>
<!--    通过名称自动注入 设置autowire="byName" 这里为school属性自动注入,
        然后上面school注册bean的id名称和student类设置的属性名要一致   -->
    <bean id="student" class="com.lwg.domain.Student" autowire="byName">
        <property name="name" value="张三"/>
        <property name="age" value="23" />
    </bean>

注:这里的student类的属性名要和school注册bean时的id一致才行

编写测试类

@Before
public void getCtx(){
    ctx=new ClassPathXmlApplicationContext(xmlPath);
}
@After
public void closeCtx(){
    ctx.close();
}
@Test
public void automaticTest(){
    Student student = ctx.getBean("student", Student.class);
    System.out.println("student = " + student);
}

运行流程:id为student的bean设置autowire="byName"就开启了自动注入,就会帮student这个bean自动注入引用类型,不用手动注入,也就是自动注入School类型的school属性。spring就会在bean里面找id为school的bean,如果找到就注入,如果没找到就不注入,不会报错,只是对应的属性为null,也就是school属性为null。

原理图示:

xNoZjO.png

byType 按类型自动注入

Java类中引用类型的数据类型 (private School school) 和 Spring 容器中(配置文件) 的 class 属性是同源关系的,这样的bean才能按类型自动注入。

同源关系主要有以下三种情况:

  1. Java类中引用类型的数据类型和 bean 的 class 属性值是一样的。
  2. Java类中引用类型的数据类型和 bean 的 class 属性值是父子类关系的。
  3. Java类中引用类型的数据类型和 bean 的 class 属性值是接口和接口实现类的关系的

语法:

<bean id="对象名称" class="对象所对应类的全限定名称" autowire="byType"> 
简单类型属性赋值 
引用类型属性赋值可以不配置 
</bean>

java类School类和Student一样。

编写spirng核心配置

<bean id="school" class="com.lwg.domain.School">
    <property name="schoolName" value="清华大学"/>
    <property name="address" value="北京"/>
</bean>
<!--    通过类型自动注入-->
    <bean id="student1" class="com.lwg.domain.Student" autowire="byType">
        <property name="name" value="李四"/>
        <property name="age" value="67"/>
    </bean>

id为student1的bean设置了autowire=“byType”,就开启引用类型的按类型自动注入,spring就会扫描bean中是否有和属性同源关系的类的bean,有就注入,没有就不注入,也不会报错,只是对应的属性为null。

xNTRQf.png

注:

按类型自动注入,如果spring注册的bean中有两个或多个同源的bean不但不会自动注入,还会报错。

总结: 在 byType 方式的自动注入中,在 xml 配置文件中声明 bean 只能存在一个满足条件的,多余的都是错误的。

应用指定的多个spirng配置文件

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。

多个配置文件的优势

  1. 每个文件的大小比一个文件要小很多。效率高。
  2. 避免多人开发竞争带来的冲突。
  3. 提高了配置文件的可读性和可维护性

多文件的分配方式主要有以下两种:

  1. 按照功能模块: 一个模块一个配置文件,例如你的电商项目有用户管理模块,商品管理模块,订单
    管理模块,那么用户管理模块对应一个配置文件,商品管理也对应一个配置文件,订单模块同样对
    应一个配置文件。
  2. 按照三层架构中的分工:数据库相关的配置对应一个配置文件,事务管理对应一个配置文件,业务
    层对应一个配置文件,控制层对应一个配置文件。

将上面自动注入的程序更改一下

xN74c6.png

创建spring-student.xml、spring-school.xml两个配置文件作为子配置文件,

spring-config.xml作为主配置文件,一般不作bean配置,只做包含子配置文件。

spring-school.xml代码

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


    <bean id="school" class="com.lwg.domain.School">
        <property name="schoolName" value="清华大学"/>
        <property name="address" value="北京"/>
    </bean>

</beans>

spring-student.xml代码

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

    <!--    通过名称自动注入 设置autowire="byName" 这里为school属性自动注入,
        然后上面school注册bean的id名称和student类设置的属性名要一致   -->
    <bean id="student" class="com.lwg.domain.Student" autowire="byName">
        <property name="name" value="张三"/>
        <property name="age" value="23" />
    </bean>
    <!--    通过类型自动注入-->
    <bean id="student1" class="com.lwg.domain.Student" autowire="byType">
        <property name="name" value="李四"/>
        <property name="age" value="67"/>
    </bean>

</beans>

spring-config.xml代码

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

<!--    主配置文件加载其他配置文件,使用import
        resource属性写文件路径
        classpath:表示类路径
        这里的可用通配符*
-->
    <import resource="classpath:spring-school.xml"/>
    <import resource="classpath:spring-student.xml" />
</beans>

包含其他配置文件使用import标签,resource属性表示配置文件路径。

classpath:表示类路径下

可使用通配符*表示所有

<!--    使用通配符*-->
    <import resource="classpath:spring-*.xml" />

注:在这里,我们需要将我们的主配置文件更改一个名,因为spring-*.xml也包含了主配置,这样子会报错的,所有我只需要改主配置名字不带spring-即可。

基于注解的IOC

注解配置和 XML 配置要实现的功能是一样的,都是要降低程序的耦合,只不过配置的形式不一样而已。

school类代码

package com.lwg.domain;

/**
 * @author lwg
 * @title
 * @create 2022/10/11 15:25
 */
public class School {
    private String schoolName;
    private String address;

    public School() {
    }

    public String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }

    public String getAddress() {
        return address;
    }

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

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

基于XML配置的IOC

<bean id="school" class="com.lwg.domain.School">
    <property name="schoolName" value="清华大学"/>
    <property name="address" value="北京"/>
</bean>

注解配置的方式和 XML 是一样的,那么我们根据用途将注解配置主要分为以下四类:

​ 1. 用于创建对象的:他们的作用就和 XML 配置文件中编写一个标签实现 的功能是一样的。

  1. 用于注入数据的:他们的作用就和在 XML 配置文件中的 bean 标签内部写标签的作用是一样的。
  2. 用于改变作用范围的: 他们的作用就和 bean 标签中使用 scope 属性实现的功能是一样的。
  3. 生命周期相关的:他们的作用就和 bean 标签中使用 init-method 和 destroy-method 的作用是一样的。

配置bean的注解

XML配置:

<bean id="school" class="com.lwg.domain.School" />

而注解配置bean是在要注册成bean的类名上面使用注解@Component

value 属性:用于指定 bean 的 id 。当我们不写时,它的默认值是当前类名,且首字母小写。

xNqw9A.png

编写spirng核心配置文件(这不是纯注解,所有还需要配置文件)

<?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">
<!--    设置创建spirng容器扫描的包-->
    <context:component-scan base-package="com.lwg.domain"/>

</beans>

注:

需要添加结束xmlns:context=“http://www.springframework.org/schema/context”

IDEA会自动导入,其他开发工具需要手动导入。

设置创建spring容器扫描的包,这样才能添加扫描到注解然后将要注册的bean添加进去。

<context:component-scan base-package=“包名的”/>

运行原理:

首先 Spring 加载核心配置文件,然后解析标签中 base-package 属性的值,然后 Spring 会自动去扫描 设置的包下所有的类和子包下所有的类,扫描时,会判断类上是否存在@Component注解,如果存在会通过反射创建对象,如果@Component注解设置了value值,就以value值作为了变量名接受反射创建的对象,如果没有设置值就以类名首字母转小写作为变量名接收刚才反射创建的对象。

扫描多个包的写法:

<!--        方式一:使用多次组件扫描器,指定不同的包-->
        <context:component-scan base-package="com.lwg.domain"/>
        <cpntext:component-scan base-package="com.lwg.dao"/>
<!--        方式二:使用;作为分隔符,写个包名-->
        <context:component-scan base-package="com.lwg.dao;com.lwg.domain"/>
<!--        方式三:将包名扩大,写父包,同时包括要扫描的多个包-->
        <context:component-scan base-package="com.lwg"/>

除了可以用**@Component**注解来配置bean之外,还可以用

@Controller 一般用于表现层
@Service 一般用于业务层
@Repository 一般用于数据层

这三个注解是 Spring 框架为我们提供在三层上使用的注解,让我们针对三层对象更加清晰,可以理解为是语义化注解,底层实现原理也比较简单,先实现@Component注解的功能,其他三个注解的功能在实现的时候通过 super 访问父类 @Component 注解的功能就好了,虽说功能一样,但是 Spring 在处理这几个注解的时候还是有一些细节的。

在三层架构中一般表现层不交给spirng容器管理,所有如果在配置设置扫描的包是com.lwg这样就包含了了表现层。所有可以use-default-filters设置属性和搭配着<context:exclude-filter/>或和 <context:include-filter/>。

use-default-filters:表示扫描是否指定配置的包

<context:exclude-filter/>:表示排除哪些注解标识的类不扫描,搭配着use-default-filters=“true”,扫描包下的所有类,但排除用哪些注解标识的类。

<context:include-filter/>:表示包含哪些注解标识的类,搭配着搭配着use-default-filters=“false”,不扫描包下的所有类,但要扫描用这些注解标识的类。

<context:component-scan base-package="com.lwg" use-default-filters="true">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<context:component-scan base-package="com.lwg" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

bean作用范围的注解

在XML配置中设置scope属性来设置bean的作用范围,在注解配置中使用@Scope来设置bean的作用范围。

@Scope:设置bean的作用范围。

value 属性:取值有singleton 、 prototype 、 request 、 session 、 global-session,默认是singleton(单例)。作用和XML配置的一样。

bean生命周期的注解

在XML配置中使用init-method、destroy-method属性来设置生命周期方法,在注解中使用是 @PostConstruct 、 @PreDestroy 注解,在要指定的方法的方法上面设置注解。

@PostConstruct:设置对象创建后执行的方法

@PreDestroy :设置对象销毁之后执行的方法

@PostConstruct
public void into(){
    System.out.println("schoo对象创建");
}
@PreDestroy
public void destroy(){
    System.out.println("scho对象销毁");
}

基于注解的DI

简单类型注入的注解

给简单类型赋值使用@Value注解

注解定义的位置:

  1. 在属性定义的上面,这种定义不需要属性提供set方法

xUsLGQ.png

​ 2.定义在set方法上

xUyGQA.png

基于注解的引用类型自动注入

基于注解的引用类型的自动注入方式和 XML 一样,也是有两种:

  1. byType 根据类型的自动注入方式,需要用到 @Autowired 注解(掌握)。
  2. byName 根据名称的自动注入方式,需要用到 @Autowired 与@ Qualifier 注解(掌握)。
byType 根据类型自动注入

使用**@Autowired** 注解,在需要自动注入的引用类型的属性上面定义这个注解即可,spring容器就会根据类型自动为这个属性注入spirg容器中有定义的相同类型的bean。

@Autowired 定义位置:

  1. 定义在属性上

xUgii8.png

​ 2.定义在set方法上

xU22jJ.png

java类代码:

School类代码:

package com.lwg.domain;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.lang.annotation.Retention;

/**
 * @author lwg
 * @title
 * @create 2022/10/11 15:25
 */

@Repository
public class School {
    @Value("北京大学")
    private String schoolName;
    @Value("北京")
    private String address;

    public School() {
    }

    public String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }

    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    @PostConstruct
    public void into(){
        System.out.println("schoo对象创建");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("scho对象销毁");
    }

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

Student类代码

package com.lwg.domain;

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

/**
 * @author lwg
 * @title
 * @create 2022/10/11 15:26
 */

@Repository
public class Student {
    @Value("张三")
    private String name;
    @Value("34")
    private Integer age;
//    自动注入,根据类型自动注入引用类型
    @Autowired
    private School school;

    public Student() {
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public School getSchool() {
        return school;
    }

    public void setSchool(School school) {
        this.school = school;
    }

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

实现原理:spring容器会根据school对象的类型去spring容器找同源类型的bean,如果找到就注入,找不到则抛出异常。

总结:

  1. 只要容器中有一个(且唯一)的bean和要注入的类型属于同源关系,就可以注入,否则注入失败,抛出 NoSuchBeanDefinitionException 异常。
  2. 如果容器中有多个类型匹配成功时,会在类型一致的多个对象中继续根据变量名称去容器中匹配,如果变量名称匹配一致,就可以注入成功,如果匹配失败,则抛出NoUniqueBeanDefinitionException 异常。
byName 根据名称自动注入

根据名称自动注入使用到两个注解,@Autowired 注解和 @Qualifier(value=“bean的id”)。

xU4neJ.png

java类代码

School类代码

package com.lwg.domain;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.lang.annotation.Retention;

/**
 * @author lwg
 * @title
 * @create 2022/10/11 15:25
 */

@Repository("myshcool")
public class School {
    @Value("北京大学")
    private String schoolName;
    @Value("北京")
    private String address;

    public School() {
    }

    public String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }

    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    @PostConstruct
    public void into(){
        System.out.println("schoo对象创建");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("scho对象销毁");
    }

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

Student类代码

package com.lwg.domain;

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.Repository;

/**
 * @author lwg
 * @title
 * @create 2022/10/11 15:26
 */

@Repository
public class Student {
    @Value("张三")
    private String name;
    @Value("34")
    private Integer age;
//    自动注入,根据类型自动注入引用类型
//    @Autowired

//    自动注入,根据名称自动注入引用类型
    @Autowired
    @Qualifier("myshcool")
    private School school;

    public Student() {
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public School getSchool() {
        return school;
    }

//    @Autowired
    public void setSchool(School school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}
JDK注解自动注入

Spring 提供了对 jdk 中 @Resource 注解的支持。它属于 javax.annotation 包下的注解。

@Resource 注解既可以按名称匹配 Bean ,也可以按类型匹配 Bean ,默认是按名称注入。

使用该注解,要求 JDK 必须是 6 及以上版本, @Resource 可在属性上,也可在 set 方法上。

当使用@Resource(name=“bean的id”)首先按名称自动注入,找到唯一一个bean就注入,如果没有找到id为设置的bean会再按类型查找自动注入,如果找到同源的bean(唯一一个)就注入,找到多个就抛出异常,如果最后都没找到也会抛出异常。

xUIfzV.png

当使用@Resource()就会首先按类型查找,找容器中是否有唯一的同源的bean,如果找到就注入,如果找到多个就按名称查找,查找这些是否有和属性名一致的bean,如果有一个就注入,如果找到多个就报错,最后,都没有找到就抛出异常。

总结

xU55Ed.png

纯注解配置

实现一个基本的查询数据库到业务逻辑层(XML配置)

1、导入依赖

<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>5.3.2</version>
    </dependency>
<!--    mysql依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>
    <!-- 导入apache的一个jdbc工具包 dbutils -->
    <dependency>
      <groupId>commons-dbutils</groupId>
      <artifactId>commons-dbutils</artifactId>
      <version>1.4</version>
    </dependency>
    <!-- 导入数据库连接池依赖-->
    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
    </dependency>

2、编写domain层,实现Article类

package com.lwg.domain;

/**
 * @author lwg
 * @title
 * @create 2022/10/12 16:57
 */
public class Article {
    private Integer id;
    private String title;
    private String content;
    private Integer aid;

    public Article() {
    }

    public Article(Integer id, String title, String content, Integer aid) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.aid = aid;
    }

    public Integer getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Integer getAid() {
        return aid;
    }

    public void setAid(Integer aid) {
        this.aid = aid;
    }

    @Override
    public String toString() {
        return "Article{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", aid=" + aid +
                '}';
    }
}

3、编写dao层,编写IArticleDao接口和ArticleDaoIpml实现类

IArticleDao接口

package com.lwg.dao;

import com.lwg.domain.Article;

import java.util.List;

/**
 * @author lwg
 * @title
 * @create 2022/10/12 17:00
 */
public interface IArticleDao {
//    查询所有文章信息
    List<Article> selectAll();
//    通过Id查询文章
    Article selectById(Integer id);
}

ArticleDaoIpml实现类

package com.lwg.dao.impl;

import com.lwg.dao.IArticleDao;
import com.lwg.domain.Article;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.SQLException;
import java.util.List;

/**
 * @author lwg
 * @title
 * @create 2022/10/12 21:55
 */
public class ArticleDaoIpml implements IArticleDao {
    private QueryRunner queryRunner;

    public void setQueryRunner(QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }

    @Override
    public List<Article> selectAll() {
        try {
           return queryRunner.query("SELECT * FROM article",new BeanListHandler<Article>(Article.class));

        } catch (SQLException throwables) {
            throw new RuntimeException(throwables);
        }
    }

    @Override
    public Article selectById(Integer id) {
        return null;
    }
}

4、编写service层,编写IArticleService接口和ArticleServiceImpl实现类

IArticleService接口

package com.lwg.service;

import com.lwg.domain.Article;

import java.util.List;

/**
 * @author lwg
 * @title
 * @create 2022/10/12 22:01
 */
public interface IArticleService {
    public List<Article> getAll();
}

ArticleServiceImpl实现类

package com.lwg.service.impl;

import com.lwg.dao.IArticleDao;
import com.lwg.domain.Article;
import com.lwg.service.IArticleService;

import java.util.List;

/**
 * @author lwg
 * @title
 * @create 2022/10/12 22:01
 */
public class ArticleServiceImpl implements IArticleService {
    private IArticleDao articleDao;

    public void setArticleDao(IArticleDao articleDao) {
        this.articleDao = articleDao;
    }

    @Override
    public List<Article> getAll() {
        return articleDao.selectAll();
    }
}

5、编写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">
    <bean id="articleService" class="com.lwg.service.impl.ArticleServiceImpl">
        <property name="articleDao" ref="articleDao"/>
    </bean>
    <bean id="articleDao" class="com.lwg.dao.impl.ArticleDaoIpml">
        <property name="queryRunner" ref="runner"/>
    </bean>
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg ref="ds"/>
    </bean>
    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis_task? useUnicode=true&amp;characterEncoding=utf8"/>
        <property name="user" value="root"/>
        <property name="password" value="xxxxx"/>
    </bean>
</beans>

6、编写测试类

package com.lwg.dao;

import com.lwg.domain.Article;
import com.lwg.service.IArticleService;
import com.lwg.service.impl.ArticleServiceImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.omg.CORBA.PUBLIC_MEMBER;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

/**
 * @author lwg
 * @title
 * @create 2022/10/12 22:13
 */
public class ArticleServiceTest {
    private ClassPathXmlApplicationContext ctx;
    private String xmlPath="applicationContext.xml";
    private IArticleService articleService;
    @Before
    public void getCtx(){
        ctx=new ClassPathXmlApplicationContext(xmlPath);
        articleService=ctx.getBean("articleService", ArticleServiceImpl.class);
    }
    @After
    public void close(){
        ctx.close();
    }
    @Test
    public void getAllTest(){
        List<Article> all = articleService.getAll();
        System.out.println("all = " + all);
    }
}

上面是XML配置,现将它改成注解方式实现

1、在 dao 实现类中添加 @Repository 注解,并且通过 @Autowired 注入 QueryRunner 对象,在service 类中添加 @Service 注解,并且通过 @Autowired 注入 dao 对象。

xaKUk8.png

xaKatS.png

2、修改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">
    <!--<bean id="articleService" class="com.lwg.service.impl.ArticleServiceImpl">
        <property name="articleDao" ref="articleDao"/>
    </bean>
    <bean id="articleDao" class="com.lwg.dao.impl.ArticleDaoIpml">
        <property name="queryRunner" ref="runner"/>
    </bean>-->
    <context:component-scan base-package="com.lwg" use-default-filters="true">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg ref="ds"/>
    </bean>
    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis_task? useUnicode=true&amp;characterEncoding=utf8"/>
        <property name="user" value="root"/>
        <property name="password" value="xxxxxx"/>
    </bean>
</beans>

3、重新测试配置文件,运行结果一样。

上面的注解方式还需要结合配置文件来实现,现我们要将上面继续简化,使之成为全部注解方式实现。

将上面的XML配置文件用配置类用替代,需要用上 @Configuration,用来标识一个类为配置类。

使用@ComponentScan或@ComponentScans替代context:component-scan base-package,表示扫描哪些包,将定义的bean注册进spring容器中。

单一包时:@ComponentScan(“com.lwg”)

多个包时:@ComponentScan({“com.lwg.dao”,“com.lwg.service”})

ComponentScans可以声明多个ComponentScan,@ComponentScans是另外一个扫描包的注解,有一个数组形势的属性value,而数组的类型就@ComponentScan,也就是一个@ComponentScans中可以配置多个@ComponentScan。

@ComponentScans(value={@ComponentScan("com.lwg.dao"),@ComponentScan("com.lwg.service")}
)

也可以像XML配置一样包含哪些注解配置的类,或者排除哪些注解定义的类。

扫描com.lwg下所有的类,但排除Controller注解的类。

@ComponentScan(basePackages = "com.lwg",useDefaultFilters = true,excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)
})

在XML配置中使用bean标签配置QueryRunner和ComboPooledDataSource,而在配置类中要使用方法返回需要的对象,方法使用@Bean(“自定义bean名字”)注解定义,如果不设置名字就默认是方法名。

上述XML配置文件换成下面的配置类。

package com.lwg.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * @author lwg
 * @title
 * @create 2022/10/12 22:35
 */
//标识这个类是一个配置类
@Configuration
//@ComponentScan({"com.lwg.dao","com.lwg.service"})
//@ComponentScans()
@ComponentScan(basePackages = "com.lwg",useDefaultFilters = true,excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)
})
public class SpringConfig {

    @Bean("runner")
    public QueryRunner queryRunner(DataSource ds){
        return new QueryRunner(ds);
    }
    @Bean("ds")
    public DataSource dataSource(){
        ComboPooledDataSource dataSource=new ComboPooledDataSource();
        try {
            dataSource.setDriverClass("com.mysql.jdbc.Driver");
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mybatis_task? useUnicode=true&amp;characterEncoding=utf8");
            dataSource.setUser("root");
            dataSource.setPassword("xxxxxxxxxx");
            return dataSource;
        } catch (PropertyVetoException e) {
            e.printStackTrace();
            return null;
        }
    }
}

注:当方法里需要传入引用类型的bean,直接以形参的形式传入,spring容器会自动查找有没有同源类型的bean,如找到就注入,如果没有就按变量名称去查。

如果想形参注入的方式是按名称注入可以使用@Qualifier 注解按名称自动注入。

xa8O4P.png

使用方法创建的对象默认是单例的,可使用@Scope注解设置bean的作用范围。

xalxc4.png

再修改测试类获取spring容器的方法,之前我们是用ClassPathXmlApplicationContext读取xml配置文件创建spirng容器,现在我们要使用AnnotationConfigApplicationContext(SpringConfig.class)读取配置类创建spring容器。

package com.lwg.dao;

import com.lwg.config.SpringConfig;
import com.lwg.domain.Article;
import com.lwg.service.IArticleService;
import com.lwg.service.impl.ArticleServiceImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.omg.CORBA.PUBLIC_MEMBER;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

/**
 * @author lwg
 * @title
 * @create 2022/10/12 22:13
 */
public class ArticleServiceTest {

    private IArticleService articleService;
    @Before
    public void getCtx(){
       /* ctx=new ClassPathXmlApplicationContext(xmlPath);*/
        ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfig.class);
        articleService=ctx.getBean("articleService", ArticleServiceImpl.class);
    }
    @Test
    public void getAllTest(){
        List<Article> all = articleService.getAll();
        System.out.println("all = " + all);
    }
}

当存在多个配置类时,我们可在创建spirng容器时就传入多个配置类。

 ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfig.class,JdbcConfig.class);

或者也可以直接在主配置类中使用@Import导入其他配置类,创建spirng容器时只传主配置类即可。

@Import 注解:用于导入其他的配置类。

value属性:要导入的配置类的路径。

可将上述配置类优化一下,将有关数据库处理的bean放在JdbcConfig.class配置类中,并将数据库连接信息放在properties文件,然后使用**@PropertySource**注解指定 properties 文件的位置,让spring容器去加载这个文件,在配置类就可以读取到properties文件里的信息了。

新建JdbcConfig类

package com.lwg.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * @author lwg
 * @title
 * @create 2022/10/12 23:45
 */
/*@Configuration
@PropertySource("JdbcConfig.properties")*/
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.userName}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;
    @Bean("runner")
    @Scope("prototype")
    public QueryRunner queryRunner(DataSource ds){
        return new QueryRunner(ds);
    }
    @Bean("ds")
    public DataSource dataSource(){
        ComboPooledDataSource dataSource=new ComboPooledDataSource();
        try {
            dataSource.setDriverClass(driver);
            dataSource.setJdbcUrl(url);
            dataSource.setUser(userName);
            dataSource.setPassword(password);
            return dataSource;
        } catch (PropertyVetoException e) {
            e.printStackTrace();
            return null;
        }
    }
}

JdbcConfig.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_task?useUnicode=true&characterEncoding=utf8
jdbc.userName=root
jdbc.password=xxxxxxxx

SpringConfig类修改成

package com.lwg.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * @author lwg
 * @title
 * @create 2022/10/12 22:35
 */
//标识这个类是一个配置类
@Configuration
//@ComponentScan({"com.lwg.dao","com.lwg.service"})
//@ComponentScans()
@ComponentScan(basePackages = "com.lwg",useDefaultFilters = true,excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)
})
@PropertySource("JdbcConfig.properties")
@Import(JdbcConfig.class)
public class SpringConfig {


}

使用@Import注解导入JdbcConfig子配置类

使用@PropertySource注解指定文件的路径,spring容器会加载。

Spring整合Junit单元测试

这里的单元测试使用junit4。

一个程序的入口是main方法,但junit中不存在此方法,是因为 junit 内部的原理是它自己内部集成了一个 main 方法,运行时会扫描带 @Test 注解的方法,然后反射调用该方法,完成测试。

在传统的juit测试中,需要手动new创建spring容器,spring整合junit4就不需要了。

整合步骤:

1、导入依赖

<!--    spring-test-->
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.3.18</version>
      <scope>test</scope>
    </dependency>
<!--    junit单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

注:如果你使用的是 Spring5.0 以上的话,你的 Junit 版本必须是4.12及以上

2、使用注解@RunWith将Junit原有的main方法换成spirng的main方法。

@RunWith(SpringJUnit4ClassRunner.class)

SpringJUnit4ClassRunner.class:是 Runner的实现类,代表 Spring 的运行器。

3、告诉spring配置类或者配置文件的位置

配置类写法:

@ContextConfiguration(classes = SpringConfig.class)

配置文件写法

@ContextConfiguration(locations = "classpath:applicationContext.xml")

4、修改测试类内容,使用 @Autowired注解自动注入测试对象。

package com.lwg.dao;

import com.lwg.config.SpringConfig;
import com.lwg.domain.Article;
import com.lwg.service.IArticleService;
import com.lwg.service.impl.ArticleServiceImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.omg.CORBA.PUBLIC_MEMBER;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * @author lwg
 * @title
 * @create 2022/10/12 22:13
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
//@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ArticleServiceTest {

    @Autowired
    private IArticleService articleService;
    @Test
    public void getAllTest(){
        List<Article> all = articleService.getAll();
        System.out.println("all = " + all);
    }
}

总结:

最后我们再来总结一下 Spring 整合 Junit 单元测试后的执行原理,首先运行单元测试方法,自动解析 RunWith 注解,启动 Spring 运行器,由于SpringJUnit4ClassRunner 运行器代替了 Junit 的运行器 Runner ,所以会执行Spring 中的 main 方法,创建 SpringIOC 容器,接着会解析ContextConfiguration 注解,读取 SpringConfiguration 配置类或者 applicationContext.xml 配置文件,将相关 bean 。例如:IArticleService对象保存在spring容器中,最后通过依赖注入到articleService变量中,同时通过反射调用包含@Test注解的方法即可。

代理模式

在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

生活中的代理模式

xdVFjx.png

1、代理对象和真实对象都具备相同的功能,换言之,代理对象和真实对象都实现同一个接口。

2、代理对象可以扩展真实对象的功能。

3、代理对象一般持有真实对象的引用,可以依赖真实对象做任何事情。

代理模式优缺点

优点:

  1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用。
  2. 代理对象可以扩展目标对象的功能。
  3. 原来客户端直接依赖目标对象,通过代理模式后,客户端直接依赖代理对象,从而在一定程度上降低了系统的耦合度,简化了目标对象的代码,降低了目标对象的依赖关系,能将客户端与目标对象分离。

缺点:

  1. 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢。
  2. 增加了系统的复杂度。

代理模式的结构与实现

代理模式的角色:

  • 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题( Real Subject )类:实现抽象主题类中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

结构图如下所示:

xdGZQO.png

根据代理创建时间,分为静态代理和动态代理。

静态代理

由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

静态代理简单实现

学生要向老师交学费,现学生不想自己去交钱给老师,而请班长(代理)交给老师。

真实对象:老师

代理:班长

公共接口业务:交学费

客户:学生

1、实现交学费的IPerson 业务接口,这个接口是学生(被代理类)和代理类公共接口。

package com.lwg.proxy;

/**
 * @author lwg
 * @title
 * @create 2022/10/13 19:47
 */
public interface IPerson {
    public void payMoney();
}

2、创建学生类并实现IPerson 接口,实现交学费方法。

package com.lwg.proxy;

/**
 * @author lwg
 * @title
 * @create 2022/10/13 19:48
 */
public class Student implements IPerson{
    @Override
    public void payMoney() {
        System.out.println("本人亲自交了2000学费");
    }
}

3、创建代理类,实现IPerson 接口,同时持有一个学生对象,他可以代理学生执行交学费。

package com.lwg.proxy;

/**
 * @author lwg
 * @title
 * @create 2022/10/13 19:49
 */
public class StudentProxy implements IPerson{
//    被代理的对象
    private Student student;

    public StudentProxy(Student student) {
        this.student = student;
    }
    @Override
    public void payMoney() {
        System.out.println("班长代交了学费");
    }
}

4、写测试类,模拟班长代理交学费。

@Test
public void proxyTest(){
    IPerson student=new Student();
    IPerson proxy=new StudentProxy((Student) student);
    proxy.payMoney();
}

运行结果:

xdYMGt.png

案例里没有直接调用学生类的交学费方法,而是通过班长(代理)来交学费,这就是代理模式。代理模式有一个公共接口,一个代理类,一个具体类,代理有具体类的实例,代执行具体实例的方法。代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。

动态代理

代理类在程序运行时创建代理的方式被称为动态代理。 我们上面静态代理的例子中,代理类( StudentProxy )是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java 代码中定义的,而是在运行时根据我们在 Java 代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对目标类的函数进行统一的处理,而不用修改每个目标类中的方法。

动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler 来完成的代理过程的。

xBc9Og.png

xBcPmQ.png

xBcepV.png

分析动态代理类,可得其特点:

  1. 动态代理类继承自 Proxy 类,并且实现了和真实对象同样的接口。

  2. 动态代理类持有了 Object 的方法对象的引用以及接口中所定义方法对象的引用。

  3. 动态代理类持有 InvocationHandler 接口的引用,该引用的值通过外界以构造方法的方式传入。

  4. 调用动态代理类的任意一个成员方法,都会触发 InvocationHandler 对象的 invoke 方法。

  5. public Object invoke(Object proxy, Method method, Object[] args) 方法参数含义如下:

proxy :表示代理对象。
method :表示当前正在执行的方法对象。
args: 表示代理对象当前正在执行的方法的参数。

分析动态代理原理得:

执行代理对象的任何一个方法,都会触发执行自定义 InvocationHandler 的 invoke 方法,整个代理的过程全靠我们自己如何重写 InvocationHandler 的 invoke 方法。通过在 invoke 方法中我们可以对功能进行增强,或者调用真实对象执行方法,从而实现在不修改目标类的基础上,实现目标类的函数进行统一的处理,而不用修改每个目标类中的方法。

AOP

概述

AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。 AOP 是 Spring 框架中的一个重要内容。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

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

A(Aspect) :切面,给你的目标类增加的功能,就是切面。例如日志,事务都是切面。切面所代指的功能一般都可以独立使用,都属于非业务方法。

面向切面编程是面向对象编程的补充,面向切面编程的步骤

  1. 分析项目的功能,找出对应的切面,并不是所有的功能都可以作为切面。
  2. 合理的安排切面的执行时间(在目标方法前,还是目标方法后)。
  3. 合理的安排切面执行的位置(在哪个类,哪个方法进行增强,然后进行织入)。

面向切面编程的好处

  1. 减少重复:在程序运行期间,不修改源码的基础上对已有方法进行增强。
  2. 专注业务:将业务功能和通用功能进行代码解耦,从而让我们在开发的过程中更加专注业务。

AOP相关术语

切面( Aspect )

切面泛指业务逻辑。实际是对业务逻辑的一种增强。即增强功能,一个独立的业务功能模块。

连接点( JoinPoint )

可以被切面织入的具体方法。通过是业务层中的方法称为连接点。

切入点( Pointcut )

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

目标对象( Target )

目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。

通知( Advice )

通知表示切面的执行时间, Advice 也叫增强。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。

织入( Weaving )

指的是把增强应用到目标对象来创建新的代理对象的过程, Spring 采用动态代理织入,而 AspectJ 采用编译器织入和类装载期织入。

Proxy(代理)

一个类被 AOP 织入增强后,就产生一个结果代理类。

Aspect(切面)

即是切入点和通知的结合。

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

spring基于注解实现AOP

对于 AOP 这种编程思想,很多框架都进行了实现。 Spring 就是其中之一,可以完成面向切面编程。然而, AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以, Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。

在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。

AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。

AspectJ框架的使用

AspectJ关注的三个点:切面、切面的执行时间、切入的位置。

切面:业务逻辑,即增强的功能,独立非业务功能的代码。

切面的执行时间(通知类型):

  • 前置通知
  • 后置通知
  • 环绕通知
  • 异常通知
  • 最终通知

在AspectJ框架中可使用XML配置文件中的标签也可以使用注解来表示通知类型。

注解:

  • @Before 前置通知
  • @AfterReturning 后置通知
  • @Around 环绕通知
  • @AfterThrowing 异常通知
  • @After 最终通知

切入位置:AspectJ 的切入点表达式

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

表达式分为4个部分:

execution(1.访问权限 2.方法返回值 3.方法声明(参数) 4.异常类型)

其中访问访问权限、异常类型可省略。

切入点表达式要匹配的对象就是目标方法的方法名。所以, execution 表达式中明显就是方法的签名。
注意,表达式中各部分间用空格分开。

切入点表达式通配符:

符号意义
*表示0-N个字符
用在方法参数,表示多个任意参数。用在包名,表示多级目录(即当前包或子包)
+用在类名后,表示当前类或者子类;用在接口后,表示接口或者实现类

例:

execution(public * *(…))

指定切入点为:任意公共方法。

execution(* set*(…))

指定切入点为:任何一个以“ set ”开始的方法。

execution(* com.xyr.service..(…))

指定切入点为:定义在 service 包里的任意类的任意方法。

execution(* com.xyr.service….(…))

指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“…”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。

execution(* …service..*(…))

指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点。

execution(* .service..*(…))

指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点。

execution(* .IUserService.(…))

指定只有一级包下的 IUserService 接口中所有方法为切入点。

execution(* …IUserService.(…))

指定所有包下的 IUserService 接口中所有方法为切入点。

execution(* com.xyr.service.IUserService.*(…))

指定切入点为: IUserService 接口中的任意方法。

execution(* com.xyr.service.IUserService+.*(…))

指定切入点为: IUserService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。

execution(* add(String,int)))

指定切入点为:所有的 add(String,int) 方法,且 add() 方法的第一个参数是 String ,第二个参数是 int 。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 add(java.util.List, int) 。

execution(* add(String,*)))

指定切入点为:所有的 add() 方法,该方法第一个参数为 String ,第二个参数可以是任意类型,如add(String s1,String s2) 和 add(String s1,double d2) 都是,但 add(String s1,double d2,String s3) 不是。

execution(* add(String, …)))

指定切入点为:所有的 add() 方法,该方法第一个参数为 String ,后面可以有任意个参数且参数类型不限,如 add(String s1) 、 add(String s1,String s2) 和 add(String s1,double d2,String s3) 都是。

execution(* add(Object))

指定切入点为:所有的 add() 方法,方法拥有一个参数,且参数是 Object 类型。 add(Object ob)是,但 add(String s) 与 add(User u) 均不是。

execution(* add(Object+)))

指定切入点为:所有的 add() 方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅

add(Object ob) 是, add(String s) 和 add(User u) 也是。

注意:

  1. 切入点表达式的中的方法参数类型如果是自定义类,要写限定类名
  2. 如果切入点表达式对应的方法没有定位到对应的切入位置,那么访方法不会被代理
Aspectj框架使用
入门案例

1、导依赖

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.6</version>
</dependency>

2、创建业务层和要增强的业务层接口和实现类,准备给业务层的方法进行增强。

UserService接口:

package com.lwg.service;

import com.lwg.dao.User;

/**
 * @author lwg
 * @title
 * @create 2022/10/17 15:11
 */
public interface UserService {
    public User getUserById(Integer id);
    public Boolean upDateUserById(User user);
    public Boolean deleteUserById(Integer id);
}

UserServiceImpl实现类

package com.lwg.service.impl;

import com.lwg.dao.User;
import com.lwg.service.UserService;

/**
 * @author lwg
 * @title
 * @create 2022/10/17 15:12
 */
public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(Integer id) {
        System.out.println("查询id为:"+id+"的用户");
//        System.out.println(10/0);
        return new User(1,"张三",23);
    }

    @Override
    public Boolean upDateUserById(User user) {
        System.out.println("更新用户为"+user);
        return true;
    }

    @Override
    public Boolean deleteUserById(Integer id) {
        System.out.println("删除Id为"+id+"用户");
        return true;
    }
}

3、创建切面类,切面类是用来增强业务层的功能。

  • 切面类要加注解@Aspect
  • 自定义方法,实现要增强的功能
  • 在方法添加相应的通知相关注解
package com.lwg.aspect;

import com.lwg.dao.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.omg.CORBA.PUBLIC_MEMBER;

import java.util.Objects;

/**
 * @author lwg
 * @title
 * @create 2022/10/17 15:39
 */
@Aspect
public class UserAspect {
        @Before("execution(* com.lwg.service.UserService.getUserById(*))")
    public void aopDemo1(){
        System.out.println("前置通知执行");
    }
        }
}

​ 4、创建spirng核心配置文件

  • 注册目标类对象
  • 注册切面类对象
  • 注册自动代理器,使用Aspectj和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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="user" class="com.lwg.dao.User"/>
<!--    注册目标类-->
    <bean id="userService" class="com.lwg.service.impl.UserServiceImpl"/>
<!--    注册切面类对象-->
    <bean id="userAspect" class="com.lwg.aspect.UserAspect"/>
<!--    声明自动代理生成器-->
    <aop:aspectj-autoproxy/>
</beans>

5、新建测试类

package com.lwg.service;

import com.lwg.dao.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author lwg
 * @title
 * @create 2022/10/17 15:43
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:springConfig.xml")
public class UserServiceTest {
    //这里生成的对象就代理类的代理对象
    @Autowired
    private UserService userService;
    @Test
    public void aopDemoTest01(){
        User userById = userService.getUserById(1);
        System.out.println("userById = " + userById);
    }
    @Test
    public void beforeAopTest(){
        User user = userService.getUserById(1);
        System.out.println(user);
    }
    @Test
    public void afterAopTest(){
        userService.upDateUserById(new User(2,"李四",23));
    }
}

原理分析:

首先加载spring容器,然后扫描classpath:springConfig.xml文件,加载核心配置文件,创建userService对象、userAspect对象放在容器中,然后当扫描<aop:aspectj-autoproxy/>,就会在spring容器中扫描每个对象是否有@Aspect注解:

  • 没有,就继续找下一个对象
  • 如果有,就说明此类有功能增强的代码

​ 继续找此的类的方法,看方法前面是否通知相关的的注解,没有就继续往下找,有就扫描注解里的value值,也就是切入点表达式,最后根据切入点表达式定位到指定类的指定业务方法,在该业务方法根据通知类型在相应的位置添加增强代码,最后生成代理对象。

xgMb3d.png

前置通知

在目标方法执行之前执行,前置通知标记的通知方法可以有一个JoinPoint参数。通过这个参数可以获取到切入点表达式、方法签名、目标对象等,所有通知方法都可以有JoinPoint参数。

在前置通知方法前面使用 @Before注解

前置通知方法:

  1. 方法返回修饰符public
  2. 可以有一个JoinPoint参数
  3. 方法没有返回值
//    前置通知
    @Before("execution(* com.lwg.service.UserService.getUserById(..))")
    public void beforeAop(JoinPoint jp){
        System.out.println("前置通知开始执行了");
//        获取方法的声明
        System.out.println("jp.getSignature() = " + jp.getSignature());
        System.out.println("jp.getSignature().getName() = " + jp.getSignature().getName());
//        获取方法实参
        System.out.println("获取实参");
        Object[] args = jp.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
//        获取目标对象
        System.out.println("jp.getTarget() = " + jp.getTarget());
        System.out.println("前置通知执行完毕,现在执行目标方法");
    }
后置通知

在目标方法执行后执行,使用@AfterReturning注解,@AfterReturning注解有个一个returning属性,用来设置封装目标方法执行的结果的变量的名,该值要和后置通知方法的参数(装结果的)名一样。当然后置通知方法的也可以有JoinPoint参数。

后置通知方法:

  1. 方法没有返回值
  2. 方法还可以有一个装目标方法的结果的变量,此变量名要与@AfterReturning注解中returning属性值一致
//    后置通知
    @AfterReturning(value = "execution(* com.lwg.service.*.*(..))",returning = "result")
    public void afterAop(Object result){
        System.out.println("后置通知开始执行");
        System.out.println("执行的结果result是:"+result);
        User user=(User)result;
        user.setName("武大郎");
    }
环绕通知

环绕通知是最强的,可以在目标方法前后执行。环绕通知方法要有返回值(Object 类型),方法可以有一个ProceedingJoinPoint类型的参数,可以通ProceedingJoinPoint参数调用proceed()方法去执行目标方法,如果该方法有返回值环绕通知方法的返回值就是此值,最后再环绕方法再将返回值返回。

ProceedingJoinPoint类是JoinPoint类的子类

使用@Around注解

环绕通知方法:

  1. 方法返回修饰符public
  2. 可以有一个ProceedingJoinPoint参数
  3. 方法要有返回值(Object 类型)
//    环绕通知
    @Around("execution(* com.lwg.service.*.*(..))")
    public Object aroundAop(ProceedingJoinPoint pjp){
        Object proceed = null;
        System.out.println("环绕通知的前面一部分开始执行");
        System.out.println("开始执行目标方法");
        try {
             proceed= pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("执行结果proceed"+proceed.toString());
        User user = (User) proceed;
        user.setName("驻外机构中");
        return user;
    }
异常通知

在目标方法抛出异常后执行,使用注解@AfterThrowing,@AfterThrowing注解的有个throwing属性,然后异常通知方法可以有一个Throwable参数,用来封装目标方法执行的异常,Throwable参数的参数名要与throwing属性的值一致。当然异常通知方法也有JoinPoint类的参数。

异常通知方法

  1. 方法返回修饰符public
  2. 可以有一个JoinPoint参数
  3. 可以有Throwable参数,但参数名要与throwing属性的值一致
  4. 方法没有返回值
//    异常通知
    @AfterThrowing(value = "execution(* com.lwg.service.*.*(..))",throwing = "ex")
    public void throwingAop(Throwable ex){
        System.out.println("方法出现异常了,执行异常通知");
        System.out.println("ex = " + ex);
    }
最终通知

无论目标方法是否抛出异常,都执行。使用注解@After

最终通知方法

  1. 方法返回修饰符public
  2. 可以有一个JoinPoint参数
  3. 方法没有返回值
//    最终通知
    @After("execution(* com.lwg.service.*.*(..))")
    public void finalExe(){
        System.out.println("最终通知,无论方法是否报错都会执行这个通知");
    }
切入点

当多个通知的切入表达式是相同切入表达式,这时候可以定义切入点,以后使用这个切入点就相当于使用了这个切入表达式。

使用@Pointcut注解定义切入点

  1. 切入点对应的方法没有任何操作,是个空方法
  2. 调用这个切入点使用的是方法名
  3. @Pointcut的value写切入表达式
//    切入点
//    定义切入点
    @Pointcut("execution(* com.lwg.service.*.*(..))")
    public void cutString(){

    }
@After("cutString()")
public void finalExe(){
    System.out.println("最终通知,无论方法是否报错都会执行这个通知");
}
Spring使用动态代理的方式
  • JDK动态代理,使用动态代理中Proxy、Method、InvocationHandler创建对象,JDK动态代理要求目标类必须要有实现类
  • cglib动态代理:第三方工具库,创建代理对象,原理是继承。通过继承目标类,创建子类,子类对象就是代理对象,要不目标类不能是final的,方法也不能是final的。

Aop底层通过动态代理实现的,spring同时采用了两种动态代理方式:JDK代理和cglib动态代理。如果目标类有实现类,spirng的动态代理默认是JDK代理,如果目标类没有实现类就默认是cglib动态代理。

可通过设置aop标签的proxy-target-class属性,当值为true时设置使用cglib动态代理,为false时设置成JDK代理。

纯注解配置AOP

上面的注解配置还有需要在核心配置文件中注解bean和声明自动代理生成器。

将目标类和代理类使用注解注册成bean

目标类UserServiceImpl

xgBVqU.png

代理类UserAspect
x4hB0U.png

创建spirng配置类替代核心配置文件

package com.lwg.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @author lwg
 * @title
 * @create 2022/10/23 10:45
 */
@Configuration
@ComponentScan("com.lwg")
//自动代理配置注解
@EnableAspectJAutoProxy
/*设置自动代理方式,使用JDK动态代理还是使用cglib动态代理
@EnableAspectJAutoProxy(proxyTargetClass = true)*/
public class SpringConfig {
}

<aop:aspectj-autoproxy proxy-target-class=“false” />使用@EnableAspectJAutoProxy注解替代,里面的proxyTargetClass属性用来设置spirng使用的动态代理方式。

Spring使用XML配置AOP

案例

编写目标接口和实现类

UserService接口

package com.lwg.service;

import com.lwg.dao.User;

/**
 * @author lwg
 * @title
 * @create 2022/10/23 11:00
 */
public interface UserService {
  public User getUserById(Integer id);
}

UserServiceImpl类:

package com.lwg.service.impl;

import com.lwg.dao.User;
import com.lwg.service.UserService;

/**
 * @author lwg
 * @title
 * @create 2022/10/23 11:03
 */
public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(Integer id) {
        return new User(1,"张生",34);

    }
}

编写切面类

package com.lwg.aspect;

import com.lwg.dao.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * @author lwg
 * @title
 * @create 2022/10/23 11:07
 *
 * 切面类
 */
public class MyAspect {
    public void beforeAspet(){
        System.out.println("前置通知执行");
    }
    public void afterAspect(Object result){
        System.out.println("目标方法的结果:"+result);
        System.out.println("后置通知执行");
        User user= (User) result;
        user.setName("王富强");
    }
    public Object aroundAspect(ProceedingJoinPoint pjp){
        Object reslut=null;
        System.out.println("环绕通知在执行方法前面执行");
        System.out.println("执行目标方法");
        try {
            reslut=pjp.proceed();
            System.out.println("环绕通知在执行方法后面执行部分");
        } catch (Throwable throwable) {
            System.out.println("环绕通知报异常后执行");
            throwable.printStackTrace();
        }
        return reslut;
    }
    public void throwingAspct(){
        System.out.println("异常通知方法执行");
    }
    public void lastAspct(){
        System.out.println("最终通知执行");
    }
}

编写核心配置文件,配置切面

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

<!--    配置目标类-->
    <bean id="userService" class="com.lwg.service.impl.UserServiceImpl"/>
<!--    配置切面类-->
    <bean id="myAspect" class="com.lwg.aspect.MyAspect"/>
<!--    配置aop,aop:config表示当前配置是一段切面配置-->
    <aop:config>
<!--        aop:aspect配置一个通知,id自定义,ref属性表示切面类的id
             aop:aspect标签里可以有:
             aop:before:前置通知
             aop:after-returning:后置通知
             aop:around:环绕通知
             aop:after-throwing:异常通知
             aop:after:最终通知
             属性说明:
                method:表示通知方法名
                pointcut:切入点表达式
                pointcut-ref:切点id
-->
        <aop:aspect id="beforeAspect" ref="myAspect" >
<!--            配置切入点,id是此切面的唯一标识
                在aop:aspect内部配置的,这个切入点只有在这个配置里使用
                如果是在aop:aspect外部配置,这个切入点可以在任何的aspect里使用
-->
            <aop:pointcut id="cut" expression="execution(* com.lwg.service.*.*(..))"/>
<!--            配置前置通知-->
            <aop:before method="beforeAspet" pointcut="execution(* com.lwg.service.*.*(..))"/>
<!--            method属性表示通知方法名,pointcut是切入点表达式,pointcut-ref是切入点id-->
<!--                配置后置通知-->
            <aop:after-returning method="afterAspect" pointcut-ref="cut" returning="result"/>
<!--            returning属性表示通知方法中封装的目标方法执行的结果,要和通知方法上的参数名一致-->
<!--            配置环绕通知-->
            <aop:around method="aroundAspect" pointcut-ref="cut"/>
<!--            配置异常通知-->
            <aop:after-throwing method="throwingAspct" pointcut-ref="cut"/>
<!--            配置最终通知-->
            <aop:after method="lastAspct" pointcut-ref="cut"/>
        </aop:aspect>
    </aop:config>
</beans>
  • aop:config标签用来配置aop,里面可以写多个aop:aspect标签

  • aop:aspect标签配置切面,里面可以配置前置、后置、环绕、异常、最终通知。

    • ​ aop:before:前置通知
      ​ aop:after-returning:后置通知
      ​ aop:around:环绕通知
      ​ aop:after-throwing:异常通知
      ​ aop:after:最终通知
    • 相关属性:method:表示通知方法名
      pointcut:切入点表达式
      pointcut-ref:切点id
    • aop:aspect标签用来配置切点,id属性表示是这个切点的唯一标识,expression属性表示切入点表达式

Spring整合MyBatis

整合思想

将spirng整合mybatis就可以让我们在使用的时候就像在使用一个框架。

整合的技术是用IOC技术,IoC可以创建对象,把MyBatis框架中用到的对象交给spring容器管理,开发人员就可以直接在spring获取对象。

整合MyBatis只需要将SqlSessionFactory对象交给spring管理,需要将SqlSessionFactory的对象生成器SqlSessionFactoryBean注册到spring容器,再将dao实现类即可整合。

实现Spring与MyBatis的整合常用的方式:扫描Mapper动态代理

整合原理分析

分析MyBatis框架使用步骤:

1、定义dao接口

2、编写xml文件

3、定义编写MyBatis核心配置文件

4、通过SqlSession.getMapper获取dao的代理对象

5、使用代理对象执行方法

分析使用步骤得出MyBatis需要哪些对象

要使用getMapper方法要SqlSession对象

SqlSession对象要通过SqlSessionFactory.openSession()方法

SqlSessionFactory对象要 SqlSessionFactoryBuilder对象的builder方法

读取主配置文件来获得

在主配置文件中使用了myBtais自带的连接池,要使用独立的第三方连接池就要将连接池对象交给spring容器管理。

spring要给MyBatis创建以下对象;

  • 独立的连接池对象,Druid连接池
  • SqlSessionFactory对象
  • Mapper/Dao对象

案例

使用XML配置实现spring整合MyBatis

导依赖

<!--    mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.9</version>
    </dependency>
<!--    mybatis和spring集成依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.7</version>
    </dependency>
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
<!--    druid连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.9</version>
    </dependency>

1、编写实体类User

package com.lwg.domain;

/**
 * @author lwg
 * @title
 * @create 2022/10/24 16:47
 */
public class User {
    private Integer id;
    private String name;
    private String pwd;

    public User() {
    }

    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 getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

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

2、编写dao接口、mapper.xml文件

UserDao接口

package com.lwg.dao;

import com.lwg.domain.User;

/**
 * @author lwg
 * @title
 * @create 2022/10/24 16:50
 */
public interface UserDao {
    public User selectUserById(Integer id);
}

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.lwg.dao.UserDao">
    <select id="selectUserById" parameterType="int" resultType="user">
        SELECT * FROM USER WHERE id=#{id}
    </select>
</mapper>

3、编写service层,编写接口、实现类

UserService接口

package com.lwg.service;

import com.lwg.domain.User;

/**
 * @author lwg
 * @title
 * @create 2022/10/24 17:04
 */
public interface UserService {
    public User getUserById(Integer id);
}

UserService实现类

package com.lwg.service.impl;

import com.lwg.dao.UserDao;
import com.lwg.domain.User;
import com.lwg.service.UserService;

/**
 * @author lwg
 * @title
 * @create 2022/10/24 17:04
 */
public class UserServiceImpl implements UserService {
    private UserDao userDao;
    
//    通过set注入,要提供set方法
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public User getUserById(Integer id) {
        User user = userDao.selectUserById(id);
        return user;
    }
}

4、编写spring核心配置文件applicationContext.xml

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

<!--    注册数据源druidDataSource-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis_task? useUnicode=true&amp;characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="xxxxxxxxxxxxxxxx"/>
    </bean>
<!--    注解SqlSessionFactoryBean-->
    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--        configLocation属性是Resource类型的 注入mybatis核心配置文件-->
        <property name="configLocation" value="classpath:myBatis.xml"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    注解dao/mapper对象
    MapperScannerConfigurer类会自动扫描指定的包下的接口,然后将Mapper对象创建放在spring容器中
-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.lwg.dao"/>
    </bean>
    
<!--    注解serviec对象-->
    <bean id="userService" class="com.lwg.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

上面的xml中的dataSource配置还可以优化,将数据库连接信息放在配置文件中

<!--    注册数据源druidDataSource-->
    <!--<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis_task? useUnicode=true&amp;characterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="20010107wdsr"/>
    </bean>-->
<!--    导入properties文件-->
    <context:property-placeholder location="jdbcConfig.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.userName}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

jdbcConfig.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_task? useUnicode=true&amp;characterEncoding=utf8
jdbc.userName=root
jdbc.password=xxxxxxx

5、编写对应测试类

dao测试类

package com.lwg.dao;

import com.lwg.domain.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author lwg
 * @title
 * @create 2022/10/24 17:25
 */
public class UserDaoTest {
    @Test
    public void selectUserByIdTest(){
        ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = ctx.getBean("userDao", UserDao.class);
        User user = userDao.selectUserById(1);
        System.out.println("user = " + user);
    }
}

service测试类

package com.lwg.service;

import com.lwg.domain.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author lwg
 * @title
 * @create 2022/10/24 17:32
 */
public class UserServiceTest {
    @Test
    public void getUserByIdTest(){
        ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = ctx.getBean("userService", UserService.class);
        User user = userService.getUserById(1);
        System.out.println("user = " + user);
    }
}

注:spring整合MyBatis后的自动提交事务,也就是增、删改自动提交,不用手动提交或者手动开启自动提交。

使用注解实现整合MyBatis

将spring核心配置文件换成配置类

将配置数据源dataSource放在JdbcConfig类中

JdbcConfig类

package com.lwg.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

/**
 * @author lwg
 * @title
 * @create 2022/10/24 19:03
 */

public class JdbcConfig {
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.userName}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    //    注解dataSource
    @Bean("dataSource")
    public DruidDataSource dataSource(){
        DruidDataSource dataSource=new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setUsername(userName);
        dataSource.setPassword(password);
        return dataSource;

    }
}

SpringConfig

package com.lwg.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.omg.CORBA.MARSHAL;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;

import javax.sql.DataSource;

/**
 * @author lwg
 * @title
 * @create 2022/10/24 18:05
 *
 * spring配置类
 */
@Configuration
//导入properties文件
@PropertySource("jdbcConfig.properties")
//扫描bean
@ComponentScan(basePackages = "com.lwg",useDefaultFilters = true,excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)
})
@Import(JdbcConfig.class)
public class SpringConfig {

//    注册SqlSessionFactoryBean
    @Bean
    public SqlSessionFactoryBean sessionFactory(DruidDataSource dataSource){
        SqlSessionFactoryBean sessionFactoryBean=new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
//        这里没有直接设置mybatsi核心配置文件的路径,但核心配置文件里的所有设置可以直接set方法设置
/*        设置,配置整个domain包
*               <typeAliases>
                    <package name="com.lwg.domain"/>
                </typeAliases>
* */
        sessionFactoryBean.setTypeAliasesPackage("com.lwg.domain");
        return sessionFactoryBean;
    }
    //注册dao,自动扫描包mapper
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        //设置扫描dao包,将dao包的接口都创建对象并加spring容器中
        MapperScannerConfigurer mapperScannerConfigurer=new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.lwg.dao");
        return mapperScannerConfigurer;
    }
}

注:坑。。。。@PropertySource(“jdbcConfig.properties”)导properties文件,在引入的那个类使用@Value自动注入,注入的数据为Nulll。

事务

事务可以帮助维护数据库的完整性和一致性,确保多条sql语句同成功,同失败。

MySql事务主要用于处理SQL异常执行导致数据出错的情况,事务主要管理insert、update、delete语句。

事务的实现原理

事务的本质是在开启事务的时候拷贝一张和原始表一模一样的副本表,然后在事务内的操作其实都是在操作副本表,并不会动真实表。

  • 如果执行成功了,并执行commit,那么系统就会用副本表的数据覆盖真实表的数据
  • 如果执行失败,并且执行了rollback,那么系统就会删除副本表,并不会影响原始表的数据
  • 开启事务后,任何执行都是在副本表里进行操作,和真实表没有任何关系,事务只有在提交了commit或者rollback才结束

事务的特点

  • 原子性(A):事务开启后的所有SQL操作同成功,同失败
  • 一致性(C):事务在开始前和结束后,数据库的完整性没有补破坏。
  • 持久性(I):事务的操作都是操作副本表,副本表在内存中如果手动提交了commit,副本表就会覆盖原来数据,并且这种改变是不可逆的
  • 隔离性(D):数据库允许多个并发事务同时对数据进行读写和修改的能力,隔离性可以防止多个并发事务执行时由于同时执行导致数据不一致的问题

事务隔离级别

  • 读未提交
  • 读已提交
  • 可重复读
  • 串行化

默认MySql的隔离级别是读已提交

读未提交

一个事务可以读到另一个事务未提交的数据,会产生脏读问题。

xRuY5D.png

如果一个事务读取到另一个事务未提交的数据,这里这个事务进行回滚,这读到的数据就是错误的数据了,产生脏读的问题。

读已提交

为了解决脏读的问题,一个事务只能读取到事务已经提交的数据。但会产生不可重复读的问题。

A事务在进行更新,B事务进行查询只能查询到上次提交的数据,然后A事务进行了提交,这里B事务再进行查询,查询是这次提交的数据了,两次查询的数据就不一致,这就是不可以重复读问题。

xRu4rq.png

可重复读

同一个事务内,多次查询的数据是一致的,解决了不可重复读问题,但存在幻读。

A事务单独一个事务,进行第一次查询,然后B事务又单独开启一个事务进行更新操作,然后结束B事务,A事务再一次查询,查询到的数据和第一次查询是一样的,但数据库中的数据已经更新了,所以就产生了幻读问题。

xRKPiD.png

串行化

前一个事务没有执行结束后面的所有事务都不能执行。

解决了幻读的问题,也解决了所有问题,但串行化有个问题,就是效率低。

xRKQJg.png

Spring事务

Spirng内部提供一套统一处理事务的统一模型,能使用步骤,方式完成多种不同数据库访问技术的事务处理。

Spring把每一种数据库访问技术对应的事务的实现都创建好了。

xRMnXR.png

使用事务只需要告诉spring使用数据库访问技术,在spring的配置文件中使用标签中声明就可以了哦。

业务方法的事务类型也要配置的:

  1. 事务的隔离级别

    xRMBAf.png

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

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

  4. 事务提交事务,回滚时机

    1. 当事务执行成功,没有抛出异常,当方法执行完毕,spring在方法执行后提交事务
    2. 当业务方法执行时抛出异常,spring执行回滚
    3. 当业务方法抛出异常后,spring提交事务
Spring的事务管理

事务原本是数据库层的概念,在dao层,但在一般情况,事务提到业务层,即service层。

spring通过以下两种方式实现对事务的管理:

  • 使用spring的事务注解管理事务
  • 使用Aspectj的AOP配置管理事务
spring事务管理API
事务管理器

事务管理器是PlatformTransactionManager接口对象,主要是用于完成事务的提交、回滚、以及获取事务的状态信息。

常用的两个实现类:

xRQUGF.png

事务定义接口

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

  • 事务隔离级别常量:

xRQsVx.png

  • 事务传播行为常量:

事务传播行为就是不同事务方法在相互调用时,执行期间事务的维护情况。

xRQ4sA.png

xRQTdP.png

xRlpd0.png

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W5tmg8jY-1666970713955)(C:\Users\35173\AppData\Roaming\Typora\typora-user-images\image-20221024234513361.png)]

Spring事务管理实现基于XML

实现转账业务,同失败同成功

1、添加事务依赖

<!--    事务依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

2、编写实体类

Goods类

package com.lwg.domain;

/**
 * @author lwg
 * @title
 * @create 2022/10/24 23:57
 *
 * 商品实体类
 */
public class Goods {
    private Integer gId;
    private String name;
    private Integer amout;
    private Double price;

    public Goods() {
    }

    public Integer getgId() {
        return gId;
    }

    public void setgId(Integer gId) {
        this.gId = gId;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAmout() {
        return amout;
    }

    public void setAmout(Integer amout) {
        this.amout = amout;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "gId=" + gId +
                ", name='" + name + '\'' +
                ", amout=" + amout +
                ", price=" + price +
                '}';
    }
}

Sale类

package com.lwg.domain;

/**
 * @author lwg
 * @title
 * @create 2022/10/24 23:59
 *
 * 订单记录
 */
public class Sale {
    private Integer sId;
    private Integer gId;
    private Integer nums;

    public Sale() {
    }

    public Integer getsId() {
        return sId;
    }

    public void setsId(Integer sId) {
        this.sId = sId;
    }

    public Integer getgId() {
        return gId;
    }

    public void setgId(Integer gId) {
        this.gId = gId;
    }

    public Integer getNums() {
        return nums;
    }

    public void setNums(Integer nums) {
        this.nums = nums;
    }

    @Override
    public String toString() {
        return "Sale{" +
                "sId=" + sId +
                ", gId=" + gId +
                ", nums=" + nums +
                '}';
    }
}

3、编写dao层,编写dao接口和对应的.xml文件

GoodsDao接口

package com.lwg.dao;

import com.lwg.domain.Goods;

/**
 * @author lwg
 * @title
 * @create 2022/10/25 0:01
 */
public interface GoodsDao {
    public Goods selectGoodsByGId(Integer gId);
    public Integer updateGoodsByGId(Goods goods);
}

GoodsDao.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.lwg.dao.GoodsDao">
    <resultMap id="goodsMap" type="goods">
        <id column="g_id" property="gId"/>
    </resultMap>
    <select id="selectGoodsByGId" parameterType="int" resultMap="goodsMap">
        select * from goods where g_id =#{gId}
    </select>
    <update id="updateGoodsByGId">
        update goods set amout=#{amout} where g_id=#{gId}
    </update>
</mapper>

SaleDao接口

package com.lwg.dao;

import com.lwg.domain.Sale;

/**
 * @author lwg
 * @title
 * @create 2022/10/25 9:01
 */
public interface SaleDao {
    public Integer insertSale(Sale sale);
}

SaleDao.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.lwg.dao.SaleDao">
    <resultMap id="saleMap" type="sale">
        <id column="s_id" property="gId"/>
    </resultMap>
    <insert id="insertSale">
        INSERT sale(g_id,nums) VALUES(#{gId},#{nums})
    </insert>
</mapper>

4、编写异常类,自定义异常

GoodNumsException类

package com.lwg.exception;

/**
 * @author lwg
 * @title
 * @create 2022/10/25 10:18
 *
 * 商品不足异常
 */
public class GoodNumsException extends RuntimeException{
    public GoodNumsException() {
        super();
    }

    public GoodNumsException(String message) {
        super(message);
    }
}

GoodsNoException类

package com.lwg.exception;

/**
 * @author lwg
 * @title
 * @create 2022/10/25 10:15
 *
 * 商品不存在异常
 */
public class GoodsNoException extends RuntimeException{
    public GoodsNoException() {
        super();
    }

    public GoodsNoException(String message) {
        super(message);
    }
}

5、编写service层,编写转账业务接口以及实现类

PayService接口

package com.lwg.service;

/**
 * @author lwg
 * @title
 * @create 2022/10/25 8:58
 *
 * 交易业务
 *
 */
public interface PayService {
    public boolean trade(Integer gId,Integer nums);
}

PayServiceImpl

package com.lwg.service.impl;

import com.lwg.dao.GoodsDao;
import com.lwg.dao.SaleDao;
import com.lwg.domain.Goods;
import com.lwg.domain.Sale;
import com.lwg.exception.GoodNumsException;
import com.lwg.exception.GoodsNoException;
import com.lwg.service.PayService;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;

/**
 * @author lwg
 * @title
 * @create 2022/10/25 9:01
 */
public class PayServiceImpl implements PayService {
    private GoodsDao goodsDao;
    private SaleDao saleDao;

    public void setGoodsDao(GoodsDao goodsDao) {
        this.goodsDao = goodsDao;
    }

    public void setSaleDao(SaleDao saleDao) {
        this.saleDao = saleDao;
    }
    //开启事务
    @Transactional
    @Override
    public boolean trade(Integer gId, Integer nums) {
        Sale sale = new Sale();
        sale.setgId(gId);
        sale.setNums(nums);
        Goods goods = goodsDao.selectGoodsByGId(gId);
        if (Objects.isNull(goodsDao)) {
            throw new GoodsNoException("物品不存在");
        }
        saleDao.insertSale(sale);


        if (goods.getAmout()<nums) {
            throw new  GoodNumsException("库存不足");
        }else {
            Goods goods1 = new Goods();
            goods1.setgId(gId);
            goods1.setAmout(goods.getAmout()-nums);
            goodsDao.updateGoodsByGId(goods1);
        }
        return true;
    }
}

注:@Transactional相应属性:

xRzl5V.png

6、编写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是核心配置的根标签,表示配置的意思
-->
<configuration>
 <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
         </settings>
    <typeAliases>
            <package name="com.lwg.domain"/>
    </typeAliases>
    </configuration>

6、编写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" xmlns:tx="http://www.springframework.org/schema/tx"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:property-placeholder location="JdbcConfig.properties"/>
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.userName}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="myBatisConfig.xml"/>
        <property name="dataSource" ref="druidDataSource"/>
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.lwg.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
    </bean>
    <bean id="payService" class="com.lwg.service.impl.PayServiceImpl">
        <property name="goodsDao" ref="goodsDao"/>
        <property name="saleDao" ref="saleDao"/>
    </bean>
<!--    注册事务管理器对象到spring容器中-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>
<!--    开启事务注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

JdbcConfig.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_task? useUnicode=true&amp;characterEncoding=utf8
jdbc.userName=root
jdbc.password=xxxxxx
Spring事务管理实现基于纯注解

将spring核心配置文件换成下面的两个配置类

JdbcConfig配置类

package com.lwg.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

/**
 * @author lwg
 * @title
 * @create 2022/10/25 21:29
 */
public class JdbcConfig {
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.userName}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;
    @Bean
    public DruidDataSource druidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(userName);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }
}

SpringConfig配置类

package com.lwg.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import sun.reflect.generics.tree.FieldTypeSignature;

import java.lang.reflect.Field;

/**
 * @author lwg
 * @title
 * @create 2022/10/25 21:25
 */
@Configuration
@PropertySource("classpath:JdbcConfig.properties")
@ComponentScan(basePackages = "com.lwg",useDefaultFilters = true,excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)
})
//开启事务驱动
@EnableTransactionManagement
@Import(JdbcConfig.class)
public class SpringConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DruidDataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setTypeAliasesPackage("com.lwg.domain");
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.lwg.dao");
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
        return mapperScannerConfigurer;
    }
//    注册事务管理器
    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DruidDataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

注:

使用注解@EnableTransactionManagement开启事务驱动

service层使用@Service注解配置成Bean,goodsDao和saleDao开启自动注入。

Aspectj的AOP配置事务管理

Aspectj的AOP适合大项目,有很多类、方法需要配置大量的事务。在spring配置文件中声明类,方法需要的事务,这种业务方法和事务配置完全分离。

案例,还是使用上面的转帐业务

1、导依赖

<!--      Aspectj的AOP-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>5.2.5.RELEASE</version>
      </dependency>
      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.9.7</version>
      </dependency>

2、编写实体类、dao层、service层

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

    <bean id="payService" class="com.lwg.service.impl.PayServiceImpl">
        <property name="goodsDao" ref="goodsDao"/>
        <property name="saleDao" ref="saleDao" />
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.lwg.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
    </bean>
    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="myBatis-config.xml"/>
    </bean>
    <context:property-placeholder location="JdbcConfig.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.userName}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
<!--    注册事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--    配置方法的事务行为-->
    <!--
        id:自定义名称
        transaction-manager:事务管理器的id
    -->
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--
                方法可以有多个,可以设置多个<tx:method>
                name:设置事务的方法名,这里不是全限定类名也就是不带包名,可以使用通配符*,表示任意字符
                propagation:设置事务的传播行为
                isolation:设置隔离级别
                rollback-for:指定异常类,发生指定异常类一定回滚
                read-only:设置是否只读
                no-rollback-for:设置哪些异常类不回滚
                timeout;设置超时时间
            -->
            <tx:method name="trade" propagation="REQUIRED" isolation="DEFAULT" rollback-for="com.lwg.exception.GoodNumsException,com.lwg.exception.GoodsNoException"
            read-only="false" no-rollback-for="" timeout="-1"
            />
        </tx:attributes>
    </tx:advice>
<!--    配置AOP切面,指定哪些类要创建代理对象-->
    <aop:config>
<!--        配置切点-->
        <aop:pointcut id="myCut" expression="execution(* com.lwg.service.*.*(..))"/>
<!--        配置通知与切入点关联-->
<!--        aop:advisor是使用spring写好的切面-->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="myCut"/>
    </aop:config>
</beans>

web项目获取Spring容器

之前的项目获取都是javaSE项目中,项目中是有主方法的,执行代码就是执行我们的主方法,在主方法中获取spring容器。

但在web项目中,代码是运行在服务器中的,没有主方法的。

在web项目中,要解决的就是在表现层(web层)获取到spring容器

案例

web项目-用户注册功能

1、导依赖(web项目)

<!--      使用监听器-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
          <version>5.2.5.RELEASE</version>
      </dependency>
      <!--      jsp-->
            <dependency>
              <groupId>javax.servlet.jsp</groupId>
              <artifactId>jsp-api</artifactId>
              <version>2.2</version>
            </dependency>
      <!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
      <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>servlet-api</artifactId>
          <version>2.5</version>
      </dependency>

2、根据数据表编写对应的实体类

User类

package com.lwg.domain;



/**
 * @author lwg
 * @title
 * @create 2022/10/26 0:08
 * 用户实体类
 */
public class User {
    private Integer id;
    private String name;
    private String pwd;

    public User() {

    }

    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 getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

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

3、编写dao层,编写UserDao接口、UserDao.xml

UserDao接口

package com.lwg.dao;

import com.lwg.domain.User;

/**
 * @author lwg
 * @title
 * @create 2022/10/26 9:06
 */
public interface UserDao {
    public Integer insertUser(User user);
}

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.lwg.dao.UserDao">
    <insert id="insertUser" parameterType="user">
        INSERT INTO USER(NAME,pwd) VALUES(#{name},#{pwd})
    </insert>
</mapper>

4、编写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是核心配置的根标签,表示配置的意思
-->
<configuration>
<settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--        <setting name="localCacheScope" value="STATEMENT"/>-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <typeAliases>
            <package name="com.lwg.domain"/>
    </typeAliases>
    </configuration>

5、编写service层,UserService接口以及实现类

UserService接口

package com.lwg.service;

import com.lwg.domain.User;

/**
 * @author lwg
 * @title
 * @create 2022/10/26 21:38
 */
public interface UserService {
    public Boolean addUser(User user);
}

UserServiceImpl

package com.lwg.service.impl;

import com.lwg.dao.UserDao;
import com.lwg.domain.User;
import com.lwg.service.UserService;

/**
 * @author lwg
 * @title
 * @create 2022/10/26 21:38
 */
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public Boolean addUser(User user) {
        Integer result = userDao.insertUser(user);
        if (result>0) {
            return true;
        }
        return false;
    }
}

6、编写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:cotext="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">
    <bean id="userService" class="com.lwg.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.lwg.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
    </bean>
    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="dataSource" ref="druidDataSource"/>
    </bean>
    <cotext:property-placeholder location="classpath*:JdbcConfig.properties"/>
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.userName}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

JdbcConfig.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_task?uuseUnicode=true&characterEncoding=UTF-8
jdbc.userName=root
jdbc.password=xxxxxx

7、编写controller层

package com.lwg.controller;

import com.lwg.domain.User;
import com.lwg.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.context.WebApplicationContext;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * @author lwg
 * @title
 * @create 2022/10/26 22:19
 */
@WebServlet("/register")
public class UserController extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        String name = req.getParameter("name");
        String pwd = req.getParameter("pwd");
        User user = new User();
        user.setName(name);
        user.setPwd(pwd);
        System.out.println("user = " + user);

       /*
        这样创建spring容器每次请求的创建一个spring容器,很容易发生内存溢出
        ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = ctx.getBean("userService", UserService.class);

        */
        /*
        * 采用监听器的形式,web容器启动就创建spring容器并放在servletContext对象里面
        * */
        WebApplicationContext ctx = (WebApplicationContext) getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
        UserService userService = ctx.getBean("userService", UserService.class);
        HttpSession session = req.getSession();
        String resultString="注册失败,请重试!";
        if (userService.addUser(user)) {
            resultString="注册成功,请登录!";
        }
        session.setAttribute("resultString",resultString);
        req.getRequestDispatcher("/index.jsp").forward(req,resp);
    }
}

注:上面使用了监听器的方式创建spring容器,监听web容器创建的时候就创建spring容器为将放在servletContext对象里面,之后就直接取对象就行了。

在web.xml配置监听器(使用框架提供好的ContextLoaderListener)

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
</web-app>

8、编写前端页面

<%--
  Created by IntelliJ IDEA.
  User: 35173
  Date: 2022/10/26
  Time: 0:11
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>用户注册</title>
</head>
<body>
<h2>用户注册</h2>
<form method="post" action="${pageContext.request.contextPath}/register">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="name"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="text" name="pwd"></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="注册"></td>
        </tr>
        <tr>
          <td>${resultString}</td>
        </tr>
    </table>

</form>

</body>
</html>

将案例改纯注解

1、将spring核心配置换成配置类,将业务层使用注解@Service定义成Bean。

2、将web.xml监听器监听上下文资源换成配置类

x4hZYd.png换成下面

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
</web-app>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

广深度优先

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值