相信大家通过上一篇的Spring AOP 编程入门对AOP 的术语已经有了一定的认知,概念看起来总是有点懵,所以需要慢慢在应用中理解,今天主要是通过一些小例子加深一下aop在实际应用中到底能干嘛。
一、初步认识 Spring AOP
1. Spring AOP 的特点
AOP 框架有很多种, Spring 中的 AOP 是通过动态代理实现的。不同的 AOP 框架支持的连接点也有所区别,例如,AspectJ 和 JBoss,除了支持方法切点,它们还支持字段和构造器的连接点。而 Spring AOP 不能拦截对对象字段的修改,也不支持构造器连接点,我们无法在 Bean 创建时应用通知。
2. Spring AOP 的简单例子
上一篇博客我们也新建了一个小demo,主要是为了弄清楚advice(通知)执行的顺序。
首先先创建一个接口TakeawayService.java:
package com.example.demo.service;
import com.sun.org.apache.xpath.internal.objects.XString;
public interface TakeawayService {
/**
* 点餐接口
* @return
*/
public String Order();
}
在创建两个类实现TakeawayService.java;
ZSServiceImpl.java:
package com.example.demo.service.impl;
import org.springframework.stereotype.Service;
import com.example.demo.service.TakeawayService;
@Service
public class ZSServiceImpl implements TakeawayService {
@Override
public String Order() {
return "张三点了一份爆浆牛丸";
}
}
WMZServiceImpl.java:
package com.example.demo.service.impl;
import com.example.demo.service.TakeawayService;
import com.example.demo.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class WMZServiceImpl implements TakeawayService {
@Override
public String Order()
{
return "王麻子点了一份瓜皮香锅";
}
}
测试方法TakeController.java:
package com.example.demo.controller;
import com.example.demo.entity.Response;
import com.example.demo.entity.ResponseResult;
import jdk.internal.org.objectweb.asm.tree.analysis.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.service.TakeawayService;
@RestController
@RequestMapping("/api")
public class TakeController {
/**
*此处@Autowired 也可替换为 @Resource(name="") name:中填写你想注入的bean对象名称
* 如:@Resource("WMZServiceImpl")
* private TakeawayService wmzService;
*/
@Qualifier("WMZServiceImpl")//指定注入的bean对象名称
@Autowired
private TakeawayService wmzService;
@Qualifier("ZSServiceImpl")
@Autowired
private TakeawayService zsService;
@RequestMapping("/order")
public ResponseResult Orde()
{
String wmz= wmzService.Order();
String zs=zsService.Order();
System.out.println(wmz);
System.out.println(zs);
return Response.makeOKRsp(wmz+";"+zs);
}
}
运行结果:
然后隔了一段时间,产品经理觉得我们可不可以在用户点餐之前整点提示性语言比如,阿里阿塞哟、刷我滴卡、亚麻带类似的,当然其它的暂时也没想到了。那么这个我们怎么办?要么是新定义一个方法,要么就在原来的点餐方法里重写各自的方法,照这样的进度下去东京热不热我不清楚,反正上海今天是真的热。如果用aop 来解决的话就没上面这种难以维护了,让我们先加一个亚麻带。
2.1.引用依赖:
<!--引入AOP依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
2.2.新建切面类
按照上一篇通知执行的顺序可知在此场景我们需要用到@Before,新建切面类OrderAop.java:
package com.example.demo.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect // 表示它是一个切面。
@Component //表明它将作为一个Spring Bean 被装配
public class OrderAop {
/**
* @description 在连接点执行之前执行的通知
*/
@Before("execution(public * com.example.demo.service.TakeawayService.*(..)))")
public void doBefore(){
System.out.println("阿里阿塞哟!");
}
}
然后在运行看下结果:
这样就可以完全没有去更改之前的代码,通过aop实现了这个“阿里阿塞哟”。
然后又隔了几天,这个产品经理啊又开始搞事了,说什么今天天气不错,或者今年是张san的本命年,更有甚者是说zs最近心情不好,所以咱们要临时取消zs的‘阿里阿塞哟’,只通知wmz,这个时候不要慌,Spring AOP 所支持的 AspectJ 切点指示器完全能实现这个变态要求。
2.3. Spring AOP 所支持的 AspectJ 切点指示器
网上找了一张图,大家伙讲究看下,更详细的说明可以自己再找找博文资料之类。
修改OrderAop.java:
package com.example.demo.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect // 表示它是一个切面。
@Component //表明它将作为一个Spring Bean 被装配
public class OrderAop {
/**
* @description 在连接点执行之前执行的通知
* 全匹配
* execution(public * com.example.demo.service.TakeawayService.*(..)))
* 只匹配zs within 限定该切点仅匹配的包 bean 在切点中选择
* execution(public * com.example.demo.service.TakeawayService.Order(..)) && within(com.example.demo.service.impl.*) && bean(ZSServiceImpl)
*/
@Before("execution(public * com.example.demo.service.TakeawayService.Order(..)) && within(com.example.demo.service.impl.*) && bean(ZSServiceImpl)")
public void doBefore(){
System.out.println("阿里阿塞哟!");
}
}
此时,切面只会对 ZSServiceImpl.java
这个类生效,执行结果:
切面就是这么社会,所以我以后再也不怕产品经理让我改亚麻带了。
3. Spring AOP 中5 种通知类型
上一篇博客已经有描述过具体是那五种通知,以及各自的执行顺序这里就不再加以描述了,就只贴一张网图当做复习了。
图看着像是有重影,但是碍于本人比较懒,所以就懒得找别的图了。
3.1. 通知执行顺序
再次修改切面类OrderAop.java:
package com.example.demo.aop;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* 饿鸟嘛配送
**/
@Aspect
@Component
public class EatAop {
/**
*@description 定义切入点,切入点为com.example.demo.controller.AopController中的所有函数
*@description 通过@Pointcut注解声明频繁使用的切点表达式
* */
@Pointcut("execution(public * com.example.demo.controller.AopController.*(..)))")
public void EatAop()
{
}
/**
* @description 在连接点执行之前执行的通知
*/
@Before("EatAop()")
public void doBeforeEat(){
System.out.println("饿鸟嘛配送员登录接单系统为接单做准备!");
}
/**
* @description 在连接点执行之后执行的通知(返回通知和异常通知的异常)
*/
@After("EatAop()")
public void doAfterEat(){
System.out.println("饿鸟嘛配送员为今天的外卖单量感到异常吃惊!");
}
/**
* @description 在连接点执行之后执行的通知(返回通知)
*/
@AfterReturning("EatAop()")
public void doAfterReturningEat(){
System.out.println("返回的通知:饿鸟嘛配送员接单完毕,并会心一笑百媚生!");
}
/**
* @description 在连接点执行之后执行的通知(异常通知)
*/
@AfterThrowing("EatAop()")
public void doAfterThrowingGame(){
System.out.println("异常通知:服务错误,请联系饿鸟嘛系统管理员!");
}
}
修改ZSServiceImpl.java:
package com.example.demo.service.impl;
import org.springframework.stereotype.Service;
import com.example.demo.service.TakeawayService;
@Service
public class ZSServiceImpl implements TakeawayService {
@Override
public String Order() {
System.out.println("zs爆浆牛丸");
return "张三点了一份爆浆牛丸";
}
}
修改 WMZServiceImpl.java:
package com.example.demo.service.impl;
import com.example.demo.service.TakeawayService;
import com.example.demo.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class WMZServiceImpl implements TakeawayService {
@Override
public String Order()
{
System.out.println("wmz瓜皮香锅");
return "王麻子点了一份瓜皮香锅";
}
}
运行结果如下:
3.2. SpringAOP增强处理 @around
@Around的作用
既可以在目标方法之前织入增强动作,也可以在执行目标方法之后织入增强动作;
可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标目标方法的执行;
可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值; 当需要改变目标方法的返回值时,只能使用Around方法;
切面类OrderAop.java新增以下方法:
/**
* @description 定义增强
* @param pj
*/
@Around("point()")
public void doAround(ProceedingJoinPoint pj) {
try {
System.out.println("Around 调用方法前 ");
pj.proceed();
System.out.println("Around 调用方法后");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
运行结果:
结果显而易见,值得注意的是 @Around
修饰的环绕通知类型,是将整个目标方法封装起来了,在使用时,我们需要调用 ProceedingJoinPoint
的 proceed()
方法。 如果没有调用 该方法,执行结果为 :
所以,如果不调用该对象的 proceed() 方法,表示原目标方法被阻塞调用。
3.3 通过注解处理通知中的参数
上面的例子,我们要进行增强处理的目标方法没有参数,在有参数的情况,并且在增强处理中使用该参数。
下面我们给接口增加一个参数,表示购买所花的金钱。通过AOP 增强处理,如果sz下单超过了了 4396元,就可以赠送一份鲜果饮汇源牌饮料。
代码修改如下:
TakeawayService.java
package com.example.demo.service;
import com.sun.org.apache.xpath.internal.objects.XString;
public interface TakeawayService {
/**
* 点餐接口
* @return
*/
public String Order(double price);
}
ZSServiceImpl.java
package com.example.demo.service.impl;
import org.springframework.stereotype.Service;
import com.example.demo.service.TakeawayService;
@Service
public class ZSServiceImpl implements TakeawayService {
@Override
public String Order(double price) {
System.out.println("zs爆浆牛丸:"+price);
return "张三点了一份爆浆牛丸";
}
}
WMZServiceImpl.java
package com.example.demo.service.impl;
import com.example.demo.service.TakeawayService;
import com.example.demo.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class WMZServiceImpl implements TakeawayService {
@Override
public String Order(double price)
{
System.out.println("wmz瓜皮香锅:"+price);
return "王麻子点了一份瓜皮香锅";
}
}
TakeController.java
package com.example.demo.controller;
import com.example.demo.entity.Response;
import com.example.demo.entity.ResponseResult;
import jdk.internal.org.objectweb.asm.tree.analysis.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.service.TakeawayService;
@RestController
@RequestMapping("/api")
public class TakeController {
/**
*此处@Autowired 也可替换为 @Resource(name="") name:中填写你想注入的bean对象名称
* 如:@Resource("WMZServiceImpl")
* private TakeawayService wmzService;
*/
@Qualifier("WMZServiceImpl")//指定注入的bean对象名称
@Autowired
private TakeawayService wmzService;
@Qualifier("ZSServiceImpl")
@Autowired
private TakeawayService zsService;
@RequestMapping("/order")
public ResponseResult Orde()
{
String wmz= wmzService.Order(12);
System.out.println(wmz);
String zs=zsService.Order(4396);
System.out.println(zs);
return Response.makeOKRsp(wmz+";"+zs);
}
}
OrderAop.java
package com.example.demo.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect // 表示它是一个切面。
@Component //表明它将作为一个Spring Bean 被装配
public class OrderAop {
/**
*@description 定义切入点,切入点为execution(public * com.example.demo.service.TakeawayService.*(..)))中的所有函数
*@description 通过@Pointcut注解声明频繁使用的切点表达式
* */
@Pointcut("execution(public * com.example.demo.service.TakeawayService.Order(double)) && args(price) && bean(ZSServiceImpl)")
public void point(double price)
{
}
/**
* @description 在连接点执行之前执行的通知
* 全匹配
* execution(public * com.example.demo.service.TakeawayService.*(..)))
* 只匹配zs within 限定该切点仅匹配的包 bean 在切点中选择
* execution(public * com.example.demo.service.TakeawayService.Order(..)) && within(com.example.demo.service.impl.*) && bean(ZSServiceImpl)
*/
/* @Before("point()")
public void doBefore(){
System.out.println("阿里阿塞哟!");
}
*/
/**
* @description 在连接点执行之后执行的通知(返回通知和异常通知的异常)
*/
/* @After("point()")
public void doAfter(){
System.out.println("after!");
}
*/
/**
* @description 在连接点执行之后执行的通知(返回通知)
*/
// @AfterReturning("point()")
// public void doAfterReturning(){
// System.out.println("返回通知:AfterReturning");
// }
/**
* @description 在连接点执行之后执行的通知(异常通知)
*/
//@AfterThrowing("point()")
//public void doAfterThrowing(){
// System.out.println("异常通知:AfterThrowing");
// }
/**
* @description 定义增强
* @param pj
*/
@Around("point(price)")
public String doAround(ProceedingJoinPoint pj,double price) {
try {
System.out.println("Around 调用方法前 ");
pj.proceed();
if(price>=4396)
{
System.out.println("zs下单超过了4399,赠送一份鲜果饮汇源牌饮料");
return "爆浆牛丸和饮料";
}
System.out.println("Around 调用方法后");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return "爆浆牛丸";
}
}
运行结果:
我们 可以看到,结果和预想的一致。因时间关系本人临时有事处理,决定吧原本今天 的XML 配置文件声明切面,放在明天另写一篇。
小结
spring aop 的简单运用今天就到这儿了,下次再见铁汁们!
源码:https://github.com/LeeCurtain/demo.git