一直心心念的想写一篇关于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切面编程实例的更多相关文章

  1. 注解与AOP切面编程实现redis缓存与数据库查询的解耦

    一般缓存与数据库的配合使用是这样的. 1.查询缓存中是否有数据. 2.缓存中无数据,查询数据库. 3.把数据库数据插入到缓存中. 其实我们发现 1,3 都是固定的套路,只有2 是真正的业务代码.我们可 ...

  2. 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版

    欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...

  3. Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志

    其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...

  4. 基于springboot通过自定义注解和AOP实现权限验证

    一.移入依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spri ...

  5. Spring Boot 中使用自定义注解,AOP 切面打印出入参日志及Dubbo链路追踪透传traceId

    一.使用背景 开发排查系统问题用得最多的手段就是查看系统日志,在分布式环境中一般使用 ELK 来统一收集日志,但是在并发大时使用日志定位问题还是比较麻烦,由于大量的其他用户/其他线程的日志也一起输出穿 ...

  6. 注解配置AOP切面编程

    1.导入先关jar包 2.编写applicationContext.xml,配置开启注解扫描和切面注解扫描 <?xml version="1.0" encoding=&quo ...

  7. SpringBoot自定义注解、AOP打印日志

    前言 在SpringBoot中使用自定义注解.aop切面打印web请求日志.主要是想把controller的每个request请求日志收集起来,调用接口.执行时间.返回值这几个重要的信息存储到数据库里 ...

  8. 如何通过自定义注解实现AOP切点定义

    面向切面编程(Aspect Oriented Programming, AOP)是面向对象编程(Object Oriented Programming,OOP)的强大补充,通过横切面注入的方式引入其他 ...

  9. Spring MVC通过AOP切面编程 来拦截controller 实现日志的写入

    首选需要参考的是:[参考]http://www.cnblogs.com/guokai870510826/p/5977948.html    http://www.cnblogs.com/guokai8 ...

随机推荐

  1. python2.0 s12 day2

    s12 day2 视频每节的内容 05 python s12 day2 python编码   1.第一句python代码 python 执行代码的过程 文件读到内存 分析内容 编译字节码  转换机器码 ...

  2. ping命令和telnet命令

    1.检查能不能连接上远程主机 ping  主机ip 2.检查远程主机端口是不是开放 telnet 198.10.10.69 1521 Trying 198.10.10.69...Connected t ...

  3. 在properties.xml中定义变量,在application.xml中取值问题

    如果为application.xml中的变量赋默认值,同时又在properties.xml中变量赋值,而加载后是取不到properties.xml中的值的问题. 解决这个问题需要加上黑体部分配置: & ...

  4. ZooKeeper(七)-- ZK原生API实现分布式锁

    一.使用场景 在分布式应用,往往存在多个进程提供同一服务.这些进程有可能在相同的机器上,也有可能分布在不同的机器上. 如果这些进程共享了一些资源,可能就需要分布式锁来锁定对这些资源的访问. 二.实现分 ...

  5. Linux mysqladmin 命令

    mysqladmin命令可以用来设置或修改 MySQL 密码,常见用法如下: [root@localhost ~]$ mysqladmin -uroot password 'newPass' # 在无 ...

  6. C++中的字节对齐分析

    struct A { int a; char b; short c; }; struct B { char a; int b; short c; }; #pragma pack(2) struct C ...

  7. iOS - UITableView判断reloadData加载数据已经结束

    问题: stackoverflow上有人提问这样的问题 http://stackoverflow.com/questions/16071503/how-to-tell-when-uitableview ...

  8. select 相关 获取当前项以及option js选定

    $("#product option[value='170']").prop("selected","true")//要确定是selecte ...

  9. 图的建立(邻接矩阵)+深度优先遍历+广度优先遍历+Prim算法构造最小生成树(Java语言描述)

    主要参考资料:数据结构(C语言版)严蔚敏   ,http://blog.chinaunix.net/uid-25324849-id-2182922.html   代码测试通过. package 图的建 ...

  10. Android 长截屏原理

    https://android-notes.github.io/2016/12/03/android%E9%95%BF%E6%88%AA%E5%B1%8F%E5%8E%9F%E7%90%86/   a ...