Aspect Oriented Programming ,面向切面编程,这是对面向对象思想的一种补充。
面向切面编程,就是在程序运行时,不改变程序源码的情况下,动态的增强方法的功能,常见的使用场景非常多:
以上操作有很多模板化的代码
AOP常见概念
切点:要添加代码的地方,称作切点
通知(增强):通知就是向切点动态添加的代码
切面:切点+通知
连接点:切点的定义
AOP实现
Aop实际上是基于Java的动态代理来实现的
Java中的动态代理有两种实现方式:cglib,jdk
package org.kk.aop;
/**
* @program: ioc01
* @description:实现动态代理,定义一个计算器接口
* @author: zjx
* @create: 2021-12-27 22:38
**/
public interface MyCalculator {
int add(int a,int b);
}
package org.kk.aop;
/**
* @program: ioc01
* @description: 计算器接口的一个实现
* @author: zjx
* @create: 2021-12-27 22:44
**/
public class MyCalculatorImpl implements MyCalculator{
@Override
public int add(int a, int b) {
return a+b;
}
}
package org.kk.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @program: ioc01
* @description: 代理类里面增强功能
* @author: zjx
* @create: 2021-12-27 22:45
**/
public class CalculatorProxy {
public static Object getInstance(MyCalculatorImpl myCalculator)
{
//第一个参数:classLoader,第二个参数实现的接口,第三个参数为new的InvocationHandler
return Proxy.newProxyInstance(CalculatorProxy.class.getClassLoader(), myCalculator.getClass().getInterfaces(), new InvocationHandler() {
@Override
/**
* @Description:
* @Param: [proxy:代理对象, method:代理方法(add), args 方法的参数]
* @return: java.lang.Object 方法的返回值
* @Author: zjx
* @Date: 2021/12/27
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前");
Object invoke = method.invoke(myCalculator, args);
System.out.println("方法执行后");
return invoke;
//也可以return 99,那么调用add后的结果也就是99了
}
});
}
}
public class Main {
@Test
public void test1()
{
MyCalculatorImpl myCalculator=new MyCalculatorImpl();
MyCalculator calculator = ((MyCalculator) CalculatorProxy.getInstance(myCalculator));
int add=calculator.add(3,4);
System.out.println("add="+add);
}
}
调用结果:
方法执行前
方法执行后
add=7
Aop的通知类型有五种:
在以上代码中,在method.invoke之前执行——前置通知,在method.invoke之后执行——后置通知,异常通知,返回通知,环绕通知
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.19.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
两种方式:使用注解(不推荐)、使用规则
使用注解
package org.kk.aop;
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 {
}
package org.kk.aop;
import org.springframework.stereotype.Component;
/**
* @program: ioc01
* @description: 计算器接口的一个实现
* @author: zjx
* @create: 2021-12-27 22:44
**/
@Component
public class MyCalculatorImpl implements MyCalculator{
@Override
@Action //对需要增强的方法添加这个自定义注解 侵入式--要改源码
public int add(int a, int b) {
return a+b;
}
@Override
public void min(int a, int b) {
System.out.println(a+"-"+b+"="+(a-b));
}
//然后想要给这两个方法增加一个日志 在不改变源码的前提下
}
@Component
@Aspect
其中的方法用于通知:前置通知 @Before("@annotation(org.javaboy.app.Action)" ) 注解的包路径
package org.kk.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* @program: ioc01
* @description:定义增强和通知
* @author: zjx
* @create: 2021-12-28 13:46
**/
@Component
@Aspect //表示这是一个切面 五种通知
public class LogAspect {
/**
* @Description: 前置通知,在add逻辑开始之前执行
* @Param: [joinPoint]
* @return: void
* @Author: zjx
* @Date: 2021/12/28
*/
@Before("@annotation(Action)")//有Action注解的方法 执行之前会有前置通知
public void before(JoinPoint joinPoint)
{
String name=joinPoint.getSignature().getName();
System.out.println(name+"方法开始执行了");
}
}
配置类添加注解,需要开启自动代理
package org.kk.aop;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
/**
* @program: ioc01
* @description:
* @author: zjx
* @create: 2021-12-28 13:56
**/
@Configuration
@ComponentScan
//还要开启自动代理
@EnableAspectJAutoProxy
public class JavaConfig {
}
测试
@Test
public void test2()
{
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
MyCalculator bean = context.getBean(MyCalculator.class);
int res = bean.add(3, 4);
System.out.println(res);
}
结果
add方法开始执行了
7
四种通知方法
package org.kk.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @program: ioc01
* @description:定义增强和通知
* @author: zjx
* @create: 2021-12-28 13:46
**/
@Component
@Aspect //表示这是一个切面 五种通知
public class LogAspect {
/**
* @Description: 前置通知,在add逻辑开始之前执行
* @Param: [joinPoint]
* @return: void
* @Author: zjx
* @Date: 2021/12/28
*/
@Before("@annotation(Action)")//有Action注解的方法 执行之前会有前置通知
public void before(JoinPoint joinPoint)
{
String name=joinPoint.getSignature().getName();
System.out.println(name+"方法开始执行了");
}
//后置通知
@After("@annotation(Action)")
public void after(JoinPoint joinPoint)
{
String name=joinPoint.getSignature().getName();
System.out.println(name+"方法执行结束了");
}
//返回通知
/**
* @Description: 返回通知,可以在该方法中获取目标方法的返回值,如果目标方法的返回值是void,则收到null
* @Param: [joinPoint, r 返回的参数名称,和这里的方法的参数名一一对应]
* @return: void
* @Author: zjx
* @Date: 2021/12/28
*/
@AfterReturning(value = "@annotation(Action))",returning = "r")
public void returning(JoinPoint joinPoint,Integer r)//类型不匹配方法返回类型,是不会返回结果的;可以用Object
{
String name=joinPoint.getSignature().getName();
System.out.println(name+"返回通知:"+r);//r是方法执行的结果
}
/**
* @Description: 异常通知,当目标方法抛出异常时,该方法会被触发
* @Param: [joinPoint, e 异常参数,和方法的参数名一一对应,注意异常的类型]
* @return: void
* @Author: zjx
* @Date: 2021/12/28
*/
@AfterThrowing(value = "@annotation(Action)",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e)
{
String name=joinPoint.getSignature().getName();
System.out.println(name+"方法异常通知:"+e.getMessage());
}
/**
* @Description: 环绕通知,是上面四种方法集大成者,环绕通知的核心类似于在反射中执行方法
* @Param: [pjp]
* @return: java.lang.Object
* @Author: zjx
* @Date: 2021/12/28
*/
@Around("@annotation(Action)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//这个有点类似于method.invoke方法,我们可以在这个方法前后分别添加日志,相当于前置和后置通知
Object proceed=pjp.proceed(new Object[]{5,5});
return proceed;
}
}
//优化1 通过方法来定义切点 下面的注解都改 @Before("pointcut()")
@Pointcut("@annotation(Action)")
public void pointcut()
{
}
非侵入式定义切点:
//非侵入式定义切点
@Pointcut("execution(* org.kk.aop.service.*.*(..))") //任意类 任意方法 参数类型、个数任意
public void pointcut()
{
}
package org.kk.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @program: ioc01
* @description:定义增强和通知--定义的切面
* @author: zjx
* @create: 2021-12-28 13:46
**/
@Component
@Aspect //表示这是一个切面 五种通知
public class LogAspect {
// //优化1 通过方法来定义切点 下面的注解都改 @Before("pointcut()")
// @Pointcut("@annotation(Action)")
// public void pointcut()
// {
//
// }
//非侵入式定义切点
@Pointcut("execution(* org.kk.aop.service.*.*(..))") //任意类 任意方法 参数类型、个数任意
public void pointcut()
{
}
/**
* @Description: 前置通知,在add逻辑开始之前执行
* @Param: [joinPoint]
* @return: void
* @Author: zjx
* @Date: 2021/12/28
*/
@Before("pointcut()")//有Action注解的方法 执行之前会有前置通知
public void before(JoinPoint joinPoint)
{
String name=joinPoint.getSignature().getName();
System.out.println(name+"方法开始执行了");
}
//后置通知
@After("pointcut()")
public void after(JoinPoint joinPoint)
{
String name=joinPoint.getSignature().getName();
System.out.println(name+"方法执行结束了");
}
//返回通知
/**
* @Description: 返回通知,可以在该方法中获取目标方法的返回值,如果目标方法的返回值是void,则收到null
* @Param: [joinPoint, r 返回的参数名称,和这里的方法的参数名一一对应]
* @return: void
* @Author: zjx
* @Date: 2021/12/28
*/
@AfterReturning(value = "pointcut())",returning = "r")
public void returning(JoinPoint joinPoint,Integer r)//类型不匹配方法返回类型,是不会返回结果的;可以用Object
{
String name=joinPoint.getSignature().getName();
System.out.println(name+"返回通知:"+r);//r是方法执行的结果
}
/**
* @Description: 异常通知,当目标方法抛出异常时,该方法会被触发
* @Param: [joinPoint, e 异常参数,和方法的参数名一一对应,注意异常的类型]
* @return: void
* @Author: zjx
* @Date: 2021/12/28
*/
@AfterThrowing(value = "pointcut()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e)
{
String name=joinPoint.getSignature().getName();
System.out.println(name+"方法异常通知:"+e.getMessage());
}
/**
* @Description: 环绕通知,是上面四种方法集大成者,环绕通知的核心类似于在反射中执行方法
* @Param: [pjp]
* @return: java.lang.Object
* @Author: zjx
* @Date: 2021/12/28
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//这个有点类似于method.invoke方法,我们可以在这个方法前后分别添加日志,相当于前置和后置通知
Object proceed=pjp.proceed(new Object[]{5,5}); //return了10
return proceed;
}
}
xml配置Aop
<?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="org.kk.aop.LogAspect" name="logAspect"/>
<bean class="org.kk.aop.service.MyCalculatorImpl" name="myCalculator"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* org.kk.aop.service.*.*(..))"/>
<aop:aspect ref="logAspect">
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:before method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>