一、概述

  面向切面编程(AOP)是针对面向对象编程(OOP)的补充,可以非侵入式的为多个不具有继承关系的对象引入相同的公共行为例如日志、安全、事务、性能监控等等。SpringAOP允许将公共行为从业务逻辑中抽离出来,并将这些行为以一种非侵入的方式织入到所有需要的业务逻辑中,相较于OOP纵向模式的业务逻辑实现,其关注的方向是横向的切面。

  从Spring2.0开始,引入AspectJ注释来对POJO进行标注,支持通过切点函数、逻辑运算符、通配符等高级功能来对切点进行灵活的定义,结合各种类型的通知来形成强大的连接点描述能力。

二、使用示例

2.1、基础示例,拦截指定类与方法

1、pom

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>

或者

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

核心模块:两者效果都一样,将Spring核心模块Spring core、Spring beans、Spring context、Spring aop以及AspectJ注释提供库aspectjweaver引入项目。(另外包括SpringBoot特有的自动配置模块等等)

2、正常的业务逻辑

@Service
public class MyTestService {
Logger logger = LoggerFactory.getLogger(MyTestService.class);
public String doSomething1(){
logger.info("invoking doSomething1......");
return "doSomething1";
} public String doSomething2(){
logger.info("invoking doSomething2......");
return "doSomething2";
}
}

3、定义切面

@Aspect
@Component
public class MyIntercepter {
private static final Logger logger = LoggerFactory.getLogger(MyIntercepter.class);
@Pointcut("execution(public * com.github.bjlhx15.springaop.service.MyTestService.doSomething1*(..))")
public void doSomethingPointcut(){}; @Before("doSomethingPointcut()")
public void auth(JoinPoint pjp) throws Throwable{
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod(); //获取被拦截的方法
String methodName = method.getName(); //获取被拦截的方法名
logger.info("权限认证:调用方法为:{}", methodName);
}; @AfterReturning(value = "doSomethingPointcut()", returning = "returnVal")
public void logNormal(JoinPoint pjp, Object returnVal) throws Throwable{
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod(); //获取被拦截的方法
String methodName = method.getName(); //获取被拦截的方法名
logger.info("正常返回记日志:调用方法为:{};返回结果为:{}", methodName, returnVal);
}; @AfterThrowing(value = "doSomethingPointcut()", throwing = "e")
public void logThrowing(JoinPoint pjp, Throwable e) throws Throwable{
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod(); //获取被拦截的方法
String methodName = method.getName(); //获取被拦截的方法名
logger.info("抛出异常记日志:调用方法为:{};异常信息为:{}", methodName, e.getMessage());
}; @After(value = "doSomethingPointcut()")
public void afterall(JoinPoint pjp) throws Throwable{
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod(); //获取被拦截的方法
String methodName = method.getName(); //获取被拦截的方法名
logger.info("方法调用完成:调用方法为:{}", methodName);
} @Around("doSomethingPointcut()")
public Object timer(ProceedingJoinPoint pjp) throws Throwable{
long beginTime = System.currentTimeMillis();
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod(); //获取被拦截的方法
String methodName = method.getName(); //获取被拦截的方法名
logger.info("计时切面:请求开始,方法:{}", methodName); Object result = null;
try {
// 一切正常的情况下,继续执行被拦截的方法
result = pjp.proceed();
} catch (Throwable e) {
logger.info("exception: ", e);
}
long endTime = System.currentTimeMillis();
logger.info("计时切面:请求结束,方法:{},执行时间:{}", methodName, (endTime-beginTime));
return result;
}
}

  上面定义了一个切点Pointcut,并围绕这个切点定义了5中不同类型的通知Advice,每个切点及其通知以及通知执行的逻辑共同构成了一个切面Advisor,用以在方法执行过程的各个时间点切入,执行一些特定逻辑。

4、调用

  这时程序调用即有对应的切面信息,测试

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ApplicationMain.class)
public class MyTestServiceTest {
@Autowired
MyTestService myTestService; @Test
public void doSomething1() {
myTestService.doSomething1();
}
}

输出

c.g.b.s.interceptor.MyIntercepter        : 计时切面:请求开始,方法:doSomething1
c.g.b.s.interceptor.MyIntercepter : 权限认证:调用方法为:doSomething1
c.g.b.springaop.service.MyTestService : invoking doSomething1......
c.g.b.s.interceptor.MyIntercepter : c.g.b.s.interceptor.MyIntercepter : 方法调用完成:调用方法为:doSomething1
c.g.b.s.interceptor.MyIntercepter : 正常返回记日志:调用方法为:doSomething1;返回结果为:doSomething1

  以无侵入的形式在方法调用的前后增加了很多横切向的业务逻辑,业务逻辑代码并不必关心这些横切逻辑,只需要专注于自己的业务逻辑的实现就好。

  在实际执行当中,在方法调用前后我们定义的切面都开始执行了。SpringAOP确保我们定义的切面织入到业务逻辑代码中,并在执行时发挥作用。

  另外如结果所示多个切面的执行顺序也并不是按照方法定义的顺序执行。

2.2、基于自定义注解的切面

  直接使用execution(public * com.github.bjlhx15.springaop.service.MyTestService.doSomething1*(..))这种切面定义方式与实际的类路径、类名或方法名紧密绑定,不利于扩展,后续使用需要编写切面的人才能继续。

  我们希望像SpringCache那样基于自定义注解的方式启动各种切面,SpringAOP通过切点函数@annotation和@Within来支持这种方式。

1、pom同上

2、定义注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestLogger {
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestTimer {
}

  其中TestLogger是定义在类上的注释;TestTimer是定义在方法上的注释。

3、定义基于自定义注解的切面

@Aspect
@Component
public class MyIntercepter2 {
private static final Logger logger = LoggerFactory.getLogger(MyIntercepter2.class); @Pointcut("@annotation(com.github.bjlhx15.springaop.anno.TestTimer)")
public void timerPointcut(){}; @Pointcut("@within(com.github.bjlhx15.springaop.anno.TestLogger)")
public void recordLogPointcut(){}; @Before("recordLogPointcut()")
public void log(JoinPoint pjp) throws Throwable{
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod(); //获取被拦截的方法
String methodName = method.getName(); //获取被拦截的方法名
logger.info("开始记日志:调用方法为:{}", methodName);
} @Around("timerPointcut()")
public Object timer(ProceedingJoinPoint pjp) throws Throwable{
long beginTime = System.currentTimeMillis();
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod(); //获取被拦截的方法
String methodName = method.getName(); //获取被拦截的方法名 logger.info("请求开始,方法:{}", methodName); Object result = null; try {
// 一切正常的情况下,继续执行被拦截的方法
result = pjp.proceed();
} catch (Throwable e) {
logger.info("exception: ", e);
}
long endTime = System.currentTimeMillis();
logger.info("请求结束,方法:{},执行时间:{}", methodName, (endTime-beginTime));
return result;
}
}

  上述代码表示打了@TestLogger注释的类,其中的所有方法被调用时都会记日志;而不管什么类,其打了@TestTimer注释的方法都会监控其执行时间。

  切点函数@annotation表示匹配方法上的注解,切点函数@within表示匹配类上的注解。

4、编写业务逻辑并使用切面注解

@Service
@TestLogger
public class MyTestService2 {
Logger logger = LoggerFactory.getLogger(MyTestService2.class);
public String sayHello(){
logger.info("invoking method sayHello......");
return "Hello world!";
} @TestTimer
public int count(){
logger.info("invoking method count......");
return 10;
}
}

根据服务MyTestService2中的注释,其表达的意思是MyTestService2中所有方法调用时都需要记日志,另外count()方法被调用时候需要监控执行时间。

5、测试输出

 c.g.b.s.interceptor.MyIntercepter2       : 开始记日志:调用方法为:sayHello
c.g.b.springaop.service.MyTestService2 : invoking method sayHello......
c.g.b.s.interceptor.MyIntercepter2 : 请求开始,方法:count
c.g.b.s.interceptor.MyIntercepter2 : 开始记日志:调用方法为:count
c.g.b.springaop.service.MyTestService2 : invoking method count......
c.g.b.s.interceptor.MyIntercepter2 : 请求结束,方法:count,执行时间:0

由上可见,由于我们标注的注解的不同,在调用方法sayHello时只将记日志的逻辑切入进来,而在调用方法count时,将记日志和监控执行时间的逻辑都切入进来了。

 

010-Spring aop 001-核心说明-拦截指定类与方法、基于自定义注解的切面的更多相关文章

  1. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  2. Spring AOP深入理解之拦截器调用

    Spring AOP深入理解之拦截器调用 Spring AOP代理对象生成回想 上一篇博客中:深入理解Spring AOP之二代理对象生成介绍了Spring代理对象是怎样生成的,当中重点介绍了JDK动 ...

  3. Spring3系列10- Spring AOP——Pointcut,Advisor拦截指定方法

    Spring3系列10- Spring AOP——Pointcut,Advisor 上一篇的Spring AOP Advice例子中,Class(CustomerService)中的全部method都 ...

  4. spring aop 、Redis实现拦截重复操作

    一.问题:项目中有一些重复操作的情况,比如: 1.从场景有用户快速点击提交按钮,或者postMan测试时快速点击 2.从业务上来说,用户注册.用户下单等 3.黑客攻击 二.解决办法 1.使用sprin ...

  5. Spring自定义注解配置切面实现日志记录

    一: spring-mvc.xml: <!--配置日志切面 start,必须与mvc配置在同一个配置文件,否则无法切入Controller层--><!-- 声明自动为spring容器 ...

  6. SpringAOP拦截Controller,Service实现日志管理(自定义注解的方式)

    转载:http://itindex.net/detail/50710-springaop-controller-service 从业近二,三年了,第一次写博客,平时做做脚手架或者架构一些基础框架然后给 ...

  7. 使用自定义注解和切面AOP实现Java程序增强

    1.注解介绍 1.1注解的本质 Oracle官方对注解的定义为: Annotations, a form of metadata, provide data about a program that ...

  8. Spring mvc:配置不拦截指定的路径

    <!-- 访问拦截 --> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**/ ...

  9. spring aop 利用JoinPoint获取参数的值和方法名称

    AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用org.aspectj.lang.ProceedingJoinPoint表示连接点 ...

随机推荐

  1. 【RabbitMQ】RabbitMQ的安装以及基本概念的介绍

    一.如何安装 https://www.cnblogs.com/756623607-zhang/p/11469962.html 二.基本概念介绍 ·Broker:可以理解为消息队列服务器的实体,它是一个 ...

  2. FreeBSD安装过程

    对于现在版本,安装过程中该使用哪些键,现简单总结: Space:选中/取消选中: Tab:切换,主要是分区界面时用它选择输入行: Enter:确定(并进入下一页): 方向键:在一些子组里更换输入项得用 ...

  3. 使用NB Exploit Kit攻击的APT样本分析——直接看流程图,就是网页挂马,利用java和flash等漏洞来在你主机安装和运行恶意软件

    使用NB Exploit Kit攻击的APT样本分析 from:https://cloud.tencent.com/developer/article/1092136 1.起因 近期,安恒工程师在某网 ...

  4. Beta产品测试报告:那周余嘉熊掌将得队、为了交项目干杯队

    测试对象: 那周余嘉熊掌将得队 一.截图 安装截图 运行截图 二.测试情况 1.第一次上手体验感觉如何?能否正常运行? 界面UI设计令人眼前一亮,客户端和管理员端皆可正常运行.组件动画流畅,响应流畅, ...

  5. Spring Boot 缓存 知识点

    每次调用需要缓存功能的方法时,Spring会检查指定参数的指定的目标方法是否已经被调用过:如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户.下次调用直接从缓存中获取. ...

  6. test20191205 WC模拟赛

    又是先开T3的做题顺序,搞我心态.以后我必须先看看题了. 整数拆分 定义 \(f_m(n)\) 表示将 \(n\) 表示为若干 \(m\) 的非负整数次幂的和的方案数.例如,当 \(m = 2\) 的 ...

  7. JQuery系列(7) - JQuery最佳实践

    上篇文章是一篇入门教程,从设计思想的角度,讲解"怎么使用jQuery".今天的文章则是更进一步,讲解"如何用好jQuery". 我主要参考了Addy Osman ...

  8. JSP中EL表达式的比较符号、字符串比较

    在EL表达式中我们可以使用运算符以达到我们想要的结果,运算符按作用分为以下几种: 1.算术运算符 +  例如:${6+6} .注意:在EL表达式中的‘+’只有数学运算的功能,没有连接符的功能,它会试着 ...

  9. Java中对象的比较(学习笔记)

    1)详细说明对象的比较方式有哪些? ①对象引用的比较("= ="运算符) "= ="是将对象的引用进行比较,实质是比较两个引用变量是否引用同一个对象.注意的点: ...

  10. prisma2 预览版

    prisma2 预览版已经发布好几个版本了,同时官方的参考文档也在github 可以看到 新版本的架构变动 参考图 说明 photon 为一个类型安全的数据库客户端(替换orm) lift 数据模型的 ...