【Spring IOC/AOP】
IOC
参考:
Spring基础 - Spring核心之控制反转(IOC) | Java 全栈知识体系 (pdai.tech)
概述:
- Ioc 即 Inverse of Control (控制反转),是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。
- 控制反转是目的,依赖注入是手段,Spring通过DI(依赖注入)实现IOC(控制反转)。
- 控制反转,是一种理论、概念、思想。把代码的创建、赋值、管理工作都交给代码之外的容器实现。
- 控制:创建对象、属性赋值、对象之间的关系管理。
- 反转:把原先开发人员管理、创建对象的权限交给容器去做。
- 正传:由开发人员在代码中使用new构造方主动创建、管理对象。
参与者有哪些?
1)对象
2)IOC/DI容器
3)某个对象的外部资源
依赖,谁依赖谁?为什么需要依赖?
依赖嘛,很好理解的,对象依赖于IOC/DI容器,至于为什么要依赖呢?对象需要IOC/DI 容器来提供对象需要的外部资源。
注入,谁注入谁?又注入了什么呢?
显而易见是IOC/DI容器注入对象,注入了what呢?肯定注入的是某个需要的东西那就 是注入对象所需要的资源,肯定不会注入无关紧要的内容,你说呢?
类A需要类B的时候,直接在A中注入类B。
谁控制谁?
Ioc容器管理控制对象,对象的创建不再是由使用者创建,而是由第三方统一 创建和 管理。
为何要反转?
正转就是正常创建对象,一个个为属性赋值。
哪些方面被反转了?
创建对象的权力被反转了,以前创建对象的权利是资源的使用者,现在由第三方去托管。 实现了解耦。
叙述:
1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IOC 这个概念。对于面向对象设计及编程的基本思想,前面我们已经讲了很多了,不再赘述,简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。
IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。如下图:
大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。
我们再来做个试验:把上图中间的IOC容器拿掉,然后再来看看这套系统:
我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。所以,如果真能实现IOC容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现自己的类就可以了,跟别人没有任何关系!
我们再来看看,控制反转(IOC)到底为什么要起这么个名字?我们来对比一下:
软件系统在没有引入IOC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
软件系统在引入IOC容器之后,这种情形就完全改变了,如图3所示,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
作用:
管理对象的创建和依赖关系的维护。
对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是相当头疼的解耦,由容器去维护具体的对象托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的。
实现原理:
工厂模式 + 反射机制
优缺点:
优点:
- 降低了应用的额代码量。
- 实现组件之间的解耦,提高程序的灵活性和可维护性。
- IOC容器支持加载服务时的饿汉式初始化和懒加载。
缺点:
- 创建对象的步骤变复杂了,不直观,当然这是对不习惯这种方式的人来说的。
- 因为使用反射来创建对象,所以在效率上会有些损耗。但相对于程序的灵活性 和可维护性来说,这点损耗是微不足道的。
- 缺少IDE重构的支持,如果修改了类名,还需到XML文件中手动修改,这似乎 是所有XML方式的缺憾所在。
什么是耦合?
如在web开发中,表现层需要持有业务层的对象,业务层中需要持有持久层的对象,这种(强的)依赖关系叫耦合。
高耦合的缺点:
连锁反应,修改一个模块,多个模块都需要进行修改
当AccountDao的标签名称被修改为时,业务层所有持有AccountDao的实例对象的类 都需要进行修改。如AccountDao被修改为AccountDao1那么所有的业务层的private accountDao = new AccountDao()都要修改成为private accountDao = new AccountDao1()。当业务层的此种类过多时,修改需要大量的时间。
由于模块之间的相依性,模块的组合会需要更多的精力及时间。
解耦思路?
• 第一步:需要一个配置文件来配置我们的service和dao(配置文件可以选用 xml或者properties)
• 第二部:读取配置文件,通过反射创建对象
• 第三部:不需要主动new对象,直接从工厂通过反射获取对象,当需要修改一个对 象名的时候,只需要将配置文件中类的全限定名修改即可。不需要大范围修改业务 层中的代码,这样代码维护起来更加省时。
相关问题:
常用的依赖注入方式有哪几种?
1、构造方法注入
public class Chinese implements Person
{
private final A a;
// 构造方法注入A对象
public Chinese (A a){
this.a= a;
}
}
2、setter注入
在一个类中通过setter注入需要的对象。
public class Chinese implements Person
{
private A a;
// 设值注入所需的setter方法
public void setA(A a){
this.a= a;
}
}
3、基于注解的注入
直接使用注解:Autowired、Resource、Qualifier、Service、Controller、Repository、Component。
什么样的类需要放入spring的IOC容器中?
Dao类、service类、controller类、工具类
什么样的类不需要放入spring的IOC容器中?
实体类对象,servlet等。
AOP
参考:
Spring基础 - Spring核心之面向切面编程(AOP) | Java 全栈知识体系 (pdai.tech)
概述:
(Aspect Oriented Programming)面向切面编程
传统的面向对象编程OOP解决不了为分散对象引入公共行为的问题,而AOP则可以,AOP可以打开封装对象的内部,将那些影响了多个类的公共行为封装到一个可重用模块,称为方面。
AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
详述:
OOP(Object-Oriented Programing,面向对象编程),AOP可以说是OOP的补充和完善, OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
相关术语:
- ①、JoinPoint(连接点):指那些被拦截到的点。(在Spring中这些点指的是方法,因为Spring只支持方法类型的连接点)。
- ②、PointCut(切入点):指的是要对哪些JoinPoint进行拦截的定义。
- ③、Advice(通知/增强):指拦截到JoinPoint之后所要做的事情。
- ④、Target(目标对象):代理的目标对象。
- ⑤、Weaving(织入):指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入;Aspect采用编译器织入和类装载期织入。
- ⑥、Aspect(切面):是切入点和通知的结合。
通知:
前置通知、后置通知、异常通知、最终通知、环绕通知。
前置通知:在切入点方法执行之前执行。
后置通知:在切入点方法执行之后执行。它和异常通知永远只能执行一个。
异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个。
最终通知:无论切入点方法是否正常执行,它都会在其后面执行。
使用分类:
环绕通知:
@Around("execution(* com..*controller..*(..))")
环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的。(也就是接口的业务处理)
执行目标方法之前会进入前置@Before执行,然后执行目标方法,在执行后置,最后返回到环绕,return ewsult。有拦截器的话,最后执行拦截器的afterCompletion。
@Around("execution(* com..*controller..*(..))")
public Object doControllerAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
if (StringUtil.isBlank(MDC.get(TRACE_ID))) {
MDC.put(TRACE_ID, StringUtil.uuid());
}
Object result = proceedingJoinPoint.proceed();
MDC.clear();
return result;
}
前置通知:
@Before(value = "execution(* com..*controller..*(..))")
后置通知:
@After(value = "execution(* com..*controller..*(..))")
@AfterReturning(pointcut = "deleteScheduleJobPointcut()", returning = "rvt")
@AfterThrowing(value = "execution(* com..*controller..*(..))", returning = "retVal")
@AfterReturning(pointcut = "deleteScheduleJobPointcut()", returning = "rvt")
public void afterDeleteScheduleJob(JoinPoint joinPoint, int rvt) {
if (rvt == 1) {
//获取修饰符+ 包名+组件名(类名) +方法名
System.out.println(joinPoint.getSignature());
//获取方法名
System.out.println(joinPoint.getSignature().getName());
//获取包名+组件名(类名)
System.out.println(joinPoint.getSignature().getDeclaringType());
// 获取连接点的参数
TbScheduleJob job = (TbScheduleJob) joinPoint.getArgs()[0];
String json = jacksonUtil.object2Json(job);
log.info("删除任务,通知其他节点,数据:" + json);
Message<String> message = MessageBuilder
.withPayload(json)
.setHeader("KEYS", EVENT_MQ_KEY_DELETE)
.build();
SendResult sendResult = rocketMQTemplate.syncSend(
BaseListener.MCMS_TOPIC
+ EVENT_TAG_SCHEDULE_JOB, message);
log.info("发送到mq,结果[{}]", sendResult.getSendStatus());
}
}
执行顺序:
所有controller环绕切点:
环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的。
拦截器preHandle执行之后进入环绕通知,环绕通知proceedingJoinPoint.proceed()作用是执行目标方法,
执行目标方法之前会进入前置@Before执行,然后执行目标方法,在执行后置,最后返回到环绕,return ewsult,最后执行拦截器的afterCompletion。
package com.eastcom.aop;
import com.eastcom.base.MDCConstants;
import com.eastcom.util.DateUtil;
import com.eastcom.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;
@Slf4j
@Aspect
@Component
public class ControllerAspect {
@Before(value = "execution(* com..*controller..*(..))")
public void before(JoinPoint joinPoint) {
log.info("[start]:------------------------------->");
HttpServletRequest request = ((ServletRequestAttributes)
Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("REQ_TIME[" + DateUtil.formatDate(new Date()) + "],");
stringBuffer.append("URL[" + request.getRequestURL().toString() + "],");
stringBuffer.append("HTTP_METHOD[" + request.getMethod() + "],");
stringBuffer.append("IP[" + request.getRemoteAddr() + "],");
stringBuffer.append("CLASS_METHOD[" + joinPoint.getSignature().getDeclaringTypeName()
+ "." + joinPoint.getSignature().getName() + "],");
if (joinPoint.getArgs() != null) {
stringBuffer.append("ARGS[" + Arrays.toString(joinPoint.getArgs()) + "],");
}
log.info(stringBuffer.toString());
}
@AfterReturning(value = "execution(* com..*controller..*(..))", returning = "retVal")
public void afterreturning(JoinPoint joinPoint, Object retVal) {
HttpServletRequest request = ((ServletRequestAttributes)
Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("RES_TIME[" + DateUtil.formatDate(new Date()) + "],");
stringBuffer.append("RETURN[" + (retVal != null ? retVal.toString() : null) + "],");
log.info(stringBuffer.toString());
log.info("[ end ]:------------------------------->");
}
/**
* 所有controller环绕切点:环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的.
* 拦截器preHandle执行之后进入环绕通知,环绕通知proceedingJoinPoint.proceed()作用是执行目标方法,
* 执行目标方法之前会进入前置@Before执行,然后执行目标方法,在执行后置,最后返回到环绕,return ewsult
* 最后执行拦截器的afterCompletion
*
* @return Object
* @throws Throwable 异常
*/
@Around("execution(* com..*controller..*(..))")
public Object doControllerAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
if (StringUtil.isBlank(MDC.get(MDCConstants.TRACE_ID))) {
MDC.put(MDCConstants.TRACE_ID, StringUtil.uuid());
}
Object result = proceedingJoinPoint.proceed();
MDC.clear();
return result;
}
}
@Pointcut切入点表达式介绍:
1 表达式类型
标准的Aspectj Aop的pointcut的表达式类型是很丰富的,但是Spring Aop只支持其中的9种,外加Spring Aop自己扩充的一种一共是10种类型的表达式,分别如下。
- execution:一般用于指定方法的执行,用的最多。
- within:指定某些类型的全部方法执行,也可用来指定一个包。
- this:Spring Aop是基于代理的,生成的bean也是一个代理对象,this就是这个代理对象,当这个对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
- target:当被代理的对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
- args:当执行的方法的参数是指定类型时生效。
- @target:当代理的目标对象上拥有指定的注解时生效。
- @args:当执行的方法参数类型上拥有指定的注解时生效。
- @within:与@target类似,看官方文档和网上的说法都是@within只需要目标对象的类或者父类上有指定的注解,则@within会生效,而@target则是必须是目标对象的类上有指定的注解。而根据笔者的测试这两者都是只要目标类或父类上有指定的注解即可。
- @annotation:当执行的方法上拥有指定的注解时生效。
- bean:当调用的方法是指定的bean的方法时生效。
2 使用示例
execution是使用的最多的一种Pointcut表达式,表示某个方法的执行,其标准语法如下。
-
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
-
name-pattern(param-pattern) throws-pattern?)
@Pointcut(value = "execution(* com.xxx.service.impl.XXXServiceImpl.mothod01(..))")
public void xxxPointcut() {
}
modifiers-pattern表示方法的访问类型,public等;ret-type-pattern表示方法的返回值类型,如String表示返回类型是String,“*”表示所有的返回类型;declaring-type-pattern表示方法的声明类,如“com.elim..*”表示com.elim包及其子包下面的所有类型;name-pattern表示方法的名称,如“add*”表示所有以add开头的方法名;param-pattern表示方法参数的类型,name-pattern(param-pattern)其实是一起的表示的方法集对应的参数类型,如“add()”表示不带参数的add方法,“add(*)”表示带一个任意类型的参数的add方法,“add(*,String)”则表示带两个参数,且第二个参数是String类型的add方法;throws-pattern表示异常类型;其中以问号结束的部分都是可以省略的。
- 1、“execution(* add())”匹配所有的不带参数的add()方法。
- 2、“execution(public * com.elim..*.add*(..))”匹配所有com.elim包及其子包下所有类的以add开头的所有public方法。
- 3、“execution(* *(..) throws Exception)”匹配所有抛出Exception的方法。
within是用来指定类型的,指定类型中的所有方法将被拦截。
- 1、“within(com.elim.spring.aop.service.UserServiceImpl)”匹配UserServiceImpl类对应对象的所有方法外部调用,而且这个对象只能是UserServiceImpl类型,不能是其子类型。
- 2、“within(com.elim..*)”匹配com.elim包及其子包下面所有的类的所有方法的外部调用。
Spring Aop是基于代理的,this就表示代理对象。this类型的Pointcut表达式的语法是this(type),当生成的代理对象可以转换为type指定的类型时则表示匹配。基于JDK接口的代理和基于CGLIB的代理生成的代理对象是不一样的。
- 1、“this(com.elim.spring.aop.service.IUserService)”匹配生成的代理对象是IUserService类型的所有方法的外部调用。
Spring Aop是基于代理的,target则表示被代理的目标对象。当被代理的目标对象可以被转换为指定的类型时则表示匹配。
- 1、“target(com.elim.spring.aop.service.IUserService)”则匹配所有被代理的目标对象能够转换为IUserService类型的所有方法的外部调用。
args用来匹配方法参数的。
- 1、“args()”匹配任何不带参数的方法。
- 2、“args(java.lang.String)”匹配任何只带一个参数,而且这个参数的类型是String的方法。
- 3、“args(..)”带任意参数的方法。
- 4、“args(java.lang.String,..)”匹配带任意个参数,但是第一个参数的类型是String的方法。
- 5、“args(..,java.lang.String)”匹配带任意个参数,但是最后一个参数的类型是String的方法。
@target匹配当被代理的目标对象对应的类型及其父类型上拥有指定的注解时。
- 1、“@target(com.elim.spring.support.MyAnnotation)”匹配被代理的目标对象对应的类型上拥有MyAnnotation注解时。
@args匹配被调用的方法上含有参数,且对应的参数类型上拥有指定的注解的情况。
- 1、“@args(com.elim.spring.support.MyAnnotation)”匹配方法参数类型上拥有MyAnnotation注解的方法调用。如我们有一个方法add(MyParam param)接收一个MyParam类型的参数,而MyParam这个类是拥有注解MyAnnotation的,则它可以被Pointcut表达式“@args(com.elim.spring.support.MyAnnotation)”匹配上。
@within用于匹配被代理的目标对象对应的类型或其父类型拥有指定的注解的情况,但只有在调用拥有指定注解的类上的方法时才匹配。
- 1、“@within(com.elim.spring.support.MyAnnotation)”匹配被调用的方法声明的类上拥有MyAnnotation注解的情况。比如有一个ClassA上使用了注解MyAnnotation标注,并且定义了一个方法a(),那么在调用ClassA.a()方法时将匹配该Pointcut;如果有一个ClassB上没有MyAnnotation注解,但是它继承自ClassA,同时它上面定义了一个方法b(),那么在调用ClassB().b()方法时不会匹配该Pointcut,但是在调用ClassB().a()时将匹配该方法调用,因为a()是定义在父类型ClassA上的,且ClassA上使用了MyAnnotation注解。但是如果子类ClassB覆写了父类ClassA的a()方法,则调用ClassB.a()方法时也不匹配该Pointcut。
@annotation用于匹配方法上拥有指定注解的情况。
- 1、“@annotation(com.elim.spring.support.MyAnnotation)”匹配所有的方法上拥有MyAnnotation注解的方法外部调用。
bean用于匹配当调用的是指定的Spring的某个bean的方法时。
- 1、“bean(abc)”匹配Spring Bean容器中id或name为abc的bean的方法调用。
- 2、“bean(user*)”匹配所有id或name为以user开头的bean的方法调用。
3 表达式组合
表达式的组合其实就是对应的表达式的逻辑运算,与、或、非。可以通过它们把多个表达式组合在一起。
- 1、“bean(userService) && args()”匹配id或name为userService的bean的所有无参方法。
- 2、“bean(userService) || @annotation(MyAnnotation)”匹配id或name为userService的bean的方法调用,或者是方法上使用了MyAnnotation注解的方法调用。
- 3、“bean(userService) && !args()”匹配id或name为userService的bean的所有有参方法调用。
4 基于Aspectj注解的Pointcut表达式应用
在使用基于Aspectj注解的Spring Aop时,我们可以把通过@Pointcut注解定义Pointcut,指定其表达式,然后在需要使用Pointcut表达式的时候直接指定Pointcut。
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* add(..))")
private void beforeAdd() {}
@Before("beforeAdd()")
public void before() {
System.out.println("-----------before-----------");
}
}
上面的代码中我们就是在@Before()中直接指定使用当前类定义的beforeAdd()方法对应的Pointcut的表达式,如果我们需要指定的Pointcut定义不是在当前类中的,我们需要加上类名称,如下面这个示例中引用的就是定义在MyService中的add()方法上的Pointcut的表达式。
@Before("com.elim.spring.aop.service.MyService.add()")
public void before2() {
System.out.println("-----------before2-----------");
}
当然了,除了通过引用Pointcut定义间接的引用其对应的Pointcut表达式外,我们也可以直接使用Pointcut表达式的,如下面这个示例就直接在@Before中使用了Pointcut表达式。
/**
* 所有的add方法的外部执行时
*/
@Before("execution(* add())")
public void beforeExecution() {
System.out.println("-------------before execution---------------");
}
@Pointcut切入点表达式例子:
// 任意公共方法的执行:
execution(public * *(..))
// 任何一个名字以“set”开始的方法的执行:
execution(* set*(..))
// AccountService接口定义的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))
// 在service包中定义的任意方法的执行:
execution(* com.xyz.service.*.*(..))
// 在service包或其子包中定义的任意方法的执行:
execution(* com.xyz.service..*.*(..))
// 在service包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service.*)
// 在service包或其子包中的任意连接点(在Spring AOP中只是方法执行):
within(com.xyz.service..*)
// 实现了AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行):
this(com.xyz.service.AccountService)// 'this'在绑定表单中更加常用
// 实现AccountService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行):
target(com.xyz.service.AccountService) // 'target'在绑定表单中更加常用
// 任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点(在Spring AOP中只是方法执行)
args(java.io.Serializable) // 'args'在绑定表单中更加常用; 请注意在例子中给出的切入点不同于 execution(* *(java.io.Serializable)): args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个 Serializable类型的参数时候匹配。
// 目标对象中有一个 @Transactional 注解的任意连接点 (在Spring AOP中只是方法执行)
@target(org.springframework.transaction.annotation.Transactional)// '@target'在绑定表单中更加常用
// 任何一个目标对象声明的类型有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行):
@within(org.springframework.transaction.annotation.Transactional) // '@within'在绑定表单中更加常用
// 任何一个执行的方法有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行)
@annotation(org.springframework.transaction.annotation.Transactional) // '@annotation'在绑定表单中更加常用
// 任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点(在Spring AOP中只是方法执行)
@args(com.xyz.security.Classified) // '@args'在绑定表单中更加常用
// 任何一个在名为'tradeService'的Spring bean之上的连接点 (在Spring AOP中只是方法执行)
bean(tradeService)
// 任何一个在名字匹配通配符表达式'*Service'的Spring bean之上的连接点 (在Spring AOP中只是方法执行)
bean(*Service)
此外Spring 支持如下三个逻辑运算符来组合切入点表达式:
&&:要求连接点同时匹配两个切入点表达式
||:要求连接点匹配任意个切入点表达式
!::要求连接点不匹配指定的切入点表达式
相关注解
相关问题:
Spring AOP 和 AspectJ 之间的关键区别?
总结来说就是 Spring AOP更易用,AspectJ更强大。
IOC/AOP模块归属
在 Spring 框架中,IoC(控制反转)和AOP(面向切面编程) 分属不同的核心模块,具体如下:
1. IoC(控制反转)
-
所属模块:
IoC 的核心实现位于 Spring Core Container 模块,具体由以下子模块支持:-
spring-core
:提供 IoC 的基础设施(如BeanFactory
)。 -
spring-beans
:实现 Bean 的定义、创建和管理。 -
spring-context
(上下文模块):扩展BeanFactory
,提供更高级的 IoC 容器(如ApplicationContext
),支持国际化、事件传播等。
-
-
关键功能:
-
通过
BeanFactory
或ApplicationContext
管理对象的生命周期和依赖注入。 -
依赖注入(DI)是 IoC 的核心实现方式,通过
@Autowired
、@Resource
等注解实现。
-
Maven 依赖示例:
xml
复制
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.22</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.3.22</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.22</version> </dependency>
运行 HTML
2. AOP(面向切面编程)
-
所属模块:
AOP 的核心实现位于spring-aop
模块,同时依赖spring-aspects
模块(集成 AspectJ)。 -
关键功能:
-
通过动态代理(JDK Proxy 或 CGLIB)实现方法拦截。
-
支持切面(Aspect)、通知(Advice)、切点(Pointcut)等核心概念。
-
与
@AspectJ
注解风格结合,简化切面编程。
-
Maven 依赖示例:
xml
复制
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.22</version> </dependency> <!-- 集成 AspectJ --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.22</version> </dependency>
运行 HTML
3. 模块关系总结
功能 | 核心模块 | 说明 |
---|---|---|
IoC | spring-core | 基础容器与工具类 |
spring-beans | Bean 的定义与管理 | |
spring-context | 高级容器与应用上下文 | |
AOP | spring-aop | 动态代理与 AOP 核心实现 |
spring-aspects | 集成 AspectJ 注解式切面 |
4. 实际应用
-
IoC 示例:通过
@Component
定义 Bean,由ApplicationContext
管理依赖注入:java
复制
@Service public class UserService { @Autowired private UserRepository userRepository; }
-
AOP 示例:通过
@Aspect
实现日志切面:java
复制
@Aspect @Component public class LogAspect { @Before("execution(* com.example.service.*.*(..))") public void logMethodCall(JoinPoint joinPoint) { System.out.println("方法调用:" + joinPoint.getSignature()); } }
5. 常见问题
-
AOP 不生效:
-
确保启用
@EnableAspectJAutoProxy
(Spring Boot 中默认启用)。 -
检查切面类是否被 Spring 容器扫描到(如添加
@Component
)。
-
-
依赖冲突:
-
若使用 CGLIB 代理,需确保项目中包含
cglib
依赖。
-
总结
-
IoC 的核心实现在
spring-core
、spring-beans
和spring-context
。 -
AOP 的核心实现在
spring-aop
,结合spring-aspects
实现高级切面功能。 -
二者共同构成 Spring 的基石,支撑声明式事务、安全拦截等企业级功能。