SpringBoot 通过自定义注解实现AOP切面编程实例
一直心心念的想写一篇关于AOP切面实例的博文,拖更了许久之后,今天终于着手下笔将其完成。
基础概念
1、切面(Aspect)
首先要理解‘切’字,需要把对象想象成一个立方体,传统的面向对象变成思维,类定义完成之后(封装)。每次实例化一个对象,对类定义中的成员变量赋值,就相当于对这个立方体进行了一个定义,定义完成之后,那个对象就在那里,不卑不亢,不悲不喜,等着被使用,等着被回收。
面向切面编程则是指,对于一个我们已经封装好的类,我们可以在编译期间或在运行期间,对其进行切割,把立方体切开,在原有的方法里面添加(织入)一些新的代码,对原有的方法代码进行一次增强处理。而那些增强部分的代码,就被称之为切面,如下面代码实例中的通用日志处理代码,常见的还有事务处理、权限认证等等。
2、切入点(PointCut)
要对哪些类中的哪些方法进行增强,进行切割,指的是被增强的方法。即要切哪些东西。
3、连接点(JoinPoint)
我们知道了要切哪些方法后,剩下的就是什么时候切,在原方法的哪一个执行阶段加入增加代码,这个就是连接点。如方法调用前,方法调用后,发生异常时等等。
4、通知(Advice)
通知被织入方法,改如何被增强。定义切面的具体实现。那么这里面就涉及到一个问题,空间(切哪里)和时间(什么时候切,在何时加入增加代码),空间我们已经知道了就是切入点中定义的方法,而什么时候切,则是连接点的概念,如下面实例中,通用日志处理(切面),@Pointcut规则中指明的方法即为切入点,@Before、@After是连接点,而下面的代码就是对应通知。
@Before("cutMethod()")
public void begin() {
System.out.println("==@Before== lingyejun blog logger : begin");
}
5、目标对象(Target Object)
被一个或多个切面所通知的对象,即为目标对象。
6、AOP代理对象(AOP Proxy Object)
AOP代理是AOP框架所生成的对象,该对象是目标对象的代理对象。代理对象能够在目标对象的基础上,在相应的连接点上调用通知。
7、织入(Weaving)
将切面切入到目标方法之中,使目标方法得到增强的过程被称之为织入。
实例代码
相关依赖包
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.8.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
定义和实现日志切面
package com.lingyejun.annotation; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; /**
* @Author: Lingye
* @Date: 2018/11/11
* @Describe:
* 定义日志切面
* @Lazy 注解:容器一般都会在启动的时候实例化所有单实例 bean,如果我们想要 Spring 在启动的时候延迟加载 bean,需要用到这个注解
* value为true、false 默认为true,即延迟加载,@Lazy(false)表示对象会在初始化的时候创建
*
* @Modified By:
*/
@Aspect
@Component
@Lazy(false)
public class LoggerAspect { /**
* 定义切入点:对要拦截的方法进行定义与限制,如包、类
*
* 1、execution(public * *(..)) 任意的公共方法
* 2、execution(* set*(..)) 以set开头的所有的方法
* 3、execution(* com.lingyejun.annotation.LoggerApply.*(..))com.lingyejun.annotation.LoggerApply这个类里的所有的方法
* 4、execution(* com.lingyejun.annotation.*.*(..))com.lingyejun.annotation包下的所有的类的所有的方法
* 5、execution(* com.lingyejun.annotation..*.*(..))com.lingyejun.annotation包及子包下所有的类的所有的方法
* 6、execution(* com.lingyejun.annotation..*.*(String,?,Long)) com.lingyejun.annotation包及子包下所有的类的有三个参数,第一个参数为String类型,第二个参数为任意类型,第三个参数为Long类型的方法
* 7、execution(@annotation(com.lingyejun.annotation.Lingyejun))
*/
@Pointcut("@annotation(com.lingyejun.annotation.Lingyejun)")
private void cutMethod() { } /**
* 前置通知:在目标方法执行前调用
*/
@Before("cutMethod()")
public void begin() {
System.out.println("==@Before== lingyejun blog logger : begin");
} /**
* 后置通知:在目标方法执行后调用,若目标方法出现异常,则不执行
*/
@AfterReturning("cutMethod()")
public void afterReturning() {
System.out.println("==@AfterReturning== lingyejun blog logger : after returning");
} /**
* 后置/最终通知:无论目标方法在执行过程中出现一场都会在它之后调用
*/
@After("cutMethod()")
public void after() {
System.out.println("==@After== lingyejun blog logger : finally returning");
} /**
* 异常通知:目标方法抛出异常时执行
*/
@AfterThrowing("cutMethod()")
public void afterThrowing() {
System.out.println("==@AfterThrowing== lingyejun blog logger : after throwing");
} /**
* 环绕通知:灵活自由的在目标方法中切入代码
*/
@Around("cutMethod()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取目标方法的名称
String methodName = joinPoint.getSignature().getName();
// 获取方法传入参数
Object[] params = joinPoint.getArgs();
Lingyejun lingyejun = getDeclaredAnnotation(joinPoint);
System.out.println("==@Around== lingyejun blog logger --》 method name " + methodName + " args " + params[0]);
// 执行源方法
joinPoint.proceed();
// 模拟进行验证
if (params != null && params.length > 0 && params[0].equals("Blog Home")) {
System.out.println("==@Around== lingyejun blog logger --》 " + lingyejun.module() + " auth success");
} else {
System.out.println("==@Around== lingyejun blog logger --》 " + lingyejun.module() + " auth failed");
}
} /**
* 获取方法中声明的注解
*
* @param joinPoint
* @return
* @throws NoSuchMethodException
*/
public Lingyejun getDeclaredAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 反射获取目标类
Class<?> targetClass = joinPoint.getTarget().getClass();
// 拿到方法对应的参数类型
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
// 根据类、方法、参数类型(重载)获取到方法的具体信息
Method objMethod = targetClass.getMethod(methodName, parameterTypes);
// 拿到方法定义的注解信息
Lingyejun annotation = objMethod.getDeclaredAnnotation(Lingyejun.class);
// 返回
return annotation;
}
}
自定义一个注解
package com.lingyejun.annotation; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @Author: Lingye
* @Date: 2018/11/11
* @Describe:
* @Modified By:
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lingyejun { /**
* 何种场景下的通用日志打印
*
* @return
*/
String module();
}
调用切面类
package com.lingyejun.annotation; import org.springframework.stereotype.Component; /**
* @Author: Lingye
* @Date: 2018/11/11
* @Describe:
* @Modified By:
*/
@Component
public class LoggerApply { @Lingyejun(module = "http://www.cnblogs.com/lingyejun/")
public void lingLogger(String event) throws Exception {
System.out.println("lingLogger(String event) : lingyejun will auth by blog address");
throw new Exception();
}
}
测试代码
package com.lingyejun.annotation; import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class AnnotationTest { @Autowired
private LoggerApply loggerApply; @Test
public void testAnnotationLogger() {
try {
loggerApply.lingLogger("Blog Home");
} catch (Exception e) {
System.out.println("a exception be there");
}
}
}
效果展示

SpringBoot 通过自定义注解实现AOP切面编程实例的更多相关文章
- 注解与AOP切面编程实现redis缓存与数据库查询的解耦
一般缓存与数据库的配合使用是这样的. 1.查询缓存中是否有数据. 2.缓存中无数据,查询数据库. 3.把数据库数据插入到缓存中. 其实我们发现 1,3 都是固定的套路,只有2 是真正的业务代码.我们可 ...
- 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版
欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...
- Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志
其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...
- 基于springboot通过自定义注解和AOP实现权限验证
一.移入依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spri ...
- Spring Boot 中使用自定义注解,AOP 切面打印出入参日志及Dubbo链路追踪透传traceId
一.使用背景 开发排查系统问题用得最多的手段就是查看系统日志,在分布式环境中一般使用 ELK 来统一收集日志,但是在并发大时使用日志定位问题还是比较麻烦,由于大量的其他用户/其他线程的日志也一起输出穿 ...
- 注解配置AOP切面编程
1.导入先关jar包 2.编写applicationContext.xml,配置开启注解扫描和切面注解扫描 <?xml version="1.0" encoding=&quo ...
- SpringBoot自定义注解、AOP打印日志
前言 在SpringBoot中使用自定义注解.aop切面打印web请求日志.主要是想把controller的每个request请求日志收集起来,调用接口.执行时间.返回值这几个重要的信息存储到数据库里 ...
- 如何通过自定义注解实现AOP切点定义
面向切面编程(Aspect Oriented Programming, AOP)是面向对象编程(Object Oriented Programming,OOP)的强大补充,通过横切面注入的方式引入其他 ...
- Spring MVC通过AOP切面编程 来拦截controller 实现日志的写入
首选需要参考的是:[参考]http://www.cnblogs.com/guokai870510826/p/5977948.html http://www.cnblogs.com/guokai8 ...
随机推荐
- 在Extjs中动态增加控件
Ext.onReady(function () { Ext.QuickTips.init(); Ext.form.Field.prototype.msgTarget = 'side'; var aut ...
- redis安装之zmalloc.h:55:2: error: #error "Newer version of jemalloc required"错误
redis是C语言编写的软件,安装前需要编译,需要gcc编译环境,确认安装gcc编译环境后(安装gcc命令:yum install gcc-c++) 在redis解压目录下,确认有Makefile文件 ...
- Vitamio视频播放器
前言 虽然Android已经内置了VideoView组件和MediaPlayer类来支持开发视频播放器,但支持格式.性能等各方面都十分有限,这里与大家一起利用免费的Vitamio来打造属于自己的And ...
- poj_2315 最小费用最大流
题目大意 一个图上有N个顶点,从1到N标号,顶点之间存在一些无向边,边有长度,要求从顶点1走到顶点N,再从顶点N走回顶点1,其中不必要经过每个顶点,但是要求走的路径上的边只能经过一次.求出从1---& ...
- bond的操作方式
本文转自网上,版权归属原作者,原文地址 :https://www.cnblogs.com/5201351/p/4898342.html 操作系统:CentOS Linux release 7.1.15 ...
- 配置maven使用nexus
本文简单介绍使用配置maven使用nexus仓库,在团队中使用nexus,避免每个人都从中央仓库去下载依赖,节省带宽,提高下载速度,同时也减少了中央仓库的压力 配置在maven中使用nexus很简单( ...
- AngularJS』一点小小的理解
『AngularJS』一点小小的理解 AngularJS 是一个前端的以Javascript为主的MVC框架.与AngularJS相类似的还有EmberJS. 随着时代在进步,各种各样的开发理念与 ...
- 【BZOJ4285】使者 cdq分治+扫描线+树状数组
[BZOJ4285]使者 Description 公元 8192 年,人类进入星际大航海时代.在不懈的努力之下,人类占领了宇宙中的 n 个行星,并在这些行星之间修建了 n - 1 条星际航道,使得任意 ...
- SSH电力项目三 - Dao层、service层查询实现(HQL)
底层方法封装:模糊查询,姓张的人 查询思路:select * from elec_text o #Dao层 where o.textName like '%张%' ...
- Mac OS X运行程序出现bad interpreter: operation not permitted的解决方案
最近想在我的mac笔记本上安装gvim,从官网上下载了程序后竟然非常诡异的双击无法打开,命令行执行时系统报错: /bin/sh bad interpreter operation not permit ...