Spring in Anction:Spring AOP 小记

1.什么是AOP(面向切面编程)

AOP

不扯那些概念的东西,简单说来AOP是OOP的一个补充,AOP可以在程序运行期追加一些公用的功能,比如权限判断,日志记录,这些功能都是项目需要的,但是又不能每个地方都调用,这样无疑增加了代码的复杂度和工作量,我们可以将这些分散在系统中的公用代码集中于一个地方并通过aop技术应用于系统各个地方。

2. AOP术语

  • 切点:我们需要插入这些公用功能的点?比如哪些类?哪些方法?等等(何处调用)
  • 切面:这些公用功能代码(调用什么)
  • 通知:在什么时候调用?

    3.通知分类

  • 前置通知(Before):在目标方法调用前调用
  • 后置通知(After):在目标方法调用后调用
  • 返回通知(AfterReturning):在目标方法成功执行后调用
  • 异常通知(AfterThrowing):在目标方法执行失败抛异常后调用
  • 环绕通知(Around):将目标方法包裹,由你决定什么时候调用(最强大,最常用)

    3.Spring AOP

    Spring 通过生成代理类将目标对象包裹从而实现AOP,调用者调用目标方法时,其实是在调用代理类,再由代理类
    调用目标方法,从而控制对目标方法的调用,Spring只支持最细方法级别的连接点,如果需要实现更细粒度的控制
    则需要借助专业的AOP框架AspectJ,可以控制字段,构造方法等更细的东西,不过需要学习新的语法。

AOP

4.Spring 切点表达式

可以通过这些表达式配置切点

AOP

上面是Spring支持的AspectJ 切点表达式。除execution是增加切点范围的外,其他都是用于缩小范围

5.编写切点

AOP

6.编写切面

用注解@Aspect表示一个切面,注解用于类上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Aspect //表示是切面
@Component //Bean注入Spring
@Order(1)//优先执行该切面
public class TicketLoginAspect {
@Autowired
private HttpServletRequest request;

/**
* 切点
*/
//表示作用于TicketLogin注解
@Pointcut("@annotation(com.cnct.webchat.common.annotation.TicketLogin)")
public void loginPointCut() {
//扩展作用范围,不用在每个@Around后写很长的表达式了,只需写方法名即可,像下面
}

@Around("loginPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
}
}

写完切面后要开启Spring aop自动代理才能生效,如果使用JavaConfig的话,在配置类上使用
@EnableAspectJ-AutoProxy即可,如果用XML配置则加入aop:aspectJ-autoproxy/

7.通过注解为目标类增加新功能(方法)

既然AOP通过代理实现,那么就可以为目标对象增加新的代理方法。

1
2
3
4
5
6
7
8
@Aspect //定义切面
@Component
public class TestAop{
@DeclareParents(value = "com.lyq3.NewFunctionInterface+",
defaultImpl = NewFunctionImpl.class
)
public static Abc abc;
}
  • value 指定那种类型的Bean要引入,(后面的“+”号表示所有的子类型而不是本身)
  • defaultImpl 指定引入功能的实现类
  • @DeclareParents 所标注的静态属性指明要引入的接口,也就是要为Abc接口增加新功能

8.注意:踩坑记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Aspect
@Component
@Order(2)//登录注解执行后执行该注解
public class HandleRecordAspect {
@Autowired
private HandleRecordMapper handleRecordMapper;
@Autowired
private HttpSession session;
/**
* 切点
*/
@Pointcut("@annotation(com.cnct.webchat.common.annotation.HandleRecord)")
public void handlePointCut() {

}

@Around("handlePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
//先执行方法
Object result = point.proceed();
//访问目标方法的参数:
Object[] args = point.getArgs();

Signature sig = point.getSignature();
MethodSignature msig = null;
msig = (MethodSignature) sig;
Method method = msig.getMethod();
// String methodName = msig.getName();//方法名
HandleRecord handleRecord = method.getAnnotation(HandleRecord.class);//获取注解
//操作标识(增?删?改?)
int handle_status = handleRecord.handle_status();
String remark = handleRecord.value();
}
}

上面的代码是我在项目中实现一个存操作纪录的部分代码,咋一看没毛病。@HandleRecord这个自定义注解在Controller层能用,我放到Service层的时候(service有接口和接口实现,注解放实现类方法上)

1
HandleRecord handleRecord = method.getAnnotation(HandleRecord.class);

这句代码是获取不到注解的,也就是说 handleRecord = null;

原因是AOP采用的jdk代理,MethodSignature 转型之后,会丢失子类的方法注解,所以得先获取实现类的方法

修改下代码,就能用了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//先执行方法
Object result = point.proceed();
//访问目标方法的参数:
Object[] args = point.getArgs();

Signature sig = point.getSignature();
MethodSignature msig = null;
msig = (MethodSignature) sig;
Method method = msig.getMethod();
//MethodSignature 转型之后,会丢失子类的方法注解,所以得先获取实现类的方法
//============================================
Method soruceMethod = point.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
//=============================================
HandleRecord handleRecord = soruceMethod.getAnnotation(HandleRecord.class);//获取注解
//操作标识(增?删?改?)
int handle_status = handleRecord.handle_status();
String remark = handleRecord.value();

详情查看Spring in Action 第四版 第四章