目录
AOP(Aspect Oriented Programming),即面向切面编程,利用一种成为“横切”的技术,抛开封装的对象内部,并将那些影响了多个类的公共行为封装为一个可重用模块,并将其命名为“Aspect”,即切面。所谓切面,简单的说,和代理设计模式有些相似,但是AOP的功能比代理模式更加的全面,利用AOP的环绕通知也可以达到代理模式的效果。
Spring AOP一共有五种通知:前置通知、后置通知、返回通知、异常通知和环绕通知。前置通是在目标方法执行前执行,后置通知在目标方法执行后执行,返回通知是当目标方法有返回值的时候执行,同理异常通知是目标方法发生异常的时候通知,而环绕通知则是其他四个通知的集合。
以下是测试AOP五个通知的代码
XML文件配置
首先添加两个依赖,spring-aspects + spring-context
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>myaop01</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
</dependencies>
</project>
/**
* 为了便于测试,分别定义有返回值和无返回值的方法
*/
public interface Calculator {
Integer add(int a, int b);
void minus(int a, int b);
}
/**
* 接口的实现类
*/
public class CalculatorImp implements Calculator {
@Override
public Integer add(int a, int b) {
return a + b;
}
@Override
public void minus(int a, int b) {
System.out.println(a + "-" + b + "=" + (a - b));
}
}
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 定义一个通知类,测试AOP的五种通知
*
* 前置通知:MethodBeforeAdvice 目标方法执行前通知
* 后置通知:AfterAdvice 目标方法执行后通知
* 返回通知:AfterReturningAdvice 当目标方法有返回值的时候触发
* 异常通知:ThrowsAdvice 当目标方法抛出异常的时候通知
* 环绕通知:MethodInterceptor 以上四个通知的集大成者
*/
public class LogAspect {
/**
* @param jp 连接点是程序类中客观存在的方法,可被Spring拦截并切入内容
*/
public void before(JoinPoint jp) {
//获取目标方法名称
String name = jp.getSignature().getName();
System.out.println(name + "方法开始执行。。。");
}
public void after(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println(name + "方法执行结束。。。");
}
/**
* 参数的类型必须匹配,目前只有当返回值类型为int时,才会进入当前方法,int改为Object则所有方法都匹配
*
* @param result 目标方法返回值
*/
public void returning(JoinPoint jp, int result) {
System.out.println("返回通知。。。" + result);
}
/**
* @param e 只有异常匹配时,才会拦截
*/
public void exception(JoinPoint jp, Exception e) {
System.out.println(jp.getSignature().getName() + "方法抛出了异常" + e.getMessage());
}
/**
* 环绕通知
*/
public Object around(ProceedingJoinPoint pjp) {
try {
//调用目标方法
long startTime = System.nanoTime();
Object proceed = pjp.proceed();
long endTime = System.nanoTime();
System.out.println(pjp.getSignature().getName() + "方法执行耗时" + (endTime - startTime));
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
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">
<--! 配置实现类,但是在获取的时候是获取接口 -->
<bean class="com.qfedu.demo.CalculatorImp" id="calculatorImp"/>
<bean class="com.qfedu.demo.LogAspect" id="logAspect"/>
<!-- 开始AOP的配置-->
<aop:config>
<!-- 配置切点,即要拦截哪些方法?id就是切点名称,expression='execution(int com.qfedu.demo.CalculatorImp.add(int,int))'就是切点表达式,表示拦截add方法
* com.qfedu.demo.*.*(..)) 表示任意返回值类型,com.qfedu.demo包下的任意类,任意方法名,任意参数 ,都拦截-->
<aop:pointcut id="pc01" expression="execution(* com.qfedu.demo.*.*(..))"/>
<!-- 配置一个拦截类-->
<aop:aspect ref="logAspect">
<!-- 定义一个前置通知,目标方法拦截下来之后触发的方法-->
<aop:before method="before" pointcut-ref="pc01"/>
<aop:after method="after" pointcut-ref="pc01"/>
<!-- 返回通知,returning表示目标方法返回值的映射-->
<aop:after-returning method="returning" pointcut-ref="pc01" returning="result"/>
<aop:after-throwing method="exception" pointcut-ref="pc01" throwing="e"/>
<aop:around method="around" pointcut-ref="pc01"/>
</aop:aspect>
</aop:config>
</beans>
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 测试类
*/
class LogAspectTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//这个地方拿到的对象实际上是Spring AOP 利用JDK动态给Calculator接口自动生成的一个对象
//配置的是实现类,获取的是实现类的代理对象
Calculator calculator = ctx.getBean(Calculator.class);
Integer add = calculator.add(3, 4);
System.out.println("add = " + add);
System.out.println("-----------------------");
calculator.minus(4, 3);
}
}
运行结果
add方法开始执行。。。
add方法执行耗时35600
返回通知。。。7
add方法执行结束。。。
add = 7
-----------------------
minus方法开始执行。。。
4-3=1
minus方法执行耗时54300
minus方法执行结束。。。
从运行结果可知,前置通知before、后置通知after和环绕通知arounding均已执行,因为minus方法没有返回值,所以调用minus()没有执行返回通知returning,因为add()和minus()两个方法都没有出现异常,所以异常通知没有执行。
Java代码配置
package com.qfedu.demo01.service;
/**
* 接口
*/
public interface CalculatorImp {
Integer add(int a, int b);
void minus(int a, int b);
}
package com.qfedu.demo01.service;
import org.springframework.stereotype.Component;
/**
* 实现类
*/
@Component //注入到Spring容器
public class Calculator implements CalculatorImp {
@Override
public Integer add(int a, int b) {
return a + b;
}
@Override
public void minus(int a, int b) {
//定义一个异常,测试AOP的异常通知
int i = 1 / 0;
System.out.println(a + "+" + b + "=" + (a + b));
}
}
package com.qfedu.demo01;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
/**
* 通知类
*
* @Component 表示将 LogAspect 注入到 Spring 容器中
* @Aspect 表示当前类是一个切面
* @EnableAspectJAutoProxy 开启切面的自动代理
*/
@Component
@Aspect
@EnableAspectJAutoProxy
public class LogAspect {
/**
* 定义统一的切点
* * com.qfedu.demo01.service.*.*(..))表示 com.qfedu.demo01.service下的 任意类名,任意方法,任意参数,任意返回值类型的方法
*/
@Pointcut("execution(* com.qfedu.demo01.service.*.*(..))")
public void pc() {
}
@Before("pc()")
public void before(JoinPoint jp) {
//获取目标方法名称
String name = jp.getSignature().getName();
System.out.println(name + "方法开始执行。。。");
}
@After("pc()")
public void after(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println(name + "方法执行结束。。。");
}
@AfterReturning(pointcut = "pc()", returning = "result")
public void returning(JoinPoint jp, Object result) {
System.out.println("返回通知。。。" + result);
}
@AfterThrowing(pointcut = "pc()", throwing = "e")
public void throwing(JoinPoint jp, Exception e) {
System.out.println(jp.getSignature().getName() + "方法抛出了异常" + e.getMessage());
}
@Around("pc()")
public Object around(ProceedingJoinPoint pjp) {
try {
//调用目标方法
long startTime = System.nanoTime();
Object proceed = pjp.proceed();
long endTime = System.nanoTime();
System.out.println(pjp.getSignature().getName() + "方法执行耗时" + (endTime - startTime));
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
package com.qfedu.demo01;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration //声明当前类是配置类
@ComponentScan //spring自动扫描并装入bean容器
public class JavaConfig {
}
package com.qfedu.demo01;
import com.qfedu.demo01.service.CalculatorImp;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 测试类
*/
class LogAspectTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
CalculatorImp calculatorImp = ctx.getBean(CalculatorImp.class);
Integer add = calculatorImp.add(3, 4);
System.out.println("add = " + add);
System.out.println("-----------------");
calculatorImp.minus(4, 3);
}
}
运行结果
add方法开始执行。。。
返回通知。。。7
add方法执行结束。。。
add方法执行耗时1885800
add = 7
-----------------
minis方法开始执行。。。
minis方法抛出了异常/ by zero
minis方法执行结束。。。
由于在com.qfedu.demo01.service.Calculator#minus()方法中有意编写了一个异常 by zero,所以在调用minus()方法时,异常通知执行了。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/14615.html