前段时间各种面试,aop问到就蒙逼,所以结合新学的springboot重新理一下这玩意儿,很重要啊
一、AOP概述
AOP(面对切面编程)是对OOP(面向对象编程)的补充,总体来说,编程范式包含:面向过程编程、面向对象编程、函数式编程、事件驱动编程、面向切面编程。
AOP的出现主要是为了解决如下的几个问题:
1.代码重复性的问题
2.关注点的分离(包含了水平分离:展示层->服务层->持久层;垂直分离:模块划分(订单、库存等);切面分离:分离功能性需求与非功能性需求)
AOP使用优势:
1.集中处理某一关注点/横切逻辑
2.可以方便的添加/删除关注点
3.侵入性少,增强代码的可读性和可维护性
AOP的应用场景:
权限控制、缓存控制、事务控制、审计日志、性能监控、分布式追踪、异常处理
所以这么优秀的东西绝不仅仅局限于Java,AOP是支持多语言开发的。
二、SpringAOP的使用
Spring AOP的使用方式包含XML配置和注解方式(比较方便),下面看一下基于注解方式的AOP。
AOP的注解主要包括@Aspect、@Pointcut、Advice三种。在详细记录前先给出一个AOP的简单使用代码(流程:引入依赖–>定义切面类–>在切面类中定义Advice以及前后逻辑),如果要获取方法的一些属性(比如方法名,返回值、参数等等),除了环绕通知需要传入ProceedingJoinPoint对象,其他的Advice则是需要传入JoinPoint对象,两类不同!
<!-- 添加AOP的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
package com.imooc.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect//表明它是一个切面类
@Component//交给Spring管理
public class HttpAspect {
// //定义拦截GirlController中所有public方法
// @Before("execution(public * com.imooc.controller.GirlController.*(..))")
// public void log() {
// System.out.println("******拦截前的逻辑******");
// }
//
// @After("execution(public * com.imooc.controller.GirlController.*(..))")
// public void doAfter() {
// System.out.println("******拦截后的逻辑******");
// }
/*
* 上面拦截的方式比较繁琐,因为Before和After的匹配规则有重复代码
*
* 可以先定义一个Pointcut,然后直接拦截这个方法即可
*
*/
//这里就定义了一个总的匹配规则,以后拦截的时候直接拦截log()方法即可,无须去重复写execution表达式
@Pointcut("execution(public * com.imooc.controller.GirlController.*(..))")
public void log() {
}
@Before("log()")
public void doBefore() {
System.out.println("******拦截前的逻辑******");
}
@After("log()")
public void doAfter() {
System.out.println("******拦截后的逻辑******");
}
}
1.@Aspect
主要用来标注Java类,表明它是一个切面配置的类,通常下面也会加上@Component注解来表明它由Spring管理
2.@Pointcut
主要有pointCutExpression(切面表达式)来表达,用来描述你要在哪些类的哪些方法上注入代码。其中切面表达式包含了designators(指示器,主要描述通过哪些方式去匹配Java类的哪些方法:如execution()等)和wildcards(通配符:如*)以及operators(运算符:如&&、||、!),具体如下所示
designators指示器
表示想要通过什么样的方式匹配想要的方法,具体组成如下图,重点是execution()
1.匹配包/类型within()
//匹配ProductService类种的所有方法
@Poincut("within(com.hhu.service.ProductService)")
public void matchType(){
...
}
//匹配com.hhu包及其子包下所有类的方法
@Pointcut("within(com.hhu..*)")
public void matchPackage(){
...
}
2.匹配对象(主要有this和target以及bean)
//匹配AOP对象的目标对象为指定类型的方法,即DemoDao的aop的代理对象
@Pointcut("this(com.hhu.DemaoDao)")
public void thisDemo() {
...
}
//匹配实现IDao接口的目标对象(而不是aop代理后的对象,这里即DemoDao的方法)
@Pointcut("target(com.hhu.Idao)")
public void targetDemo() {
...
}
//匹配所有以Service结尾的bean中的方法
@Pointcut("bean(*Service)")
public void beanDemo() {
...
}
3.参数匹配(主要有execution()和args()方法)
//过滤出第一个参数是long类型的并且在com.hhu.service包下的方法,如果是第一个参数是Long,第二个参数是String则可以写成args(Long,String),如果匹配第一个为Long,其它任意的话则可以写成args(Long..)
@Pointcut("args(Long) && within(com.hhu.service.*)")
public void matchArgs() {
...
}
@Before("mathArgs()")
public void befor() {
System.out.println("");
System.out.println("###before");
}
4.匹配注解
//匹配方法标注有AdminOnly注解的方法
@Pointcut("@annotation(com.hhu.demo.security.AdminOnly)")
public void annoDemo() {
...
}
//匹配标注有Beta的类下的方法,要求annotation的RetentionPplicy级别为CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void annoWithinDemo() {
...
}
//匹配标注有Repository类下的方法,要求的annotation的RetentionPolicy级别为RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void annoTargetDemo() {
...
}
//匹配传入的参数类型标注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void annoArgsDemo() {
...
}
5.execution()
标准的execution表达式如下,共有5个参数:
execution(
//权限修饰符(如public、private)
[modifier-pattern],
//返回值(如*表示任意返回值,void表示无返回值)
ret-type-pattern,
//包名(如com.hhu.service.*Service.*(..)表示对应包com.hhu.service中以Service结尾的类中的任意方法(该方法可以带任意参数),如果匹配无参的则可以写成(),如果像拦截产生异常的方法,则可以写成(..) throws+具体异常 )
[declaring-type-pattern],
name-pattern(或者是param-pattern),
[throws-pattern]
)
其中带有方括号的参数表示可以缺省。比如
@Pointcut("execution(* *..find*(Long)) && within(com.imooc..*) ")
wildcard主要包括常用的三种:
*表示匹配任意数量的字符
+表示匹配制定类及其子类
..表示一般用于匹配任意数的子包或参数。
operators主要包括如下的三种:
&&表示与操作
||表示或操作
!表示非操作
3.Advice注解
表示代码织入的时机,如执行之前、执行之后等等,主要有5种,通常是进行注解切面注解后会紧接着写Advice,格式为Advice(“切面拦截方法”)。比如下面的
//切面注解
@Pointcut("@annotation(com.imooc.anno.AdminOnly) && within(com.imooc..*)")
public void matchAnno(){}
//Advice注解
@After("matchAnno()")
public void after() {
System.out.println("###after");
}
- @Before,前置通知
- @After,后置通知,不管代码是成功还是抛出异常,都会织入
- @AfterReturning,返回通知,当且仅当方法成功执行
- @AfterThrowing,异常通知,当且仅当方法抛出异常
- @Around,环绕通知,基本包含了上述所有的植入位置
获取参数的话一般跟Before的Advice结合在一起的,代码如下
@Before("matchLongArg() && args(productId)")
//这里是因为知道参数的类型是Long
public void before(Long productId) {
System.out.println("###before,get args:" + productId);
}
其中@AfterReturning注解可以获取方法的返回值,比如下面的
//切面注解
@Pointcut("@annotation(com.imooc.anno.AdminOnly) && within(com.imooc..*)")
public void matchAnno(){}
//Advice注解,returning的值result代表的就是返回值,形参Object类表示
@AfterReturning(value="matchAnno()",returning="result")
public void after(java.lang.Object result) {
System.out.println("###after");
}
@Around注解比较强大,一般有它的存在就可以不用Before或者After这类注解了,如下
//由于这里可能有返回值所以必须有Object,而且必须获取Proceeding的上下文才能让方法得以继续,所以会有此形参
@Around("matchAnno()")
public java.lang.Object after(ProceedingJoinPoint joinPoint) {
System.out.println("###before");
//定义返回值
java.lang.Object result = null;
try {
//获取返回值
result = joinPoint.proceed(joinPoint.getArgs());
System.out.println("###after returning");
} catch(Throwable e) {
System.out.println("###ex");
e.printStackTrace();
} finally {
System.out.println("###finally");
}
return result;
}
三、Spring AOP的原理
AOP的设计主要用到的设计模式包含了代理模式、责任链模式;AOP的实现方式包含了两种:基于JDK实现、基于CGlib实现。
织入时机:主要有以下的3种
1. 编译期(如AspectJ)
2. 类加载时(如AspectJ5以上版本)
3. 运行时(如Spring AOP)
Spring AOP属于运行时织入,这种织入方式是基于代理实现的,这样代理才能在实际方法执行前后进行一些逻辑处理,可以有两种:从静态代理到动态代理(动态代理包含基于接口代理与基于继承代理)。
1.代理模式(简单AOP的模拟)–静态代理
//1.首先定义一个接口
package com.imooc.pattern;
public interface Subject {
void request();
}
//2.让实际对象和代理对象都实现这个接口
//2.1实际对象
package com.imooc.pattern;
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("real subject execute request");
}
}
//2.2织入逻辑对象,持有实际对象
package com.imooc.pattern;
public class Proxy implements Subject{
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("before");
try {
realSubject.request();
} catch (Exception e) {
System.out.println("ex:" + e.getMessage());
throw e;
} finally {
System.out.println("after");
}
}
}
//3.客户端调用
package com.imooc.pattern;
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Proxy proxy = new Proxy(realSubject);
realSubject.reques