问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Spring框架三大特性详解(依赖注入、控制反转、面向切面)

创作时间:
作者:
@小白创作中心

Spring框架三大特性详解(依赖注入、控制反转、面向切面)

引用
CSDN
1.
https://blog.csdn.net/m0_73051910/article/details/127541231

业精于勤而荒于嬉,行成于思而毁于随。
共勉!

一、控制反转(IOC)

Spring IOC容器

控制反转就是把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
Spring IOC 负责创建对象,管理对象(通过依赖注入(DI)装配对象,配置对象,并且管理这些对象的整个生命周期。

控制反转的作用

  1. 管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系毕竟复杂的时候,如果依赖关系需要程序员来维护的话,是很头疼的。
  2. 解耦,由容器去维护具体的对象。
  3. 托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的。

IOC的优点是什么?

  1. IOC 或 依赖注入把应用的代码量降到最低。
  2. 它使应用容易测试,单元测试不再需要单例和JNDI查找机制。
  3. 最小的代价和最小的侵入性使松散耦合得以实现。
  4. IOC容器支持加载服务时的饿汉式初始化和懒加载。

Spring IOC实现机制

工厂模式+反射机制

1 interface Fruit {
2 public abstract void eat();
3 }
4
5 class Apple implements Fruit {
6 public void eat(){
7 System.out.println("Apple");
8 }
9 }
10
11 class Orange implements Fruit {
12 public void eat(){
13 System.out.println("Orange");
14 }
15 }
16
17 class Factory {
18 public static Fruit getInstance(String ClassName) {
19 Fruit f=null;
20 try {
21 f=(Fruit)Class.forName(ClassName).newInstance();
22 } catch (Exception e) {
23 e.printStackTrace();
24 }
25 return f;
26 }
27 }
28
29 class Client {
30 public static void main(String[] a) {
31 Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple");
32 if(f!=null){
33 f.eat();
34 }
35 }
36 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

Spring IoC支持的功能

Spring 的 IoC 设计支持以下功能:依赖注入依赖检查自动装配支持集合指定初始化方法和销毁方法
支持回调某些方法(但是需要实现 Spring 接口,略有侵入);其中,最重要的就是依赖注入,从 XML 的配置上说,即 ref 标签。对应 Spring RuntimeBeanReference 对象。
对于 IoC 来说,最重要的就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。

二、依赖注入(DI)

什么是依赖注入

依赖注入:相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入
(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。

依赖注入的基本原则

依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造器传递给需要的对象。

依赖注入的优势

依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造器或者接口,使容器可以在初始化时组装对象的依赖关系。
相比较于依赖查询,其优势在于:
1,查找定位操作与应用代码完全无关。
2,不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。
3,不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。

依赖注入的两种方式

构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
注意:还有一个接口注入的方式,但是其代码侵入性较大,已经在spring4被废弃使用。

两种依赖注入方式的比较

三、面向切面(AOP)

什么是AOP?

AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。

添加的依赖

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

Spring AOP的实现方式

Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
1,JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的
代理对象。
2,如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方
式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

AOP中的名词

(1) 切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在SpringAOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
(2) 连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
(3) 通知(Advice):在AOP术语中,切面的工作被称为通知。
(4) 切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
(5) 引入(Introduction):引入允许我们向现有类添加新方法或属性。
(6) 目标对象(Target Object): 被一个或者多个切面(aspect)所通知
(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知
(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
(7) 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:
编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。

Spring 通知类型有哪些

在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过
SpringAOP框架触发的代码段。
Spring切面可以应用5种类型的通知:

  1. 前置通知(Before):在目标方法被调用之前调用通知功能;
  2. 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
  3. 返回通知(After-returning ):在目标方法成功执行之后调用通知;
  4. 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  5. 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

同一个aspect,不同advice的执行顺序:
①没有异常情况下的执行顺序:
around before advice before advice target method 执行 around after advice after advice afterReturning
②有异常情况下的执行顺序: around before advice before advice target method 执行 around after advice after advice
afterThrowing:异常发生 java.lang.RuntimeException: 异常发生

其中用的最多的还是环绕通知!!
例如用AOP实现一个统计业务方法执行耗时的切面类:

package com.qd.yemall.product.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * 统计业务方法执行耗时的切面类
 */
@Slf4j
@Aspect
@Component
public class TimerAspect {

    // 在AOP中,有多种Advice(通知)
    // @Around:包裹,可以实现在连接点之前和之后均自定义代码
    // @Before:在连接点之前执行
    // @After:在连接点之后执行,无论是正常返回还是抛出异常都会执行
    // @AfterReturning:在连接点返回之后执行
    // @AfterThrowing:在连接点抛出异常之后执行
    // 仅当使用@Around时,方法才可以自行处理ProceedingJoinPointer
    // 各Advice的执行大概是:
    // @Around
    // try {
    //   @Before
    //   连接点方法
    //   @AfterReturning
    // } catch (Throwable e) {
    //   @AfterThrowing
    // } finally {
    //   @After
    // }
    // @Around
    // ---
    // 关于ProceedingJoinPoint
    // 必须调用proceed()方法,表示执行表达式匹配到的方法
    // 调用proceed()方法必须获取返回值,且作为当前方法的返回值,表示返回表达式匹配的方法的返回值
    // 调用proceed()方法时的异常必须抛出,不可以使用try...catch进行捕获并处理
    // ---
    // 关于execution表达式:用于匹配在何时执行AOP相关代码
    // 表达式中的星号:匹配任意内容,只能匹配1次
    // 表达式中的2个连续的小数点:匹配任意内容,可以匹配0~n次,只能用于包名和参数列表部分
    // 表达式中的包是根包,会自动包含其子孙包中的匹配项
    @Around("execution(*  com.qd.yemall.product.service.*.*(..))")
    //                 ↑ 无论方法的返回值类型是什么
    //                                                  ↑ 无论是哪个类
    //                                                     ↑ 无论是哪个方法
    //                                                       ↑ 2个小数点表示任何参数列表
    public Object timer(ProceedingJoinPoint pjp) throws Throwable {
        log.debug("执行了TimerAspect中的方法……");

        long start = System.currentTimeMillis();
        Object result = pjp.proceed(); // 执行连接点方法,获取返回结果
        long end = System.currentTimeMillis();

        log.debug("【{}】类型的对象调用了【{}】方法,方法的参数值为【{}】",
                pjp.getTarget().getClass().getName(),
                pjp.getSignature().getName(),
                pjp.getArgs());
        log.debug("执行耗时:{}毫秒", end - start);

        return result; // 返回调用pjp.proceed()时的结果
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号