AOP常见使用场景和代码实现
前言:
本文没有关于AOP的概念的介绍,这些官网上和很多博客都已经做过了。直接解决的是我们在日常开发中,需要使用AOP的部分场景以及如何使用。并附上自己写的代码。希望你看到这篇文章的时候,对你能有所帮助。
开发中可能用到AOP的场景:
如果让我们在现有的代码上加上日志功能,便于后期排错。现在有两种解决方案:
第一:就是在代码上直接加上日志(代码量比较多的时候,非常痛苦,而且都是同样的工作)
第二:就是使用AOP来实现在(基于注解)
注: AOP的底层用的是动态代理,JDK代理和CGlib,两者的直观区别在于 JDK代理需要接口和接口实现,CGlib代理不需要。AOP根据我们的代码实现了动态的选择。
@Service
public class CalcServiceImpl implements CalcService {
@Override
@ApiOperation(value = "计算器加法")
public Integer add(Integer i, Integer j) {
return i+j;
}
@Override
@ApiOperation(value = "计算器减法")
public Integer reduce(Integer i, Integer j) {
return i-j;
}
@Override
@ApiOperation(value = "计算器乘法")
public Integer ride(Integer i, Integer j) {
return i*j;
}
@Override
@ApiOperation(value = "计算器除法")
public Integer except(Integer i, Integer j) throws Exception {
if(j==0){
throw new Exception(j+":不能为0");
}
return i/j;
}
直接上AOP实现:
1、新建项目引入依赖
新建一boot项目,引入AOP的依赖 :aspectjweaver,spring-aspects
引入了swagger 便于演示获取注解参数值。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--Aop的依赖:aspectjweaver,spring-aspects-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- 逆向工程swagger整合 start -->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.github.misterchangray.mybatis.generator.plugins</groupId>
<artifactId>myBatisGeneratorPlugins</artifactId>
<version>1.2</version>
</dependency>
<!-- 逆向工程swagger整合 end -->
</dependencies>
2、建立xml文件
必须在xml中,开启包扫描 和 开启AOP功能,否则不会生效。
<!--扫描包:扫描类中的所有注解,不扫描注解不会生效-->
<context:component-scan base-package="com.example.demo"></context:component-scan>
<!--因为使用过的是注解方式的Aop,所以要开启注解Aop功能-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3、创建一个增强类
@Aspect’ :声明切面
@Component :声明组件 注册到ioc容器中
@Aspect
@Component
@Slf4j
public class LogUtil {
//可以采用声明切点的方式让其他通知引用,这样可复用性更强。
@Pointcut("execution(* com.example.demo.service..*.*(..)))")
public void pointcut(){
}
//前置通过
@Before("pointcut()")
//JoinPoint:获取切点的方法和参数
public static void before(JoinPoint joinPoint){
//获取方法名
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println(methodName+":方法运行");
log.info("方法参数是:"+Arrays.asList(args).toString());
System.out.println("方法参数是:"+Arrays.asList(args).toString());
}
//后置通知
//通过@annotation 值是方法上的注解名称(首字母小写),可以挺过.value方法获取该注解的value
@After("execution(* com.example.demo.service..*.*(..)) && @annotation(apiOperation)")
public static void after(JoinPoint joinPoint, ApiOperation apiOperation){
System.out.println("方法运行后");
System.out.println("获取方法上注解名称:"+ apiOperation.value());
}
//后置异常通知
@AfterThrowing(value = "execution(* com.example.demo.service..*.*(..))",
throwing = "ex")
public static void afterException(Exception ex){
//捕获异常栈信息
StringWriter sw = new StringWriter();
ex.printStackTrace(new PrintWriter(sw,true));
System.out.println("后置异常通知:"+sw.getBuffer().toString());
}
//后置返回通知
@AfterReturning(value = "execution(* com.example.demo.service..*.*(..))",
returning = "returnValue")
public static void afterEnd(Object returnValue){
System.out.println("方法结束,返回值是:"+ returnValue);
}
//环绕通知
@Around(value = "pointcut()" )
public Object arround (ProceedingJoinPoint joinPoint){
//获取方法名
String methodName = joinPoint.getSignature().getName();
//获取参数
Object[] args = joinPoint.getArgs();
Object returnValue = null;
try {
System.out.println("环绕:::前置通知:"+methodName+"方法执行,参数:"+Arrays.asList(args));
joinPoint.proceed();
System.out.println("环绕:::后置通知:"+methodName+"方法执行,参数:"+Arrays.asList(args));
} catch (Throwable throwable) {
System.out.println("环绕:::异常通知:"+throwable);
}finally {
System.out.println("环绕:::返回通知:"+returnValue);
}
return returnValue;
}
基于注解的ioc比较简单,只需要了解注解和想对应每个注解的每个参数就可以了。
"execution( com.example.demo.service….(…)) && @annotation(apiOperation)"*
这里的切点表达式需要解释一下:
第一个== * :代表的是方法的声明类型和返回值类型默认是(public void)*代表所有声明类型和返回值类型
第二个:com.example.demo.service… 需要操作的包一般是写到接口这一层,因为spring会自动通过接口查找接口实现,这个是关于spring的底层实现。后面的点点,代表的是他他下面的子包和子孙包。 .(…)) == 代表所有的类所有的的所有方法以及方法的所有参数。
注解的方式,还是很简单易懂的,如果有兴趣的话可以敲一下代码,实践出真知。