工作三年,小胖问我什么是 Spring 的 IoC 和 AOP?真的菜!

简介: 工作三年,小胖问我什么是 Spring 的 IoC 和 AOP?真的菜!

Spring 的 IoC 和 DI


什么是 IoC 、DI


IoC( Inversion of Control)译为「控制反转」,它不是一个具体的技术,而是一种设计思想。「IoC 的作用是颠倒控制流」。没有它之前,开发者需要自行创建并销毁对象,有了它之后这些工作都交给 Spring 处理。我们直接拿来用即可。


举个栗子:找女朋友。你会有自己的要求,比如身高、体重、年龄等等,然后一个个去找符合条件的对象,贼繁琐。而 IoC 就相当于,你把要求告诉婚介中心(Spring),由 Spring 直接匹配符合条件的对象,匹配到之后你直接带回成亲即可。


IoC 是从容器角度考虑的一个概念,控制权从对象移交到了 IoC 容器。所以称为「控制反转」。还有个概念叫 DI,初学我觉得它两是一个货色。但其实 「 DI 是 IoC 设计思想的具体表现:被注入对象依赖 IoC 容器配置依赖对象」


DI (Dependency Injection)译为「依赖注入」表示对象之间的依赖关系交由容器在运行期间自动生成。由容器动态的将某个依赖关系注入到对象之中,我们只需要简单的配置(注解),就可指定目标对象需要的依赖对象,完成业务逻辑。并不需要关心依赖对象的来源。


IoC 的优点是啥?


  • 使用方便,拿来即用。不需显示管理 Bean 的生命周期
  • 解耦,低侵入设计。降低业务对象替换的复杂度
  • 提供了单例模式的支持,不用自己写了
  • 更符合面向对象的设计


IoC 的注入方式


有三种:构造方法注入、Setter 方法注入、接口注入。接口注入用的很少,不再讨论了。说说前两者。


构造方法注入


构造方法注入主要是依赖于构造方法去实现,构造方法可以是有参的也可以是无参的,我们平时 new 对象时就是通过类的构造方法来创建类对象的,每个类对象默认会有一个无参的构造方法,Spring 通过构造方法注入的代码示例如下:


public class Student {
    // 无参构造
    public Student() {
    }
    // 有参构造
    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }
    private String id;
    private String name;
    // get、set省略
}


定义好类之后,接下来就是提要求了。你想要怎么样的学生?比如,我想要名叫 Java 的学生 applicationContext.xml 配置如下:


<bean id="student" class="com.nasus.ioc.construct.Student">
 <constructor-arg value="24" type="java.lang.String"></constructor-arg>
 <constructor-arg value="Java" type="java.lang.String"></constructor-arg>
</bean>


Setter 注入


Setter 在 Spring 主流的注入方式,主要通过 Java Bean 规范所定义的 Setter/Getter 方法来完成注入,代码简单、可读性很高。示例如下:


<bean id="student" class="com.nasus.ioc.setter.Student">
 <property name="id" value="24"/>
 <property name="name" value="Java"/>
</bean>


接口注入


跳过吧,这方式很古老。反正我学这么久都没用过,也没去了解过。


Spring 的 AOP


什么是 AOP


AOP(Aspect Oriented Programming)译为面向切面编程,它是 OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP 引入封装、继承和多态性等概念来建立一种公共对象处理的能力,当我们需要处理公共行为的时候,OOP 就会显得无能为力,而 AOP 的出现正好解决了这个问题。比如统一的日志处理模块、授权验证模块等都可以使用 AOP 很轻松的处理。

AOP 的一些概念


要理解 AOP 首先要认识以下相关术语,有这么个场景,我需要给用户模块的增删改查,实现日志功能,我现在通过这个场景来解释 AOP 的一些概念。


「连接点(joinpoint)」:被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中连接点指的就是被拦截到的方法。场景中,连接点就是增删改查方法本身。


「通知(advice)」:拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类:


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


「切点(pointcut)」:对连接点进行拦截的定义,它会匹配通知所要织入的一个或多个连接点。它的格式是这样的(@AspectJ 注解方式):


640.png


「切面(aspect)」:切面就是对横切关注点的抽象,它定义了切点和通知。场景中,日志功能就是这个抽象,它定义了你要对拦截方法做什么?


「切面是通知和切点的结合。通知和切点共同定义了切面的全部内容 —— 它是什么,在何时和何处完成其功能」


「织入(weave)」:将切面应用到目标对象并导致代理对象创建的过程


「引入(introduction)」:在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段


AOP 的配置方式


Spring 提供了 Java API、@AspectJ(Java)注解以及 XML标签等三种 AOP 配置方式。


Java API 方式


这种方式要实现相关接口,比如,前置通知接口:MethodBeforeAdvice 和 后置通知接口:AfterReturningAdvice,代码如下:


public class Student {
    // 无参构造
    public Student() {
    }
    // 有参构造
    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }
    public Student getStudent(){
        Student student = new Student("24", "JDK");
        System.out.println("getStudent 被执行了");
        return student;
    }
    private String id;
    private String name;
    // get、set省略
}


再定义一个 advice 通知 类,用于对拦截方法的调用之前和调用之后进行相关的业务处理,实现代码如下:


public class MyAdvice implements MethodBeforeAdvice, AfterReturningAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("准备执行方法: " + method.getName());
    }
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(method.getName() + " 方法执行结束");
    }
}


在 application.xml 文件中配置相应的拦截规则,配置如下:


<!-- 定义 advisor -->
<bean id="myAdvice" class="com.nasus.aop.javaapi.MyAdvice"></bean>
<!-- 配置规则,拦截方法名称为 get* -->
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
 <property name="advice" ref="myAdvice"></property>
 <property name="pattern" value="com.nasus.javaapi.*.get.*"></property>
</bean>
<!-- 定义 DefaultAdvisorAutoProxyCreator 使所有的 advisor 配置自动生效 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>


以上,我们需要拦截方法的规则,然后定义一个 DefaultAdvisorAutoProxyCreator 让所有的 advisor 配置自动生效。


最后,使用测试代码来完成调用:


public class MyApplication {
   public static void main(String[] args) {
      ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application.xml");
      Student student = context.getBean("student", Student.class);
      Student.getStudent();
   }
}


运行结果:可见 AOP 拦截成功


准备执行方法: getStudent
getStudent 被执行了
getStudent 方法执行结束


@AspectJ 注解方式


pom 文件里面添加 aspectjweaver 的 jar 包,配置如下:


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


因为 Spring 使用了 AspectJ 提供的一些注解,因此需要添加此 jar 包。之后,我们需要开启 @AspectJ 的注解,开启方式有两种。


application.xml 中开启 @AspectJ 注解:


<aop:aspectj-autoproxy/>


使用 @EnableAspectJAutoProxy 注解开启:


@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}


import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class MyAspectJ {
    // 配置拦截类 Student
    @Pointcut("execution(* com.nasus.aop.aspectj.beans.Student.*(..))")
    public void pointCut() {}
    @Before("pointCut()")
    public void doBefore() {
        System.out.println("执行 doBefore 方法");
    }
    @After("pointCut()")
    public void doAfter() {
        System.out.println("执行 doAfter 方法");
    }
}


然后在 application.xml 配置中添加注解类,配置如下:


<bean class="com.nasus.aop.aspectj.advice.MyAspectJ"/>


Student 类:


public class Student {
    // 无参构造
    public Student() {
    }
    // 有参构造
    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }
    public Student getStudent(){
        Student student = new Student("24", "JDK");
        System.out.println("getStudent 被执行了");
        return student;
    }
    private String id;
    private String name;
    // get、set省略
}


测试类:


import org.springframework.beans.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application.xml");
        Student student = context.getBean("student",Student.class);
      Student.getStudent();
    }
}


运行结果:


执行 doBefore 方法
执行 getStudent 方法
执行 doAfter 方法


XML标签方式


与注解方式类似,只是不需注解,把相关信息配置到 application.xml 中即可,配置如下:


<!-- 拦截处理类 -->
<bean id="myPointcut" class="com.nasus.aop.xml.advice.MyPointcut"></bean>
<aop:config>
 <!-- 拦截规则配置 -->
 <aop:pointcut id="pointcutConfig"
                    expression="execution(* com.nasus.aop.xml.beans.Student.*(..))"/>
 <!-- 拦截方法配置 -->
 <aop:aspect ref="myPointcut">
  <aop:before method="doBefore" pointcut-ref="pointcutConfig"/>
  <aop:after method="doAfter" pointcut-ref="pointcutConfig"/>
 </aop:aspect>
</aop:config>


建立切点进行拦截业务的处理,实现代码如下:


public class MyPointcut {
   public void doBefore() {
      System.out.println("执行 doBefore 方法");
   }
   public void doAfter() {
      System.out.println("执行 doAfter 方法");
   }
}


测试方法跟 @AspectJ 注解方式是一致的,就不贴出来了。运行结果如下:


执行 doBefore 方法
执行 getStudent 方法
执行 doAfter 方法


AOP 的原理


它的原理其实不难,核心就是「动态代理」,在调用 getBean () 方法的时候返回的其实是代理类的实例,而这个代理类在 Spring 中使「用的是 JDK Proxy 或 CgLib 实现的」,它的核心代码在 DefaultAopProxyFactory.createAopProxy (...) 中,源码如下:


public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class < ? > targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation.");
            }
            // 判断目标类是否为接口
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                // 是接口,使用 jdk 的代理
                return new JdkDynamicAopProxy(config);
            }
            // 其他情况使用 CgLib 代理
            return new ObjenesisCglibAopProxy(config);
        } else {
            return new JdkDynamicAopProxy(config);
        }
    }
    // 忽略其他代码
}


巨人的肩膀



总结


这篇聊了 IoC、DI 的概念,优点以及三种注入方式;AOP 的概念,三种配置方式,最后还通过源码揪出它的核心实现,其实就是动态代理,对动态代理有兴趣的看:工作三年,小胖问我什么是动态代理?真的菜!

相关文章
|
2月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
376 0
|
29天前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
29天前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
6月前
|
监控 安全 Java
Spring AOP实现原理
本内容主要介绍了Spring AOP的核心概念、实现机制及代理生成流程。涵盖切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)等关键概念,解析了JDK动态代理与CGLIB代理的原理及对比,并深入探讨了通知执行链路和责任链模式的应用。同时,详细分析了AspectJ注解驱动的AOP解析过程,包括切面识别、切点表达式匹配及通知适配为Advice的机制,帮助理解Spring AOP的工作原理与实现细节。
|
3月前
|
人工智能 监控 安全
Spring AOP切面编程颠覆传统!3大核心注解+5种通知类型,让业务代码纯净如初
本文介绍了AOP(面向切面编程)的基本概念、优势及其在Spring Boot中的使用。AOP作为OOP的补充,通过将横切关注点(如日志、安全、事务等)与业务逻辑分离,实现代码解耦,提升模块化程度、可维护性和灵活性。文章详细讲解了Spring AOP的核心概念,包括切面、切点、通知等,并提供了在Spring Boot中实现AOP的具体步骤和代码示例。此外,还列举了AOP在日志记录、性能监控、事务管理和安全控制等场景中的实际应用。通过本文,开发者可以快速掌握AOP编程思想及其实践技巧。
|
3月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
3月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
5月前
|
XML 人工智能 Java
Spring IOC 到底是什么?
IOC(控制反转)是一种设计思想,主要用于解耦代码,简化依赖管理。其核心是将对象的创建和管理交给容器处理,而非由程序直接硬编码实现。通过IOC,开发者无需手动new对象,而是由框架负责实例化、装配和管理依赖对象。常见应用如Spring框架中的BeanFactory和ApplicationContext,它们实现了依赖注入和动态管理功能,提升了代码的灵活性与可维护性。
182 1
|
XML Java 数据格式
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
|
6月前
|
XML Java 数据格式
Spring IoC容器的设计与实现
Spring 是一个功能强大且模块化的 Java 开发框架,其核心架构围绕 IoC 容器、AOP、数据访问与集成、Web 层支持等展开。其中,`BeanFactory` 和 `ApplicationContext` 是 Spring 容器的核心组件,分别定位为基础容器和高级容器,前者提供轻量级的 Bean 管理,后者扩展了事件发布、国际化等功能。