更多相关内容,查看: spring.hhui.top/java
前面一篇博文 190301-SpringBoot基础篇AOP之基本使用姿式小结 介绍了aop的简单使用方式,在文章最后,抛出了几个问题待解决,本篇博文则将针对前面的问题,看下更多关于AOP的使用说明git
前面一文,主要介绍的是根据正则表达式来拦截对应的方法,接下来演示下如何经过注解的方式来拦截目标方法,实现也比较简单github
首先建立注解正则表达式
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnoDot {
}
复制代码
接着在目标方法上添加注解,这里借助前面博文中工程进行说明,新建一个com.git.hui.boot.aop.demo2.AnoDemoBean
,注意这个包路径,是不会被前文的AnoAspect
定义的Advice拦截的,这里新建一个包路径的目的就是为了尽量的减小干扰项spring
@Component
public class AnoDemoBean {
@AnoDot
public String genUUID(long time) {
try {
System.out.println("in genUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in genUUID finally!");
}
}
}
复制代码
接下来定义对应的advice, 直接在前面的AnoAspect
中添加(不知道前文的也不要紧,下面贴出相关的代码类,前文的类容与本节内容无关)bash
@Aspect
@Component
public class AnoAspect {
@Before("@annotation(AnoDot)")
public void anoBefore() {
System.out.println("AnoAspect ");
}
}
复制代码
测试代码dom
@SpringBootApplication
public class Application {
private AnoDemoBean anoDemoBean;
public Application(AnoDemoBean anoDemoBean) {
this.anoDemoBean = anoDemoBean;
this.anoDemoBean();
}
private void anoDemoBean() {
System.out.println(">>>>>>>" + anoDemoBean.genUUID(System.currentTimeMillis()));
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
复制代码
输出结果以下,在执行目标方法以前,会先执行before advice中的逻辑spring-boot
AnoAspect
in genUUID before process!
in genUUID finally!
>>>>>>>3a5d749d-d94c-4fc0-a7a3-12fd97f3e1fa|1551513443644
复制代码
一个方法执行时,若是有多个advice知足拦截规则,是全部的都会触发么?经过前面一篇博文知道,不一样类型的advice是均可以拦截的,若是出现多个相同类型的advice呢?学习
在前面一篇博文的基础上进行操做,咱们扩展下com.git.hui.boot.aop.demo.DemoBean
测试
@Component
public class DemoBean {
@AnoDot
public String genUUID(long time) {
try {
System.out.println("in genUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in genUUID finally!");
}
}
}
复制代码
对应的测试切面内容如
@Aspect
@Component
public class AnoAspect {
@Before("execution(public * com.git.hui.boot.aop.demo.*.*(*))")
public void doBefore(JoinPoint joinPoint) {
System.out.println("do in Aspect before method called! args: " + JSON.toJSONString(joinPoint.getArgs()));
}
@Pointcut("execution(public * com.git.hui.boot.aop.demo.*.*(*))")
public void point() {
}
@After("point()")
public void doAfter(JoinPoint joinPoint) {
System.out.println("do in Aspect after method called! args: " + JSON.toJSONString(joinPoint.getArgs()));
}
/** * 执行完毕以后,经过 args指定参数;经过 returning 指定返回的结果,要求返回值类型匹配 * * @param time * @param result */
@AfterReturning(value = "point() && args(time)", returning = "result")
public void doAfterReturning(long time, String result) {
System.out.println("do in Aspect after method return! args: " + time + " ans: " + result);
}
@Around("point()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("do in Aspect around ------ before");
Object ans = joinPoint.proceed();
System.out.println("do in Aspect around ------- over! ans: " + ans);
return ans;
}
@Before("point()")
public void sameBefore() {
System.out.println("SameAspect");
}
@Before("@annotation(AnoDot)")
public void anoBefore() {
System.out.println("AnoAspect");
}
}
复制代码
测试代码以下
@SpringBootApplication
public class Application {
private DemoBean demoBean;
public Application(DemoBean demoBean) {
this.demoBean = demoBean;
this.demoBean();
}
private void demoBean() {
System.out.println(">>>>> " + demoBean.genUUID(System.currentTimeMillis()));
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
复制代码
输出结果以下,全部的切面都执行了,也就是说,只要知足条件的advice,都会被拦截到
do in Aspect around ------ before
AnoAspect
do in Aspect before method called! args: [1551520547268]
SameAspect
in genUUID before process!
in genUUID finally!
do in Aspect around ------- over! ans: 5f6a5616-f558-4ac9-ba4b-b4360d7dc238|1551520547268
do in Aspect after method called! args: [1551520547268]
do in Aspect after method return! args: 1551520547268 ans: 5f6a5616-f558-4ac9-ba4b-b4360d7dc238|1551520547268
>>>>> 5f6a5616-f558-4ac9-ba4b-b4360d7dc238|1551520547268
复制代码
嵌套的方式有几种case,先看第一种
这里咱们借助第一节中的bean来继续模拟, 在AnoDemoBean
类中,新增一个方法
@Component
public class AnoDemoBean {
public String randUUID(long time) {
try {
System.out.println("in randUUID start!");
return genUUID(time);
} finally {
System.out.println("in randUUID finally!");
}
}
@AnoDot
public String genUUID(long time) {
try {
System.out.println("in genUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in genUUID finally!");
}
}
}
复制代码
对应的切面为
@Aspect
@Component
public class NetAspect {
@Around("@annotation(AnoDot)")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("In NetAspect doAround before!");
Object ans = joinPoint.proceed();
System.out.println("In NetAspect doAround over! ans: " + ans);
return ans;
}
}
复制代码
而后测试case须要改成直接调用 AnoDemoBean#randUUID
,须要看这个方法内部调用的genUUID
是否会被切面拦截住
@SpringBootApplication
public class Application {
private AnoDemoBean anoDemoBean;
public Application(AnoDemoBean anoDemoBean) {
this.anoDemoBean = anoDemoBean;
this.anoDemoBean();
}
private void anoDemoBean() {
System.out.println(">>>>>>>" + anoDemoBean.randUUID(System.currentTimeMillis()));
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
复制代码
输出结果以下,没有切面的日志,代表这种场景下,不会被拦截
in randUUID start!
in genUUID before process!
in genUUID finally!
in randUUID finally!
>>>>>>>0c6a5ccf-30c0-4ac0-97f2-3dc063580f3d|1551522176035
复制代码
依然使用前面的例子进行说明,不过是稍稍改一下AnoDemoBean
,调用第二节中的DemoBean的方法
DemoBean的代码以下
@AnoDot
public String genUUID(long time) {
try {
System.out.println("in DemoBean genUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in DemoBean genUUID finally!");
}
}
复制代码
而后AnoDemoBean的代码以下
@Component
public class AnoDemoBean {
@Autowired
private DemoBean demoBean;
public String randUUID(long time) {
try {
System.out.println("in AnoDemoBean randUUID start!");
return genUUID(time) + "<<<>>>" + demoBean.genUUID(time);
} finally {
System.out.println("in AnoDemoBean randUUID finally!");
}
}
@AnoDot
public String genUUID(long time) {
try {
System.out.println("in AnoDemoBean genUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in AnoDemoBean genUUID finally!");
}
}
}
复制代码
测试代码和前面彻底一致,接下来看下输出
in AnoDemoBean randUUID start!
in AnoDemoBean genUUID before process!
in AnoDemoBean genUUID finally!
### 上面三行为 anoDemoBean#randUUID方法调用 anoDemoBean#genUUID方法的输出结果,能够看到没有切面执行的日志输出
### 下面的为调用 demoBean#genUUID 方法,能够看到切面(NetAspect#doAround)执行的日志
In NetAspect doAround before!
in DemoBean genUUID before process!
in DemoBean genUUID finally!
In NetAspect doAround over! ans: f35b8878-fbd0-4840-8fbe-5fef8eda5e31|1551522532092
### 最后是收尾
in AnoDemoBean randUUID finally!
>>>>>>>e516a35f-b85a-4cbd-aae0-fa97cdecab47|1551522532092<<<>>>f35b8878-fbd0-4840-8fbe-5fef8eda5e31|1551522532092
复制代码
从上面的日志分析中,能够明确看出对比,调用本类中,知足被拦截的方法,也不会走切面逻辑;调用其余类中的知足切面拦截的方法,会走切面逻辑
这个和两个case有点像,不一样的是直接调用的方法也知足被切面拦截的条件,咱们主要关注点在于嵌套调用的方法,会不会进入切面逻辑,这里须要修改的地方就不多了,直接把 AnoDemoBean#randUUID
方法上添加注解,而后执行便可
@Component
public class AnoDemoBean {
@Autowired
private DemoBean demoBean;
@AnoDot
public String randUUID(long time) {
try {
System.out.println("in AnoDemoBean randUUID start!");
return genUUID(time) + "<<<>>>" + demoBean.genUUID(time);
} finally {
System.out.println("in AnoDemoBean randUUID finally!");
}
}
@AnoDot
public String genUUID(long time) {
try {
System.out.println("in AnoDemoBean genUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in AnoDemoBean genUUID finally!");
}
}
}
复制代码
输出结果以下
## 最外层的切面拦截的是 AnoDemoBean#randUUID 方法的执行
In NetAspect doAround before!
in AnoDemoBean randUUID start!
in AnoDemoBean genUUID before process!
in AnoDemoBean genUUID finally!
### 从跟上面三行的输出,能够知道内部调用的 AnoDemoBean#genUUID 即使知足切面拦截规则,也不会再次走切面逻辑
### 下面4行,代表其余类的方法,若是知足切面拦截规则,会进入到切面逻辑
In NetAspect doAround before!
in DemoBean genUUID before process!
in DemoBean genUUID finally!
In NetAspect doAround over! ans: d9df7388-2ef8-4b1a-acb5-6639c47f36ca|1551522969801
in AnoDemoBean randUUID finally!
In NetAspect doAround over! ans: cf350bc2-9a9a-4ef6-b496-c913d297c960|1551522969801<<<>>>d9df7388-2ef8-4b1a-acb5-6639c47f36ca|1551522969801
>>>>>>>cf350bc2-9a9a-4ef6-b496-c913d297c960|1551522969801<<<>>>d9df7388-2ef8-4b1a-acb5-6639c47f36ca|1551522969801
复制代码
从输出结果进行反推,一个结论是
前面测试的被拦截方法都是public,那么是否代表只有public方法才能被拦截呢?
从第三节基本能够看出,private方法首先淘汰出列,为啥?由于private方法正常来说只能内部调用,而内部调用不会走切面逻辑;因此接下来须要关注的主要放在默认做用域和protected做用域
@Component
public class ScopeDemoBean {
@AnoDot
String defaultRandUUID(long time) {
try {
System.out.println(" in ScopeDemoBean defaultRandUUID before!");
return UUID.randomUUID() + " | default | " + time;
} finally {
System.out.println(" in ScopeDemoBean defaultRandUUID finally!");
}
}
@AnoDot
protected String protectedRandUUID(long time) {
try {
System.out.println(" in ScopeDemoBean protectedRandUUID before!");
return UUID.randomUUID() + " | protected | " + time;
} finally {
System.out.println(" in ScopeDemoBean protectedRandUUID finally!");
}
}
@AnoDot
private String privateRandUUID(long time) {
try {
System.out.println(" in ScopeDemoBean privateRandUUID before!");
return UUID.randomUUID() + " | private | " + time;
} finally {
System.out.println(" in ScopeDemoBean privateRandUUID finally!");
}
}
}
复制代码
咱们不直接使用这个类里面的方法,借助前面的 AnoDemoBean
, 下面给出了经过反射的方式来调用private方法的case
@Component
public class AnoDemoBean {
@Autowired
private ScopeDemoBean scopeDemoBean;
public void scopeUUID(long time) {
try {
System.out.println("-------- default --------");
String defaultAns = scopeDemoBean.defaultRandUUID(time);
System.out.println("-------- default: " + defaultAns + " --------\n");
System.out.println("-------- protected --------");
String protectedAns = scopeDemoBean.protectedRandUUID(time);
System.out.println("-------- protected: " + protectedAns + " --------\n");
System.out.println("-------- private --------");
Method method = ScopeDemoBean.class.getDeclaredMethod("privateRandUUID", long.class);
method.setAccessible(true);
String privateAns = (String) method.invoke(scopeDemoBean, time);
System.out.println("-------- private: " + privateAns + " --------\n");
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
测试case
@SpringBootApplication
public class Application {
private AnoDemoBean anoDemoBean;
public Application(AnoDemoBean anoDemoBean) {
this.anoDemoBean = anoDemoBean;
this.anoDemoBean();
}
private void anoDemoBean() {
anoDemoBean.scopeUUID(System.currentTimeMillis());
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
复制代码
输出结果以下,从日志打印来看,protected和default方法的切面都走到了
-------- default --------
In NetAspect doAround before!
in ScopeDemoBean defaultRandUUID before!
in ScopeDemoBean defaultRandUUID finally!
In NetAspect doAround over! ans: 2ad7e509-c62c-4f25-b68f-eb5e0b53196d | default | 1551524311537
-------- default: 2ad7e509-c62c-4f25-b68f-eb5e0b53196d | default | 1551524311537 --------
-------- protected --------
In NetAspect doAround before!
in ScopeDemoBean protectedRandUUID before!
in ScopeDemoBean protectedRandUUID finally!
In NetAspect doAround over! ans: 9eb339f8-9e71-4321-ab83-a8953d1b8ff8 | protected | 1551524311537
-------- protected: 9eb339f8-9e71-4321-ab83-a8953d1b8ff8 | protected | 1551524311537 --------
-------- private --------
in ScopeDemoBean privateRandUUID before!
in ScopeDemoBean privateRandUUID finally!
-------- private: 1826afac-6eca-4dc3-8edc-b4ca7146ce28 | private | 1551524311537 --------
复制代码
本篇博文篇幅比较长,主要是测试代码比较占用地方,所以有必要简单的小结一下,作一个清晰的概括,方便不想看细节,只想获取最终结论的小伙伴
注解拦截方式:
@Around("@annotation(AnoDot)")
多advice状况:
嵌套场景
做用域
优先级
这个内容由于特别多,因此有必要单独拎出来,其主要的分类以下
一灰灰的我的博客,记录全部学习和工做中的博文,欢迎你们前去逛逛
尽信书则不如,以上内容,纯属一家之言,因我的能力有限,不免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
一灰灰blog
知识星球