AOP综述
AOP是什么
Aspect-oriented programming 面向切面(方面)编程
AOP有什么用
可以把业务逻辑和系统级的服务进行隔离
系统级的服务像系统的日志,事务,权限验证等
AOP怎么用
动态代理
AOP优点
1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦
2、低侵入式设计,代码的污染极低
3、利用它很容易实现如权限拦截,运行期监控和系统日志等功能
探索AOP
如果没有接触过AOP(面向切面编程)的,可能一时间没办法理解,因为之前都是用JAVA的面向对象编程(OOP),没关系,一步步跟我来学习AOP。
当然如果您是技术大牛,既然您抽出时间查看了虚竹的文章,欢迎纠正文章中的不足和缺陷。
我们先来看下来这段《精通Spring4.x 企业应用开发实战》提供的代码片段
图上的业务代码被事务管理代码和性能监控代码所包围,而且学过JAVA的都知道,出现重复代码了。出现重复代码那就需要重构代码,代码的重构步骤一般有这两种:
1、抽成方法
2、抽成类
抽取成类的方式我们称之为:纵向抽取
- 通过继承的方式实现纵向抽取
但是,虚竹发现这种抽取方式在图上的业务中不适用了,因为事务管理代码和性能监控代码是跟对应的业务代码是有逻辑关系的
现在纵向抽取的方式不行了,AOP希望将分散在各个业务逻辑代码中相同的代码通过横向切割的方式抽取到一个独立的模块中!请看下图
图上应该很好理解,把 重复性的非业务代码横向抽取出来,这个简单。但是如何把这些非业务代码融合到业务代码中,达到跟之前的代码同样的效果。这个才是难题,就是AOP要解决的难题。
AOP的动态代理:
- jdk动态代理
- cglib动态代理
Spring在选择用JDK动态代理还是CGLiB动态代理的依据:
(1)当Bean实现接口时,Spring就会用JDK的动态代理
(2)当Bean没有实现接口时,Spring使用CGlib是实现
(3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)
JDK代理和CGLib代理我们该用哪个
在《精通Spring4.x 企业应用开发实战》给出了建议:
如果是单例的我们最好使用CGLib代理,如果是多例的我们最好使用JDK代理
原因:
JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。
如果是单例的代理,推荐使用CGLib
现在大家知道什么是AOP了吧:把非业务重复代码横向抽取出来,通过动态代理织入目标业务对象函数中,实现跟之前一样的代码
AOP术语
aop术语不好理解,《精通Spring4.x 企业应用开发实战》书上有提到这些术语,我尽量解释下这些术语
连接点(JointPoint)
一个类中的所有方法都可以被称为连接点
切入点(PointCut)
上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点。
用注解来说明,被@Log定义的方法就是切入点
增强/通知(Advice)
表示添加到切点的一段逻辑代码,并定位连接点的方位信息。
前置通知(MethodBeforeAdvice )
前置通知是在目标方法执行前执行
后置通知 (AfterReturningAdvice )
后置通知是在目标方法执行后才执行 ,可以得到目标方法返回的值 ,但不能改变返回值
环绕通知(MethodInterceptor)
环绕通知有在目标方法执行前的代码,也有在目标方法执行后的代码,可以得到目标方法的值,可以改变这个返回值!
扩展知识点:
spring中的拦截器分两种:
1、HandlerInterceptor
2、MethodInterceptor
HandlerInterceptor是springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行
MethodInterceptor是AOP项目中的拦截器,它拦截的目标是方法,即使不是controller中的方法。
异常通知(ThrowsAdvice)
最适合的场景就是事务管理
引介通知(IntroductionInterceptor)
引介通知比较特殊,上面的四种通知都是在目标方法上织入通知,引介通知是在目标类添加新方法或属性
- 简单来说就定义了是干什么的,具体是在哪干
- Spring AOP提供了5种Advice类型给我们:前置通知、后置通知、返回通知、异常通知、环绕通知给我们使用!
切面(Aspect)
切面由切入点和增强/通知组成
交叉在各个业务逻辑中的系统服务,类似于安全验证,事务处理,日志记录都可以理解为切面
织入(weaving)
就是将切面代码插入到目标对象某个方法的过程,相当于我们在jdk动态代理里面的 invocationHandler接口方法的内容
用注解解释:就是在目标对象某个方法上,打上切面注解
目标对象(target)
切入点和连接点所属的类
顾问(Advisor)
就是通知的一个封装和延伸,可以将通知以更为复杂的方式织入到某些方法中,是将通知包装为更复杂切面的装配器。
Spring对AOP的支持
Spring提供了3种类型的AOP支持:
- 基于代理的经典SpringAOP
- 需要实现接口,手动创建代理
- 纯POJO切面
- 使用XML配置,aop命名空间
-
@AspectJ
注解驱动的切面 - 使用注解的方式,这是最简洁和最方便的!
知识点
切面类型主要分成了三种
- 一般切面
- 切点切面
- 引介/引入切面
一般切面,切点切面,引介/引入切面说明:
一般切面一般不会直接使用,切点切面都是直接用就可以了。
这里重点说明下引介/引入切面:
- 引介/引入切面是引介/引入增强的封装器,通过引介/引入切面,可以更容易地为现有对象添加任何接口的实现!
继承关系图:
引介/引入切面有两个实现类:
- DefaultIntroductionAdvisor:常用的实现类
- DeclareParentsAdvisor:用于实现AspectJ语言的DeclareParent注解表示的引介/引入切面
实际上,我们使用AOP往往是Spring内部使用BeanPostProcessor帮我们创建代理。
这些代理的创建器可以分成三类:
- 基于Bean配置名规则的自动代理创建器:BeanNameAutoProxyCreator
- 基于Advisor匹配机制的自动代理创建器:它会对容器所有的Advisor进行扫描,实现类为DefaultAdvisorAutoProxyCreator
- 基于Bean中的AspectJ注解标签的自动代理创建器:AnnotationAwareAspectJAutoProxyCreator
对应的类继承图:
使用引介/引入功能实现为Bean引入新方法
其实前置通知,后置通知,还是环绕通知,这些都很好理解。整个文章就引介/引入切面比较有趣,我们来实现试试吧
有个服务员的接口:
public interface Waiter {
// 向客人打招呼
void greetTo(String clientName);
// 服务
void serveTo(String clientName);
}
对应的服务员接口实现类:
@Service("Waiter")
public class NaiveWaiter implements Waiter {
@Override
public void greetTo(String clientName) {
System.out.println("NaiveWaiter:greet to " + clientName + "...");
}
@Override
public void serveTo(String clientName) {
System.out.println("NaiveWaiter:serve to " + clientName + "...");
}
}
现在我想做的就是:想这个服务员可以充当售货员的角色,可以卖东西!当然了,我肯定不会加一个卖东西的方法到Waiter接口上啦,因为这个是暂时的~
所以,我搞了一个售货员接口:
public interface Seller {
int sell(String goods, String clientName);
}
售货员接口实现类:
public class SmartSeller implements Seller {
@Override
public int sell(String goods, String clientName) {
System.out.println("SmartSeller: sell "+goods +" to "+clientName+"...");
return 100;
}
}
类图是这样子的:
现在我想干的就是:借助AOP的引入/引介切面,来让我们的服务员也可以卖东西!
我们的引入/引介切面具体是这样干的:
@Component
@Aspect
public class EnableSellerAspect {
@DeclareParents(value = "com.yiyu.kata.util.aop.NaiveWaiter", // 指定服务员具体的实现
defaultImpl = SmartSeller.class) // 售货员具体的实现
public Seller seller; // 要实现的目标接口
}
写了这个切面类后,大家猜猜有什么效果?
答案是:切面技术将SmartSeller融合到NaiveWaiter中,这样NaiveWaiter就实现了Seller接口!!!!
很神奇是不是,我们来测试下
public class TestAop {
@Test
public void testAop(){
ApplicationContext ac = new ClassPathXmlApplicationContext("dispatcher-servlet.xml","applicationContext-datasource.xml","applicationContext.xml");
Waiter waiter = ac.getBean("Waiter",Waiter.class);
// 调用服务员原有的方法
waiter.greetTo("虚竹");
waiter.serveTo("虚竹");
// 通过引介/引入切面已经将waiter服务员实现了Seller接口,所以可以强制转换
Seller seller = (Seller) waiter;
seller.sell("东西", "虚竹");
}
}
结果是: 神奇吧,哈哈哈
具体的调用过程是:
总结
- AOP的底层实现是动态代理,动态代理包含JDK动态代理和CGLib代理。当Bean实现接口时,Spring就会用JDK的动态代理;当Bean没有实现接口时,Spring使用CGlib是实现;可以强制使用CGlib。
- 如果是单实例,推荐使用CGLib代理,如果是多例的我们最好使用JDK代理。因为CGLib代理对象运行速度要比JDK的代理对象要快
- AOP的层面是方法级别的,只能对方法进入拦截
- 无论是XML方式还是注解方式,原理都一样,我们用注解AOP就够了,简单好用
- 引介/引入切面是比较有趣的,具体实现上面举例说明了,它可以达到用某个接口的方法,又不入侵代码,低耦合的效果。具体妙处还待开发,后面有发现再补充说明
本文摘自 :https://blog.51cto.com/u