面向切面编程(AOP), 通过提供另一种思考程序结构的方式来补充面向对象程序(OOP)。OOP中模块化的关键单元是类,而在AOP中,模块化单元是切面。切面实现了诸如跨越多种类型和对象的事务管理之类的关注点的模块化。
然而看了官网感觉有点绕口。
在软件业,AOP 为 Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 (来自百度百科 https://baike.baidu.com/item/AOP/1332219?fr=aladdin);
通俗一点讲: 面向切面编程, 一般的web项目结构层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面,每一个对象与对象之间,是一个切面。方法与方法与之间,模块与模块之间,都是一个切面。 在项目中我们实现它们, 主要是在做日志记录,性能统计,安全控制,事务处理,异常处理。(来自百度百科 https://baike.baidu.com/item/AOP/1332219?fr=aladdin);
如对程序的运行,记录到 传入方法的参数,返回的参数,抛出的异常,以及,什么时候结束,对数据对象 进行 锁处理。
我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。(来自百度百科 https://baike.baidu.com/item/AOP/1332219?fr=aladdin);
按照正常的逻辑,我们可以这么做。 (此处解释,来自于:https://blog.csdn.net/q982151756/article/details/80513340)
这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。
同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。
通过上面的一些例子,应该对 Spring Aop 有了一个大致的印象, 让我们看一下 Aop 的专业术语和概念。
要在Spring配置需要启用配置基于 @AspectJ 方面的Spring AOP和Spring支持使用 @AspectJ 切面;
通过在spring xml 配置中包含以下元素来启用@AspectJ支持:
< aop:aspectj-autoproxy />
如果正在使用DTD,则仍可以通过向应用程序上下文添加以下定义来启用@AspectJ支持:
< bean class = “org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator” />
注解 通过:
@EnableAspectJAutoProxy 标记在类上
如何声明 一个 切面?
注解通过:
@Aspect
xml 方式通过:
< bean id = “myAspect” class = “org.xyz.NotVeryUsefulAspect” >
< ! - 正常配置方面的属性 - >
< / bean >
相应的代码示例:请看 3 代码示例:
暂时只说一种
切点的:表达式: execution 表示执行, public int 表示 切点的返回值, 下面的 包名,类名,方法名 括号里的参数 用(..) 表示多个参数。 * 表示 匹配任何返回值, 该包下面的所有的类
@Pointcut("execution(public int com.enjoy.Cap10.bean.Calculator.subtraction(..))")
@Pointcut("execution(public * com.enjoy.Cap10.bean.* (..))")
IsModified
接口,以简化缓存package com.enjoy.Cap10.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
/**日志切面类
* LogAspects aop类 必须加注解 @Aspect
*
* 前置通知, 在方法之前运行。
* @before 注解 参数 execution() 返回值( public int), 切入方法地址(com.enjoy.Cap10.bean.Calculator.Subtraction(int,int)) , 有几个参数,什么类型,必须要写上去
*
* @Before("execution(public int com.enjoy.Cap10.bean.Calculator.Subtraction(int,int))")
*public void befor(){}
*
*
*
*@After("execution(public int com.enjoy.Cap10.bean.Calculator.Subtraction(int,int))")
*public void after(){}
*
*
*
* @author wuchao
* @date 2019/7/3
*/
@Aspect
public class LogAspects {
/**
* 此注解的作用是切点,如果没有我们需要在下面 @Befor @After @AfterReturning @AfterThrowing @Around 注解后面都跟上指定的方法,
* 当我们用此注解,指定路径之后,可以在 下面 注解后面直接 指点指定此方法,效果一样,来减少代码的冗余,
* 但是 这样指定 execution(public int com.enjoy.Cap10.bean.Calculator.Subtraction(int,int)) 写死了, 我们用什么方法,来讲代码写的更加优雅,和
* execution(public int com.enjoy.Cap10.bean.*(..)) 用* 代替的意思不管该包下面有多少类,全部实现 Aop, 参数不管有多少,用 (..) 代替,
* 就可以 直接使用 @Before("pointcut()") 其他注解也是一样
*/
@Pointcut("execution(public int com.enjoy.Cap10.bean.Calculator.subtraction(..))")
public void pointcut(){
//此方法仅做 指定地址
}
/**前置通知, 在方法之前运行。
* @before 注解 参数 execution() 返回值( public int), 切入方法地址(com.enjoy.Cap10.bean.Calculator.Subtraction(int,int)) , 有几个参数,什么类型,必须要写上去
* @param point 该JoinPoint参数就代表了织入增强处理的连接点。JoinPoint里包含了如下几个常用的方法:
* Object[] getArgs:返回目标方法的参数
*
* Signature getSignature:返回目标方法的签名
*
* Object getTarget:返回被织入增强处理的目标对象
*
* Object getThis:返回AOP框架为目标对象生成的代理对象
*/
@Before("pointcut()")
public void befor(JoinPoint point){
System.out.println("@Before:模拟权限检查...");
//获得方法参数,转换成String;
System.out.println("计算机减法运行....参数列表是:{"+Arrays.toString(point.getArgs())+"}");
//获得 aop 切入的方法名
System.out.println("@Before:目标方法为:" + point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName());
System.out.println("@Before:被织入的目标对象为:" + point.getTarget());
}
/**
* 后置通知, 在方法之后运行。
*/
@After("pointcut()")
public void after(){
System.out.println("计算机减法结束......");
}
/**
* 返回通知,在方法没有出现异常,正常放回值的状态下运行。
* @param point 连接点
* @param returnValue 返回值
*/
@AfterReturning(pointcut = "pointcut()",returning = "returnValue")
public void afterRetruning(JoinPoint point,Object returnValue){
System.out.println("@AfterReturning:模拟日志记录功能...");
System.out.println("@AfterReturning:目标方法为:" +
point.getSignature().getDeclaringTypeName() +
"." + point.getSignature().getName());
System.out.println("@AfterReturning:参数为:" +
Arrays.toString(point.getArgs()));
System.out.println("@AfterReturning:返回值为:" + returnValue);
System.out.println("@AfterReturning:被织入的目标对象为:" + point.getTarget());
System.out.println("计算机减法正常返回......运行结果是:{}");
}
/**
* 异常通知,在方法出现异常,进行通知
*/
@AfterThrowing(pointcut = "pointcut()",throwing = "e")
public void afterThrowing(JoinPoint point,ArithmeticException e){
System.out.println("计算机减法出现异常......异常是:{"+point.getSignature().getName()+"--------"+e+"}");
}
/**
* 环绕通知, 在方法前通知,在方法后通知。
*/
@Around("pointcut()")
public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("@Arount:执行目标方法之前...");
//手动执行 proceed(), 其实该方法调用的就是 Subtraction();
Object proceed = proceedingJoinPoint.proceed();
System.out.println("@Arount:执行目标方法之后...");
return proceed;
}
}
package com.enjoy.Cap10.config;
import com.enjoy.Cap10.aop.LogAspects;
import com.enjoy.Cap10.bean.Calculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/** 配置类
* Cap10MainConfig
*
* @Configuration 声明为配置类,让Spring扫描,加入到容器
* @EnableAspectJAutoProxy 只有声明了此注解启用 AOP,Spring才知道我们声明定义的Aop,并且去扫描。也就是说是个开关,声明了注解,打开了aop的开关
*
* @Befor 前置通知,在我们执行 com.enjoy.Cap10.bean.Calculator.Subtraction() 方法之前运行,
* @After 后置通知,在我们执行 com.enjoy.Cap10.bean.Calculator.Subtraction() 方法之后运行,
* @AfterReturning 返回通知, 在我们执行 com.enjoy.Cap10.bean.Calculator.Subtraction() 方法之后, 该方法没有出现异常的状态下 运行。
* @AfterThrowing 异常通知, 在我们执行 com.enjoy.Cap10.bean.Calculator.Subtraction() 方法出现异常状态下 运行
* @Around 环绕通知, 动态代理,需要手动执行,joinPoint.procced()(其实就是调用 Subtraction() 方法),执行该方法前 前置通知,执行之后后置通知。如果,
* 环绕通知 和 前置通知,后置通知 同时存在时, 先执行,环绕通知的 前置通知,后执行我们声明的前置通知, 同理,环绕通知的 后置通知也是先执行的、
* @author wuchao
* @date 2019/7/3
*/
@Configuration
@EnableAspectJAutoProxy
public class Cap10MainConfig {
@Bean
public Calculator calculator(){
return new Calculator();
}
@Bean
public LogAspects logAspects(){
return new LogAspects();
}
}
import com.enjoy.Cap10.bean.Calculator;
import com.enjoy.Cap10.config.Cap10MainConfig;
import org.aspectj.lang.annotation.Aspect;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 测试类
* Cap10Test
*
* @author wuchao
* @date 2019/7/4
*/
public class Cap10Test {
@Test
public void Test(){
/*初始化容器*/
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Cap10MainConfig.class);
Calculator calculator= (Calculator) applicationContext.getBean("calculator");
calculator.subtraction(0,0);
/* Advice a modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented using regular classes
(the schema-based approach) or regular classes annotated with the @Aspect annotation*/
}
}
Spring源码深度解析,(附代码示例 码云地址: https://gitee.com/Crazycw/SpringCode.git)
参考资料: https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop.html
参考资料:https://blog.csdn.net/q982151756/article/details/80513340
请看下篇: Spring源码深度解析,SpringAOP源码跟踪(八)(附代码示例:)