1、什么是面向切面编程
AOP(Aspect-Oriented Programming), 即面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角,也可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系,而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。使用“横切”技术。
AOP把软件系统分为两个部分:核心关注点(核心业务逻辑)和横切关注点(横向的通用逻辑)。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。
2、Spring对AOP的支持
Spring提供了四种类型的AOP支持
- 经典的基于代理的AOP
- @AspectJ注解驱动的切面
- 纯POJO切面
- 注入式AspectJ切面
前三种都是Spring AOP现实的变体、Spring AOP构建在动态代理的基础之上、因此,Spring对AOP的支持局限于方法拦截
3、AOP的相关概念
1.通知(Advice) 通知定义了在切入点代码执行时间点附近需要做的工作。
Spring支持五种类型的通知:
- @Before(前置通知) org.apringframework.aop.MethodBeforeAdvice
- @AfterReturning(后置通知) org.springframework.aop.AfterReturningAdvice
- @AfterThrowing(异常抛出) org.springframework.aop.ThrowsAdvice
- @Arround(环绕通知) org.aopaliance.intercept.MethodInterceptor
- @Introduction(引入) org.springframework.aop.IntroductionInterceptor
2.连接点(Joinpoint) 程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法调用时、异常抛出时、方法返回后等等。
3.切入点(Pointcut) 通知定义了切面要发生的“故事”,连接点定义了“故事”发生的时机,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定。
4.切面(Aspect) 通知、连接点、切入点共同组成了切面:时间、地点和要发生的“故事”。
5.引入(Introduction) 引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)。
6.目标(Target) 即被通知的对象,如果没有AOP,那么通知的逻辑就要写在目标对象中,有了AOP之后它可以只关注自己要做的事,解耦合!
7.代理(proxy) 应用通知的对象,详细内容参见设计模式里面的动态代理模式。
8.织入(Weaving) 把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
- 编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器;
- 类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;
- 运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术。
4、相关知识点
AOP 有哪些实现方式?
实现 AOP 的技术,主要分为两大类: 静态代理 - 指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强; 编译时编织(特殊编译器实现) 类加载时编织(特殊的类加载器实现)。 动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。 JDK 动态代理 CGLIB
Spring AOP and AspectJ AOP 有什么区别?
Spring AOP 基于动态代理方式实现;AspectJ 基于静态代理方式实现。 Spring AOP 仅支持方法级别的 PointCut;提供了完全的 AOP 支持,它还支持属性级别的 PointCut。
如何理解 Spring 中的代理?
将 Advice 应用于目标对象后创建的对象称为代理。在客户端对象的情况下,目标对象和代理对象是相同的。
指出在 spring aop 中 concern 和 cross-cutting concern 的不同之处。
concern 是我们想要在应用程序的特定模块中定义的行为。它可以定义为我们想要实现的功能。cross-cutting concern 是一个适用于整个应用的行为,这会影响整个应用程序。例如,日志记录,安全性和数据传输是应用程序几乎每个模块都需要关注的问题,因此它们是跨领域的问题。
5、使用注释创建切面
@Aspect
@Component
public class Spring4AopConfig {
/**
* execution 用于匹配方法执行的连接点;
* within 用于匹配指定类型内的方法执行;
*/
// @Pointcut("within(com.spr.demo.service..*)")
// public void commonServiceLogger(){}
@Pointcut(value = "execution(** com.spr.demo.controller.AopCpntroller.*(..))")
public void commonExecution(){}
@Before("commonExecution()")
public void startMethod(JoinPoint point) {
System.out.println(point.getSignature().getName() + "前置通知...");
}
@AfterReturning(pointcut = "commonExecution()",returning="result")
public void afterMethod(JoinPoint point,Object result) {
System.out.println(point.getSignature().getName() + "后置通知:" + result);
}
@AfterThrowing(pointcut = "commonExecution()",throwing = "e")
public void throwMethod(JoinPoint point, Throwable e) {
System.out.println(point.getSignature().getName() + "异常通知:" + e);
}
/**
* joinpoint和proceedingjoinpoint区别
* joinpoint:
public interface JoinPoint {
String toString(); //连接点所在位置的相关信息
String toShortString(); //连接点所在位置的简短相关信息
String toLongString(); //连接点所在位置的全部相关信息
Object getThis(); //返回AOP代理对象
Object getTarget(); //返回目标对象
Object[] getArgs(); //返回被通知方法参数列表
Signature getSignature(); //返回当前连接点签名
SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置
String getKind(); //连接点类型
StaticPart getStaticPart(); //返回连接点静态部分
}
* proceedingjoinpoint:
* public interface ProceedingJoinPoint extends JoinPoint {
public Object proceed() throws Throwable;
public Object proceed(Object[] args) throws Throwable;
}
JoinPoint.StaticPart:提供访问连接点的静态部分,如被通知方法签名、连接点类型等:
public interface StaticPart {
Signature getSignature(); //返回当前连接点签名
String getKind(); //连接点类型
int getId(); //唯一标识
String toString(); //连接点所在位置的相关信息
String toShortString(); //连接点所在位置的简短相关信息
String toLongString(); //连接点所在位置的全部相关信息
}
环绕通知 ProceedingJoinPoint 执行proceed方法的作用是让目标方法执行,
这也是环绕通知和前置、后置通知方法的一个最大区别。
Proceedingjoinpoint 继承了 JoinPoint 。是在JoinPoint的基础上暴露出 proceed 这个方法。
proceed很重要,这个是aop代理链执行的方法。
*/
@Around("commonExecution()")
// public void aroundMethod(ProceedingJoinPoint jp,Throwable e){
public void aroundMethod(ProceedingJoinPoint point){
try {
System.out.println("< 环绕通知开始执行 >...");
Object proceed = point.proceed();
System.out.println("< 环绕通知执行完成 > : " +
point.getSignature().getName() + ","+
point.getSignature().getDeclaringTypeName()+","+
proceed);
} catch (Throwable e) {
System.err.println("< 环绕通知异常 > : " + e.getMessage());
}
}
}
如果不想要使用@Component注解加载切面,也可以通过组件加载:
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConfigCenter {
@Bean
public Spring4AopConfig spring4AopConfig() {
return new Spring4AopConfig();
}
}
6、通过注解引入新功能
使用@DeclareParents注解,将接口引入到Spring Bean中。
@DeclareParents注解由三部分组成:
1)value属性指定了哪种类型的bean要引入该接口。加号(+)表示是该类型的所有子类型,而不是该类型本身。
2)defaultImpl属性指定了为引入功能提供实现的类。
3)@DeclareParents注解所标注的静态属性指明了要引入的接口。
EncoreableIntroducer是一个切面,同时声明它是一个bean。但是,它与我们 之前所创建的切面不同,它并没有提供前置、后置或环绕通知,而是 通过@DeclareParents注解,将Encoreable接口引入 到Performance bean中:
// 创建一个接口 public interface Encoreable {void performEncore();} // 实现接口 public class DefaultEncoreable implements Encoreable { @Override public void performEncore() { System.out.println("接口实现"); } } //新建切面 @Aspect @Component public class EncoreableIntroducer { @DeclareParents(value = "concert2.Performance+", defaultImpl = DefaultEncoreable.class) public static Encoreable encoreable; }
7、注入AspectJ切面
虽然Spring AOP能够满足许多应用的切面需求,但是与AspectJ相比,Spring AOP 是一个功能比较弱的AOP解决方案。AspectJ提供了Spring AOP所不能支持的许多类型的切点。
//定义切面 public aspect CriticAspect { public CriticAspect(){} //定义切点 pointcut performance():execution(* perform(..)); afterReturning : performance(){ System.out.println(criticismEngine.getCriticism()); } private CriticismEngine criticismEngine; // 注入criticismEngine public void setCriticismEngine(CriticismEngine criticismEngine){ this.criticismEngine = criticismEngine; } } public class CriticismEngineImpl extends CriticismEngine { public CriticismEngineImpl(){} private String[] criticismPool; @Override public String getCriticism(){ int i = (int)(Math.random() * criticismPool.length); return criticismPool[i]; } public void setCriticismPool(String[] criticismPool){ this.criticismPool = criticismPool; } }