AOP(Aspect Oriented Programming)
面向切面编程:基于OOP(Object Oriented Programing基础之上的编程思想)
指在程序运行期间,将某段代码动态的切入到指定的位置进行运行的编程方式
场景:计算器运行计算方法时进行日志记录
- 直接写在方法内部,不推荐,修改维护麻烦
日志记录:辅助功能
业务逻辑:核心功能
耦合 - 我们希望的是:
业务逻辑(核心功能);日志功能在运行期间,自己动态加上
运行的时候日志加上
可以使用 动态代理
动态代理的问题- 当没有实现任何接口时无法使用动态代理,代理对象和被代理对象唯一能产生的关联就是实现了同一接口
- 实现起来困难
- Spring AOP功能实现,底层使用动态代理
如何将LoggerUtils这个类(切面类)中的通知方法动态的在目标方法运行的各个位置切入
将动态代理的装换为Spring AOP
导包
1
2
3
4
5
6
7
8
9
10
11
12
13
14核心包
commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
切面编程
spring-aspects-4.0.0.RELEASE.jar
增强版切面编程(即使目标对象没有实现任何接口也能创建代理)
下载地址https://github.com/lyhcc/resourses/tree/master/Spring/extra
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar写配置
- 将目标类和切面类(封装了通知方法(在目标方法这行前后直线的方法))加入IOC容器中
- 告诉Spring哪个是切面类@Aspect
- 告诉Spring,里面的通知方法什么时候运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18try{
@Before
meethod.invoke
@AfterReturning
}catch(e){
@AfterThrowing
}finally{
@After
}
@Before 目标方法运行之前 前置通知
@After 目标方法运行之后 后置通知
@AfterReturning 在目标方法正常返回之后 返回通知
@AfterThrowing 目标方法跑出异常时 异常通知
@Around 环绕 环绕通知
指定在哪个方法运行
execution(访问权限 返回值 方法签名)
@Before("execution(方法签名)")- 开启基于注解的AOP
1
2<context:component-scan base-package="xzy.lyhcc"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 开启基于注解的AOP
AOP细节
细节一 IOC容器保存的是代理对象
AOP的底层就是动态代理,容器保存的组件就是代理对象$Proxy16,并不是本类的类型
1 | Calculator calculator = ioc.getBean(Calculator.class); |
在Spring中如果结果加上组件注解,不会创建对象
只是告诉ioc容器中可能有这种类型的组件
细节二 cglib为没有接口的类创建代理对象
没有接口,cglib帮我们创建代理对象,
1 | class xyz.lyhcc.calculate.MathCalculator$$EnhancerByCGLIB$$59ae67da |
细节三 切入点表达式(通配符)
固定格式 execution(访问权限符 返回值类型 方法全类名(参数表))
通配符
*
1) 匹配一个或者多个字符 execution(public int xyz.lyhcc.ab.MyClass.(int, int))
2) 匹配任意一个参数 execution(public int xyz.lyhcc.ab.MyClass.(int, ))
3) 只能匹配一层路径
4) 权限位置不能表示, 表示任意时不写即可
..
1) 匹配任意多个参数,任意类型参数execution(public int xyz.lyhcc.ab.MyClass.(..))
2) 匹配任意多层路径:
execution(public int xyz.lyhcc..MyClass.(int, int))
&&、||、!
1) && 切入的位置满足这两边的表达式
2) || 切入点满足两边的一个
3) ! 只要不是当前的就行
1 | 记住两种: |
细节三 通知方法的执顺序
- 正常执行:@Before –>@After –> @AfterReturning
- 异常执行:@Before –>@After –> @AfterThrowing
细节四 jointPoint获取通知方法的信息(参数方法名获取等)
1) 只需为通知方法参数列表上写一个参数
1
2
3
4
5org.aspectj.lang.JoinPoint
JointPoint 封装了目标方法的详细信息
joinPoint.getSignature().getName()
joinPoint.getArgs()
细节五 通知方法返回值获取、异常获取
在通知方法的参数列表中添加一个参数, 需要告诉Spring这个参数是用来接收返回值的
1 | @AfterReturning(value="execution(public int xyz.lyhcc.calculate.MathCalculator.*(int, int))",returning="result") |
细节六 Spring对通知方法的要求
Spring 对通知方法要求不严格 就算是有返回值,私有的方法也能正常执行
唯一的要求是参数不能乱写
通知方法是Spring利用反射调用的,每次方法调用得确定这个方法的参数表的值,参数表的参数,Spring都得知道是什么
JointPoint认识
不知道的参数要告诉Spring
Exception e :指定通知方法可以接收哪些异常
细节七 抽取切入点表达式
- 在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现。
- 在AspectJ切面中,可以通过 @Pointcut注解 将一个切入点声明成简单的方法。切入点的 方法体通常是空 的,因为将切入点定义与应用程序逻辑混在一起是不合理的。
- 切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。
- 其他通知可以通过 方法名称引入 该切入点
1 | @Pointcut(value="execution(public int xyz.lyhcc.calculate.MathCalculator.*(int, int))") |
细节八 环绕通知
- 环绕通知 (本身就是一个动态代理,四合一) 是所有通知类型中 功能最为强大 的,能够全面地控制连接点,甚至可以控制是否执行连接点。
- 对于环绕通知来说,连接点的 参数类型必须是ProceedingJoinPoint 。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。
- 在环绕通知中需要明确调用ProceedingJoinPoint的 proceed()方法 (就相当于method.invoke())来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
- 注意:环绕通知的方法 需要返回目标方法执行之后的结果 ,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17@Around(value="myPointcut()")
public static Object aroundMethod(ProceedingJoinPoint point) throws Throwable {
System.out.println("环绕通知正在执行,当前运行的方法是"+point.getSignature().getName()+",参数是"+Arrays.asList(point.getArgs()));
try {
proceed = point.proceed();
System.out.println("环绕通知:运行结果是"+proceed);
} catch (Exception e) {
System.out.println("环绕通知:异常"+e);
throw new RuntimeException(e);
}finally {
System.out.println("环绕通知:方法运行完成");
}
return proceed;
}
}细节九 环绕通知和普通通知同时存在,异常需要抛出普通通知才能接收到
环绕通知优先于普通通知执行
执行顺序: (环绕前置–>普通前置)–>目标方法执行–>环绕返回或异常–>环绕后置–>普通后置–>普通返回或异常(出现异常想要普通通知知道需要 抛异常 )
细节十 多个切面的运行顺序
- 在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。
- 切面的优先级可以通过实现Ordered接口或利用@Order注解指定。
- 实现Ordered接口,getOrder()方法的返回值越小,优先级越高。
- 若使用@Order注解,序号出现在注解中
先进去的后出来
环绕在哪个切面就在哪个切面的普通切面之前运行
AOP的应用场景
1) AOP加日志到数据库
2) AOP做权限验证
3) AOP做安全检查
4) AOP做事务控制


