[华为云在线课程][Spring入门][AOP][二][学习笔记]
1.AOP简介
AOP(Aspect Oriented Programming),面向切面编程。意思就是在程序运行时,不改变源码前提下,动态增强方法的功能,例如:日志,事务,数据库操作等等。这些操作往往有很多模板代码,可以利用AOP消除这些臃肿的模板代码。
概念 | 说明 |
---|---|
切点 | 要添加代码的地方 |
通知(增强) | 向切点动态添加的代码 |
切面 | 切点+通知 |
连接点 | 切点的定义 |
2.基于JDK动态代理实现的AOP
AOP实际上是基于Java的动态代理来实现的。Java中的动态代理有两种实现方式:cglib和jdk。
以下是一个基于jdk的动态代理实现。
定义一个计算器接口:
package org.aopexample;
public interface Calculator {
int add(int number1, int number2);
}
实现计算器接口:
package org.aopexample;
public class CalculatorImpl implements Calculator {
@Override
public int add(int number1, int number2) {
return number1 + number2;
}
}
定义代理类:
package org.aopexample;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class CalculatorProxy {
public static Object getInstance(CalculatorImpl calculator) {
return Proxy.newProxyInstance(CalculatorProxy.class.getClassLoader(),
calculator.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "方法开始执行。。。");
Object invoke = method.invoke(calculator, args);
System.out.println(method.getName() + "方法结束执行。。。");
return invoke;
}
});
}
}
newProxyInstance方法接收三个参数,第一个是classloader,第二个是代理多项实现的接口,第三个是代理对象方法的处理器,所有要额外添加的行为都在invoke中实现。
package org.aopexample;
public class CalculatorMain {
public static void main(String[] args) {
CalculatorImpl calculator = new CalculatorImpl();
Calculator calc = (Calculator) CalculatorProxy.getInstance(calculator);
int add = calc.add(1, 2);
System.out.println(add);
}
}
输出结果:
add方法开始执行。。。
add方法结束执行。。。
3
3.AOP五种通知类型
前置,后置,异常,返回,环绕。
根据上面的计算器例子来进行功能增强。
首先引入Spring依赖(AOP)
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
4.方法中统一定义切点及非侵入式定义切点
导入依赖后就可以开始定义切点,定义切点有两种方式:使用自定义注解,使用规则。
自定义注解是侵入式的,这种方式在实际开发中不推荐仅作了解。使用规则定义切点是无入侵式的,一般推荐使用这个。
4.1.自定义注解
首先定义一个注解
package org.aopexample;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
}
然后在需要拦截方法上添加@Action注解,以计算器代码为例即在add方法上添加@Action,表示该方法将会被AOP拦截,其他未添加该注解的方法则不受影响。
package org.aopexample.calculate;
import org.aopexample.Action;
import org.springframework.stereotype.Component;
@Component
public class CalculatorImpl1 {
@Action
public int add(int number1, int number2) {
return number1 + number2;
}
public void min(int number1, int number2) {
System.out.println(number1 + "-" + number2 + "=" + (number1 - number2));
}
}
接下来定义通知(增强、Advice)
package org.aopexample.useanno;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect //表示这是一个切面
public class LogAspect {
/**
* 前置通知
* joinPoint 包含了目标方法的关键信息
*
* @Before 注解表示是一个前置通知,即在目标方法执行之前执行,注解中需要填入切点
*/
@Before(value = "@annotation(Action)")
public void before(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法开始执行了。。。");
}
/**
* 后置通知
* joinPoint 包含了目标方法的关键信息
*
* @After 表示这是一个后置通知,即在目标方法执行之后执行
*/
@After("@annotation(Action)")
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法执行结束了。。。");
}
/**
* @AfterReturning 表示是一个返回通知,即有目标方法有返回值的时候才会触发,该注解中的returning属性
* 表示目标方法返回值的变量名,这个需要和参数一一对应吗,注意:目标方法的返回值类型要和这里方法返回值参数一致,
* 否则拦截不到,如果想拦截所有的话(包括返回值为void),则方法返回值参数可以为Object
*/
@AfterReturning(value = "@annotation(Action)", returning = "r")
public void returning(JoinPoint joinPoint, Integer r) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法返回:" + r);
}
/**
* 异常通知
* e表示目标方法所抛出的异常,注意,这个参数必须是目标方法所抛出的异常或者
* 所抛出的异常的父类,只有这样才会捕获。如果想拦截所有,参数类型声明为Exception
*/
@AfterThrowing(value = "@annotation(Action)", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法抛出异常了:" + e.getMessage());
}
/**
* 环绕通知
* 环绕通知是集大成者,可以用环绕通知实现上面的四个通知,这个方法的核心有点类似
* 在这里通过反射执行方法
*
* @return 这里的返回值类型最好是Object,和拦截到的方法相匹配
*/
@Around("@annotation(Action)")
public Object around(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
//相当于method.invoke方法,我们可以在这个方法的前后分别添加日志,相当于是前置/后置通知
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
在配置类中开启包扫描和自动代理
package org.aopexample.useanno;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan
@EnableAspectJAutoProxy//开启自动代理
public class JavaConfig {
}
在启动方法中调用
package org.aopexample.useanno;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);
CalculatorImpl1 myCalculator = applicationContext.getBean(CalculatorImpl1.class);
myCalculator.add(3, 4);
myCalculator.min(3, 4);
}
}
发现切点的定义不够灵活,因为切点写在注解里,这样要修改切点每个方法都要修改,我们可以统一定义切点,统一调用。
package org.aopexample.useanno;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect //表示这是一个切面
public class LogAspect1 {
/**
* 统一定义切点
* */
@Pointcut("@annotation(Action)")
public void pointcut() {
}
/**
* 前置通知
* joinPoint 包含了目标方法的关键信息
*
* @Before 注解表示是一个前置通知,即在目标方法执行之前执行,注解中需要填入切点
*/
@Before(value = "pointcut()")
public void before(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法开始执行了。。。");
}
/**
* 后置通知
* joinPoint 包含了目标方法的关键信息
*
* @After 表示这是一个后置通知,即在目标方法执行之后执行
*/
@After("pointcut()")
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法执行结束了。。。");
}
/**
* @AfterReturning 表示是一个返回通知,即有目标方法有返回值的时候才会触发,该注解中的returning属性
* 表示目标方法返回值的变量名,这个需要和参数一一对应吗,注意:目标方法的返回值类型要和这里方法返回值参数一致,
* 否则拦截不到,如果想拦截所有的话(包括返回值为void),则方法返回值参数可以为Object
*/
@AfterReturning(value = "pointcut()", returning = "r")
public void returning(JoinPoint joinPoint, Integer r) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法返回:" + r);
}
/**
* 异常通知
* e表示目标方法所抛出的异常,注意,这个参数必须是目标方法所抛出的异常或者
* 所抛出的异常的父类,只有这样才会捕获。如果想拦截所有,参数类型声明为Exception
*/
@AfterThrowing(value = "pointcut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法抛出异常了:" + e.getMessage());
}
/**
* 环绕通知
* 环绕通知是集大成者,可以用环绕通知实现上面的四个通知,这个方法的核心有点类似
* 在这里通过反射执行方法
*
* @return 这里的返回值类型最好是Object,和拦截到的方法相匹配
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
//相当于method.invoke方法,我们可以在这个方法的前后分别添加日志,相当于是前置/后置通知
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
4.2.使用规则
使用注解是侵入式的,要将其改为非入侵式的。重新定义切点,不需要@Action注解,要拦截的目标方法不用添加@Action注解。下面使用规则定义
package org.aopexample.useanno;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect //表示这是一个切面
public class LogAspect2 {
/**
* 统一定义切点
* */
@Pointcut("@annotion(Action)")
public void pointcut2() {
}
/**
* 统一定义切点
* 第一个 * 表示要拦截的目标方法返回值任意(也可以明确返回值类型)
* 第二个 * 表示包中任意类(可以指定类)
* 第三个 * 表示类中任意方法
* 最后两个点表示方法参数任意,个数任意,类型任意
* */
@Pointcut("execution(* org.aopexample.useanno.*.*(..))")
public void pointcut() {
}
/**
* 前置通知
* joinPoint 包含了目标方法的关键信息
*
* @Before 注解表示是一个前置通知,即在目标方法执行之前执行,注解中需要填入切点
*/
@Before(value = "pointcut()")
public void before(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法开始执行了。。。");
}
/**
* 后置通知
* joinPoint 包含了目标方法的关键信息
*
* @After 表示这是一个后置通知,即在目标方法执行之后执行
*/
@After("pointcut()")
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法执行结束了。。。");
}
/**
* @AfterReturning 表示是一个返回通知,即有目标方法有返回值的时候才会触发,该注解中的returning属性
* 表示目标方法返回值的变量名,这个需要和参数一一对应吗,注意:目标方法的返回值类型要和这里方法返回值参数一致,
* 否则拦截不到,如果想拦截所有的话(包括返回值为void),则方法返回值参数可以为Object
*/
@AfterReturning(value = "pointcut()", returning = "r")
public void returning(JoinPoint joinPoint, Integer r) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法返回:" + r);
}
/**
* 异常通知
* e表示目标方法所抛出的异常,注意,这个参数必须是目标方法所抛出的异常或者
* 所抛出的异常的父类,只有这样才会捕获。如果想拦截所有,参数类型声明为Exception
*/
@AfterThrowing(value = "pointcut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法抛出异常了:" + e.getMessage());
}
/**
* 环绕通知
* 环绕通知是集大成者,可以用环绕通知实现上面的四个通知,这个方法的核心有点类似
* 在这里通过反射执行方法
*
* @return 这里的返回值类型最好是Object,和拦截到的方法相匹配
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
//相当于method.invoke方法,我们可以在这个方法的前后分别添加日志,相当于是前置/后置通知
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
5.XML配置AOP
首先导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>benchmarks</artifactId>
<version>3.12.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
定义通知(增强),不需要添加注解,因为注解在Spring配置文件中定义
package org.aopexample.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
public class LogAspect {
public void before(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法开始执行了。。。");
}
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法执行结束了。。。");
}
public void returning(JoinPoint joinPoint, Integer r) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法返回:" + r);
}
public void afterThrowing(JoinPoint joinPoint, Exception e) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法抛出异常了:" + e.getMessage());
}
public Object around(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
//相当于method.invoke方法,我们可以在这个方法的前后分别添加日志,相当于是前置/后置通知
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
在Spring配置文件中进行AOP配置
<!-- XML配置AOP-->
<bean class="org.aopexample.xml.LogAspect" id="logAspect"/>
<bean class="org.aopexample.xml.CalculatorImpl1" id="calculatorImpl1"/>
<aop:config>
<aop:pointcut id="pc1" expression="execution(* org.aopexample.xml.*.*(..))"/>
<aop:aspect ref="logAspect">
<aop:before method="before" pointcut-ref="pc1"/>
<aop:after method="after" pointcut-ref="pc1"/>
<aop:after-returning method="returning" pointcut-ref="pc1" returning="r"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
<aop:around method="around" pointcut-ref="pc1"/>
</aop:aspect>
</aop:config>
package org.aopexample.xml;
import org.aopexample.useanno.Action;
import org.springframework.stereotype.Component;
//@Component
public class CalculatorImpl1 {
public int add(int number1, int number2) {
return number1 + number2;
}
public void min(int number1, int number2) {
System.out.println(number1 + "-" + number2 + "=" + (number1 - number2));
}
}
在启动方法加载配置文件
package org.aopexample.xml;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationcontext.xml");
CalculatorImpl1 calculatorImpl1 = applicationContext.getBean(CalculatorImpl1.class);
calculatorImpl1.add(1, 2);
calculatorImpl1.min(1, 2);
}
}
输出结果
add方法开始执行了。。。
add方法返回:3
add方法执行结束了。。。
min方法开始执行了。。。
1-2=-1
min方法执行结束了。。。
- 点赞
- 收藏
- 关注作者
评论(0)