Spring入门框架学习笔记

文章由B站动力站点相关课程视频整理而成,此博客为学习备忘笔记。

第一章 Spring概述

1.1 概述

Spring:出现在2002年左右,降低企业级开发难度。帮助进行模块之间、类与类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
2003年传入国内,被大量使用。
2017年出新的流行框架SpringBoot,核心思想与Spring相同。
核心技术:IoC、AOP,能使模块之间、类之间耦合。
依赖:class A使用class B的属性或方法,称之为classA 依赖class B。
官网:spring.io

1.2 优点

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

1.3 Spring体系结构

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

第二章 IoC控制反转

控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器类实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现兑现的创建,属性赋值,依赖的管理。
IoC的实现
1.依赖查找:DL(Dependency Lookup),容器提供回调接口和上下文环境给组件
2.依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。
Spring 框架使用依赖注入 (DI) 实现IoC。

2.1 Spring的第一个程序

2.1.1 创建Maven项目,导入Spring依赖

(Maven项目采用quickstart模版,建立好resources目录)

pom.xml配置文件:

  <dependencies>
    <!--单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.8.RELEASE</version>
    </dependency>
  </dependencies>

2.1.2 创建业务接口和实现类

接口:

package Arbin.service;

public interface SomeService {
    void doSome();
}

实现类:

package Arbin.service.impl;

import Arbin.service.SomeService;

public class SomeServiceImpl implements SomeService {

    public SomeServiceImpl() {
        System.out.println("SomeServiceImpl的无参构造方法");
    }

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

2.1.3 创建Spring配置文件

如同在Servlet中我们需要在web.xml中注册我们希望服务器自动创建管理的sewrvlet对象一样,在Spring中也需要蕾丝的配置,来自动创建刚才的SomeServiceImpl对象。

在src/main/resources目录下创建一个名为“applicationContext"的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">

</beans>

在Spring配置文件中:
<beans>是根标签,Spring把java对象称为bean。用bean标签声明对象:

<bean id="someService" class="Arbin.service.impl.SomeServiceImpl"/>

1.声明java对象交给Spring创建和管理,这个步骤称之为声明bean。
<bean>相当于:SomeService someService = new SomeServiceImp();。Spring把创建好的对象放入map中:springMap.put(id的值,对象)。例如:
springMap.put("someService" , new SomeServiceImpl());
2.<bean>标签的属性:
id:对象的自定义名称,是唯一值。Spring通过这个名称找到对象。
class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类)。
scope:指定bean对象的作用域,(对象的存在范围和可见性。)可取值:

  • 单例:singleton,默认值,表示叫这个名称的对象在spring容器中只有一个。
  • 原型:prototype,表示每次使用getBean()都创建一个新的对象。

2.1.4 创建单元测试方法

pom.xml加入依赖:

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

测试方法:

@Test
public void test02(){
    //使用spring容器创建的对象
    //1.指定spring配置文件的名称
    String config = "beans.xml";
    //2.创建表示spring容器的对象 , ApplicationContext
    //ApplicationContext表示Spring容器,通过容器对象获取对象了
    //ClassPathXmlApplicationContext:表示从类路径中加载spring的配置文件
    //FileSystemXmlApplicationContext:表示从项目的根之下(src、target同级目录)加载spring的配置文件
    ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

    //从容器中获取某个对象,调用对象的方法
    //getBean("<bean>的id")
    SomeService someService = (SomeService) ctx.getBean("someService");

    //调用业务方法
    someService.doSome();
    }

2.1.5 第二章未更新完整 后续补充完整笔记…

第三章 AOP面向切面编程

3.1 动态代理

动态代理:程序在整个运行过程中根本就不存在目标类和代理类,目标对象的代理对象只是由代理生成工具(不是真实定义的类)在程序运行时由JVM根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。

1.实现方式

  • jdk动态代理:使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。jdk动态代理要求目标类必须实现接口。
  • cglib动态代理:第三方的工具库,创建代理对象,原理是集成。通过集成目标类,创建子类。子类就是代理对象。要求目标类不能是final的,方法也不能说final的。

2.动态代理的作用

  • 在目标类源代码不改变的情况下,增加功能。
  • 减少代码的重复
  • 专注业务逻辑代码
  • 解耦合,让业务功能和日志,事务非业务功能分离。

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

3.2 AOP概述

面向切面编程,是面向对象编程 OOP 的一种补充。面向对象编程是从静态角度考虑程序的结构,而面向切面编程是从动态角度考虑程序运行过程。

AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理。

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

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

3.3 AOP编程术语

(1)切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面有通知与顾问。实际就是对主业务逻辑的一种增强。
(2)织入(Weaving)
织入是指将切面代码插入到目标对象的过程。上例中 MyInvocationHandler 类中的 invoke()
方法完成的工作,就可以称为织入。
(3)连接点(JoinPoint)
连接点指可以被切面织入的方法。通常业务接口中的方法均为连接点。
(4)切入点(Pointcut)
切入点指切面具体织入的方法。在 StudentServiceImpl 类中,若 doSome()将被增强,而 doOther()不被增强,则 doSome()为切入点,而 doOther()仅为连接点。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
(5)目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。
(6)通知(Advice)
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。
(7)顾问(Advisor)
顾问是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知 包装为更复杂切面的装配器。

3.4 AOP的实现

AOP的技术实现框架:

  1. Spring:Spring在内部实现了AOP规范,能做AOP的工作。Spring的AOP实现比较笨重。主要在事务处理时使用AOP。
  2. aspectJ:一个开源的专门做AOP的框架。Spring框架中集成了aspectJ框架,通过Spring就能使用aspectJ的功能。
    aspectJ框架实现AOP的两种方式:
    (1)使用xml配置文件,配置全局事务。
    (2)使用注解。一般在项目开发中使用这种方式。aspectJ有五种注解。

3.5 AspectJ框架d对AOP的实现

3.5.1 Adivice(通知)类型

Advice(通知、增强):切面的执行时间。在aspectJ框架中使用注解表示:
(1)@Before 前置通知
(2)@AfterReturning 后置通知
(3)@Around 环绕通知
(4)@AfterThrowing 异常通知
(5)@After 最终通知

3.5.2 AspectJ的切入点表达式

AspectJ定义了专门的表达式用于指定切入点。表达式的原型为:

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

切入点表达式要匹配的对象就是目标方法的方法名。execution 表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。
在其中可以使用以下符号:
*:0至多个任意字符。
..:用在方法参数中,表示任意多个参数用在包名后,表示当钱包及其子包路径。
+:用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类。

3.5.3 AspectJ的开发环境

  1. 创建Maven项目(quickstart骨架),引入依赖:
  <!--单元测试依赖-->
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  <!---spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.8.RELEASE</version>
    </dependency>
  <!--aspectj依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.8.RELEASE</version>
    </dependency>
  </dependencies>
  1. 引入约束(applicationContext.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">
</beans>

在IDEA中开发,添加<aop:aspectj-autoproxy>标签会自动引入。

3.5.4 AspectJ基于注解的AOP实现

AspectJ提供了以注解方式对于AOP的实现

3.5.4.1 实现步骤
  1. 定义业务接口与实现类
package com.Arbin.bao01;

public interface SomeService {
    void doSome(String name,Integer age);
}
package com.Arbin.bao01;

//目标类
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name,Integer age) {
        //给doSome方法增加一个功能,在doSome()执行之前,输出方法的执行时间
        System.out.println("====目标方法doSome()====");
    }
}
  1. 定义切面POJO类
    在定义的POJO类上添加@Aspect注解,指定当前POJO类为切面。
package com.Arbin.bao01;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
/**
 * @Aspect:是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    /**
     * 定义实现切面功能的方法。
     * 方法的定义要求:
     * 1.公共方法 public
     * 2.方法没有返回值
     * 3.方法名称自定义
     * 4.方法可以用参数,也可以没有参数。
     *  如果有参数,参数不是自定义的,有几个参数类型可以使用。
     */

    /**
     * @Before:前置通知注解
     *     属性:value,切入点表达式,表示切面的功能执行位置。
     *     位置:在方法的上面
     *     特点:
     *     1.在目标方法之前先执行的。
     *     2.不会改变目标方法的执行结果
     *     3.不会影响目标方法的执行。
     */
    @Before(value = "execution(public void com.Arbin.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(){
        //切面执行的功能代码
        System.out.println("切面功能:在目标方法之前输出执行时间:"+ new Date());
    }
}
  1. 注册AspectJ的代理,声明目标对象与切面类对象(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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--把对象交给-spring容器,由spring容器统一创建-,管理对象-->
    <!--声明目标对象-->
    <bean id="someService" class="com.Arbin.bao01.SomeServiceImpl"/>

    <!--声明切面类对象-->
    <bean id="myAspoect" class="com.Arbin.bao01.MyAspect"/>

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

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

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

  1. 测试类中使用目标对象的id.
@Test
public void test01(){
    String config = "applicationContext.xml";
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(config);
    //从容器中获取目标对象
    SomeService proxy = (SomeService) ctx.getBean("someService");
    //通过代理对象执行方法,实现目标方法执行时,增强的功能
    proxy.doSome("lisi",20);
}

测试结果:

切面功能:在目标方法之前输出执行时间:Mon Aug 24 11:50:47 CST 2020
目标方法doSome()

3.5.4.2 AspectJ 通知注解
3.5.4.2.1 @Before 前置通知-增强方法由JoinPoint参数

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

    /**
     * 指定通知方法中的参数:JoinPoint
     * JoinPoint:业务方法,加入切面功能的业务方法
     *     作用是:可以在通知方法中获取方法执行时的信息,例如方法名称、方法的实参。
     *     如果切面功能中需要用到方法的信息,就加入JoinPoint。
     *     JoinPoint的参数值是由框架赋予,必须是第一个位置的参数
     */
    @Before(value = "execution(* *..SomeServiceImpl.doSome(..))")
    public void myBefore(JoinPoint jp){
        //获取方法的完整定义
        System.out.println("方法的签名(定义)="+jp.getSignature());
        System.out.println("方法的名称="+jp.getSignature().getName());
        //获取方法的实参
        Object[] args = jp.getArgs();
        for (Object arg:args){
            System.out.println("参数="+arg);
        }
    }
3.5.4.2.2 @AfterReturning 后置通知-注解由 returning属性

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

  1. 在接口中定义一个有返回值的抽象方法:
String doOther(String name,Integer age);
  1. 在类中实现:
    @Override
    public String doOther(String name, Integer age) {
        System.out.println("====目标方法doOther()====");
        return "abcd";
    }
  1. 在切面类中定义一个切面:

后置通知方法定义格式:
1.public
2.方法没有返回值
3.方法名称自定义
4.4.方法有参数的,推荐是Object,参数名自定义

    /**
     * @AfterReturning:后置通知
     *          属性:1.value 切入点表达式
     *               2.returning:自定义的变量,表示目标方法的返回值的。
     *               自定义变量名必须和通知方法的形参名一样。
     *          位置:在方法定义的上面
     *          特点:
     *           1.在目标方法之后执行的。
     *           2.能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
     *              Object res = doOther();
     *           3.可以修改这个返回值
     */
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")
    public void myAfterReturning(Object res){
        // Object res:是目标方法执行后的返回值,根据返回值做切面的功能处理
        System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);
    } 
3.5.4.2.3 @Around 环绕通知-增强方法由 ProceedingJoinPoint 参数

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

  1. 在接口中定义一个有返回值的抽象方法:
String doFirst(String name, Integer age);
  1. 在目标类中实现该方法:
    @Override
    public String doFirst(String name, Integer age) {
        System.out.println("====业务方法doFirst()====");
        return "doFirst";
    }
  1. 增加切面

环绕通知方法的定义格式:
1.public
2.必须有一个返回值,推荐使用Object
3.方法名称自定义
4.方法有固定参数 ProceedingJoinPoint

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

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

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

        //1.目标方法调用
        if("zhangsan".equals(name)){
            //符合条件,调用目标方法
            result = pjp.proceed(); //method.invoke(); Object result = doFirst();
        }
        System.out.println("环绕通知:在目标方法之后,提交事务");
        //2.在目标方法的前或后加入功能

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

        //返回目标方法的执行结果
        return result;
    }
3.5.4.2.4@AfterThrowing 异常通知-注解中有 throwing 属性

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

  1. 在接口中定义一个方法:
void doSecond();
  1. 在目标类中实现该方法
    @Override
    public void doSecond() {
        System.out.println("执行业务方法doSecond()"+(1/0));//方法中出现除数为0异常
    }
  1. 增加切面

异常通知方法的定义格式:
1.public
2.没有返回值
3.方法名称自定义
4.方法可以没有参数,如果有是JoinPoint

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

无论目标方法是否抛出异常,该增强均会被执行。
特点:无论是否抛出异常,都会在目标方法之后执行。
执行模式:

try{
  SomeServiceImpl.doThird(..)
}catch(Exception e){

}finally{
  myAfter();
}
3.5.4.2.6 @Pointcut 定义切入点

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

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

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

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

3.5.5 AspectJ基于XML的AOP实现

AspectJ除了提供基于注解的AOP的实现外,还提供了以XML方式的实现。
切面就是一个POJO类,而用于增强的方法就是普通的方法。通过配置文件,将切面中的功能增强织入到了目标类的目标方法中。
后续补充相关知识点

第四章 Spring 集成 MyBatis

4.1 集成概述

将Mybatis框架与Spring集成在一起,像一个框架一样使用。主要原理在于IoC能创建对象,可以将Mybatis狂降中的对象交给Spring统一创建,开发人员从Spring中获取对象。
实现 Spring 与 MyBatis的整合常用方式:扫描的 Mapper 动态代理
Spring创建以下对象:

  1. 独立的连接池对象,使用阿里的druid连接池
  2. SqlSessionFactory对象
  3. 创建出Dao对象

4.2 项目流程

  1. MySQL 创建数据库spring-mybatis,新建表student
    建表:
  CREATE TABLE student(
  id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(80) DEFAULT NULL,
  email varchar(100) DEFAULT NULL,
  age int(11) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
  1. 加入依赖与插件
    1)junit (单元测试)
    2)spring-context (spring核心ioc)
    3)spring-tx (spring的事务)
    4)spring-jdbc (spring访问数据库)
    5)mybatis (mybatis依赖)
    6)mybatis-spring (mybatis和spring的集成依赖)
    7)mysql-connector-java (mysql驱动)
    8)druid (国内阿里公司的数据库连接池)

      <dependencies>
      <!--单元测试-->
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
      </dependency>
    <!---spring核心ioc-->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.8.RELEASE</version>
      </dependency>
    <!---spring的事务-->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.2.8.RELEASE</version>
      </dependency>
    <!--spring访问数据库-->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.8.RELEASE</version>
      </dependency>
    <!--mybatis依赖-->
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
      </dependency>
      <!--mybatis和spring集成依赖-->
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.5</version>
      </dependency>
      <!--mysql驱动-->
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.21</version>
      </dependency>
      <!--阿里公司的数据库连接池-->
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.23</version>
      </dependency>
    </dependencies>
    

    如果我们的mapper.xml文件没有放置到src-main-resources下面,是不会被maven build plugin给默认扫描到的。此时需要修改启动的模块的pom文件,在build标签里面加入:

      <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
      </build>
    
  2. 新建实体类 Student

    public class Student {
      //为了方便,属性名和列名设置一样。
      private Integer id;
      private String name;
      private String email;
      private Integer age;
      // onstructor / setter / getter
    
  3. 新建Dao接口

    public interface StudentDao {
     int insertStudent(Student student);
     List<Student> selectStudents();
     }
    
  4. 定义映射文件Mapper

       <?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.Arbin.dao.StudentDao">
    
         <insert id="insertStudent">
             insert into student values (#{id},#{name},#{email},#{age})
         </insert>
    
         <select id="selectStudents" resultType="com.Arbin.entity.Student">
             select id,name,email,age from student order by id desc
         </select>
     </mapper>
    
  5. 定义MyBatis主配置文件

     <?xml version="1.0" encoding="UTF-8" ?>
     <!DOCTYPE configuration
             PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
             "http://mybatis.org/dtd/mybatis-3-config.dtd">
     <configuration>
         <!--设置日志-->
         <settings>
             <setting name="cacheEnabled" value="true"/>
             <setting name="useGeneratedKeys" value="true"/>
             <setting name="defaultExecutorType" value="REUSE"/>
             <setting name="logImpl" value="STDOUT_LOGGING"/>
         </settings>
    
         <!--设置别名-->
         <typeAliases>
             <!--name:实体类所在的包名-->
             <package name="com.Arbin.entity"/>
         </typeAliases>
    
         <!--sql mapper(sql映射文件)的位置-->
         <mappers>
             <package name="com.Arbin.dao"/>
         </mappers>
     </configuration>
    
  6. 新建Service接口和实现类

     package com.Arbin.service;
    
     import com.Arbin.entity.Student;
    
     import java.util.List;
    
     public interface StudentService {
         int addStudent(Student student);
         List<Student> queryStudents();
     }
    
     package com.Arbin.service.impl;
    
     import com.Arbin.dao.StudentDao;
     import com.Arbin.entity.Student;
     import com.Arbin.service.StudentService;
    
     import java.util.List;
    
     public class StudentServiceImpl implements StudentService {
         //引用类型
         private StudentDao studentDao;
    
         //使用set注入,赋值
         public void setStudentDao(StudentDao studentDao) {
             this.studentDao = studentDao;
         }
    
         @Override
         public int addStudent(Student student) {
             int nums =studentDao.insertStudent(student);
             return nums;
         }
    
         @Override
         public List<Student> queryStudents() {
             List<Student> students = studentDao.selectStudents();
             return students;
         }
     }
    
  7. 定义Spring配置文件(声明mybatis对象交由Spring创建)
    1)声明数据源DataSource对象
    2)声明SqlSessionFactoryBean,创建SqlSessionFactory对象
    3)声明MapeperScannerConfigurer扫描器,创建Dao对象
    4)声明自定义的Service

     <?xml version="1.0" encoding="UTF-8"?>
     <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
         <!--将数据库的配置信息写在独立的文件,编译修改数据库的配置内容-->
         <context:property-placeholder location="jdbc.properties"/>
         <!--声明数据源DataSource,作用是连接数据库的-->
         <bean id="muDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="clone">
             <!--set注入给DruidDataSource提供连接数据库信息-->
             <!--数据库的url-->
             <property name="url" value="${jdbc.url}" /> <!--setUrl()-->
             <!--数据库的用户名-->
             <property name="username" value="${jdbc.username}"/>
             <!--数据库访问密码-->
             <property name="password" value="${jdbc.passowrd}"/>
             <!--数据库最大连接数-->
             <property name="maxActive" value="${jdbc.max}"/>
         </bean>
    
         <!--声明SqlSessionFactoryBean类,该类内部创建这创建SqlSessionFactory对象-->
         <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
             <!--set注入,把数据库的连接池赋给dataSource属性-->
             <property name="dataSource" ref="muDataSource"/>
             <!--mybatis主配置文件的位置
                 configLocation属性是Resource类型,读取配置文件
                 赋值使用value,指令文件的路径,使用classpath:表示文件的位置
             -->
             <property name="configLocation" value="classpath:mybatis.xml"/>
         </bean>
    
         <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)
             MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。-->
         <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
             <!--指定SqlSessionFactory对象的id,可以获取SqlSession-->
             <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
             <!--指定Dao接口所在的包名
                 MapperScannerConfigurer会扫描这个包中的所有接口,
                 把每个接口都执行一次getMapper()方法,得到每个接口的dao对象。
                 创建好的dao对象放入到spring容器中
             -->
             <property name="basePackage" value="com.Arbin.dao"/>
         </bean>
    
         <!--声明service-->
         <bean id="studentService" class="com.Arbin.service.impl.StudentServiceImpl">
             <property name="studentDao" ref="studentDao"/>
         </bean>
     </beans>
    

    jdbc.properties:

     jdbc.url=jdbc:mysql://localhost:3306/spring-mybatis
     jdbc.username=root
     jdbc.passowrd=****
     jdbc.max=20
    
  8. 测试类

    package com.Arbin;
    
    import com.Arbin.dao.StudentDao;
    import com.Arbin.entity.Student;
    import com.Arbin.service.StudentService;
    import org.junit.Test;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import java.util.List;
    
    public class MyTest {
        @Test
        public void test01(){
            String config = "applicationContext.xml";
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(config);
            String[] names = ctx.getBeanDefinitionNames();
            for (String name:names){
                System.out.println("容器中对象名称:"+name+"|"+ctx.getBean(name));
            }
        }
    
        @Test
        public void testDaoInsert(){
            String config = "applicationContext.xml";
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(config);
            //获取spring容器中的dao对象
            StudentDao dao = (StudentDao) ctx.getBean("studentDao");
            Student student = new Student();
            student.setId(2);
            student.setName("张三");
            student.setEmail("zhangsan@qq.com");
            student.setAge(20);
            int nums = dao.insertStudent(student);
            System.out.println("nums:"+nums);
        }
    
        @Test
        public void testServiceInsert(){
            String config = "applicationContext.xml";
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(config);
            //获取spring容器中的dao对象
            StudentService service = (StudentService) ctx.getBean("studentService");
            Student student = new Student();
            student.setId(3);
            student.setName("李四");
            student.setEmail("lisi@qq.com");
            student.setAge(20);
            int nums = service.addStudent(student);
            //spring和mybatis整合在一起使用,事务四自动提交的。无需执行SqlSession.commit();
            System.out.println("nums:"+nums);
        }
    
        @Test
        public void testServicesSelect(){
            String config = "applicationContext.xml";
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(config);
            //获取spring容器中的dao对象
            StudentService service = (StudentService) ctx.getBean("studentService");
    
            List<Student> students = service.queryStudents();
            for (Student stu:students)
                System.out.println(stu);
        }
    }
    

第五章 Spring的事务管理

5.1 章节问题

  1. 什么是事务?

    事务是指多条语句的集合,这些语句的执行是一致的并作为一个整体执行。如:在mysql中的事务是指一组sql语句的集合。

  2. 在什么时候使用事务?

    当操作涉及多个表或多个sql语句。需要保证这些语句结果的一致性使得操作是符合要求的。而在java中事务一般在service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句。

  3. 使用JDBC访问数据库或、mybatis访问数据库怎么处理事务?

    连接数据库、处理业务:
    jdbc: Connection conn; conn.commit(); conn.rollback();
    mybatis: SqlSession.commit(); SqlSession.rollback();
    hibernate:Session.commit(); Session.roolback();

  4. 问题3中的事务处理方式,有什么不足

    1)不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理。
    2)掌握多种数据库中事务的处理逻辑。如:提交事务、回滚事务的时间。
    3)处理事务的多种方法。
    多种数据库访问技术具有不同的事务处理机制、对象、方法。

  5. 怎么解决不足

    Spring提供了处理事务的统一模型,能使用统一步骤、方式完成多种不同数据库访问技术的事务处理。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-99efgzZS-1600265386842)(https://s1.ax1x.com/2020/09/01/dvInRH.md.png)]

  6. 处理事务,需要怎么做

    Spring处理事务的模型以及步骤都是固定的、只需将事务所使用的信息提供给Spring。下面该章节介绍具体方法与步骤。

5.2 Spring 事务管理API

Sring的事务管理,主要用到两个事务相关的接口。

5.2.1 事务管理接口(重点)

事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚以及事务的状态信息。PlatformTransactionManager 接口常用的两个实现类:

  • DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使。
  • HibernateTranscationManager:使用 Hibernate 进行持久化数据时使用。

5.2.2 Spring 的回滚方式

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

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

受查异常:也称为编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理,则无法通过编译。如:SQLException、ClassNotFoundException、IOException等均属于受查异常。

5.2.3 事务定义接口

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

  1. 定义了五个事务隔离级别常量
    这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
  • DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默认为 READ_COMMITTED。
  • READ_UNCOMMITTED:读未提交。未解决任何并发问题。
  • READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
  • REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
  • SERIALIZABLE:串行化。不存在并发问题。
  1. 定义了七个事务传播行为常量
    所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情 况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的 维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
    事务传播行为常量都是以 PROPAGATION_ 开头,形如PROPAGATION_XXX。
    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRES_NEW
    PROPAGATION_SUPPORTS
    PROPAGATION_MANDATORY
    PROPAGATION_NESTED
    PROPAGATION_NEVER
    PROPAGATION_NOT_SUPPORTED

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

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

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

5.3 程序举例环境搭建

详细例子请查看:https://blog.csdn.net/qq_43543442/article/details/108346228

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

5.4.1 @Transactional注解方式

通过 @Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。
@Transactional 的所有可选属性如下所示:

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

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

5.4.2 使用 @Transactional 步骤

1. 声明事务管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--连接的数据库,指定数据源-->
    <property name="dataSource" ref="myDataSource"/>
</bean>

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

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

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

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

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

5.5.1 概述

使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类 较多,配置文件会变得非常臃肿。
适用范围:大型项目,大量的类和方法需要大量的配置事务,使用aspectj框架功能,在spring配置文件中声明类,方法需要的事务。这种方式使业务方法和事务配置完全分离。

5.5.2 实现步骤

1. maven 依赖 pom.xml
加入aspectj的依赖

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

2. 声明事务管理器对象

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--连接的数据库,指定数据源-->
    <property name="dataSource" ref="myDataSource"/>
</bean>

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

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

4. 配置aop:指定所要创建代理的类

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

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

第六章 Spring 与 Web

对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个 ServletContext 对象,该对象是在 Web 应用装载时初始化的。若将 Spring 容器的创建时机, 放在 ServletContext 初始化时,就可以保证 Spring 容器的创建只会执行一次,也就保证了 Spring 容器在整个应用中的唯一性。
当 Spring 容器创建好后,在整个应用的生命周期过程中,Spring 容器应该是随时可以被访问的。即,Spring 容器应具有全局性。而放入 ServletContext 对象的属性,就具有应用的 全局性。所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就 保证了 Spring 容器的全局性。
上述的这些工作,已经被封装在了如下的 Spring 的 Jar 包的相关 API 中: spring-web-5.2.5.RELEASE
1. 添加Maven依赖

<!--servlet依赖-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>
<!--jsp依赖-->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2.1-b03</version>
</dependency>
<!--监听器依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

2. 注册监听器ContextLoader

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

3. 指定Spring配置文件中的位置

<!--注册监听器ContextLoaderListener
    监听器被创建对象后,会读取/WEB-INF/spring.xml
    读取文件的原因:因为在监听器中要创建ApplicationContext,需要加载配置文件。
    会读取/WEB-INF/applicationContext.xml是监听器默认读取的spring配置文件路径
    
    可以修改默认默认的文件位置,使用context-param重新指定文件的位置
    -->
<context-param>
    <!-- contextConfigLocation:配置文件的路径-->
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring.xml</param-value>
</context-param>

4. 在Sevlet中获取ApplicationContext对象

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

或使用工具类获取WebApplicationContext:

ServletContext sc = getServletContext();
ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值