AOP 面向切面编程
AOP(aspect-oriented-programming)确保 POJO 的简单性以及核心业务的内聚性,借助 AOP 可以将安全、事务和日志关注点与核心业务逻辑相分离。
1. 1 实现方式
- 预编译
- AspectJ
- 运行期动态代理(JDK 动态代理、CGLib 动态代理)
- SpringAOP、JbossAOP
1.2 Spring中的作用
- 提供声明式的企业服务,特别是 EJB 的替代服务声明
- 允许自定义方面,互补 OOP(面向对象) 与 AOP(面向切面)的使用
1.3 Spring中的实现
- 接口/接口集默认 JavaSE 动态代理作为 AOP 的代理标准
- 无接口可使用 GCGLIB 的代理实现
二、AOP 概念介绍
2.1 aspect 切面
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
<!-- 切面类 -->
<bean id="AspectBean_id" class=[AspectBeanPath]/>
<!-- 业务类 -->
<bean id="BizBean_id" class=[BizBeanPath]/>
<aop:config>
<aop:aspect id="AspectBeanAOP_id" ref="AspectBean_id"/>
</aop:config>
<beans>
2.2 pointcut 切入点
2.2.1 切入方法
function | impact |
---|---|
execution | 匹配方法执行的连接点 |
within/@within | 限定匹配指定类型的连接点 |
this | 匹配特定连接点的 bean,引用柿子顶类型实例的限制 |
target/@target | 限定匹配当特定连接点的代理对象,对象是指定类型的实例 |
args/@args | 限定匹配特定连接点实际传入参数的类型实例 |
@annotation | 限定匹配指定连接点的主体 |
2.2.2 切入类型
example | pointcut |
---|---|
excution(public * *(..)) | 所有 public 执行方法 |
excution(* set*(..)) | 所有 set 开始的方法 |
excution(* com.example.bean.AClazz.*(..)) | AClazz 类中所有方法 |
excution(* com.example.bean..(..)) | com.example.bean 包下所有方法 |
excution(* com.example.bean…(..)) | com.example.bean 包即包下所有方法 |
2.2.3 切入示例
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
<!-- 切面类 -->
<bean id="AspectBean_id" class=[AspectBeanPath]/>
<!-- 业务类 -->
<bean id="BizBean_id" class=[BizBeanPath]/>
<aop:config>
<aop:aspect id="AspectBeanAOP_id" ref="AspectBean_id">
<aop:pointcut expression="excution(public * *(..))" id=[POINTCUT_ID]/>
</aop:aspect>
</aop:config>
<beans>
2.2.4 切入组合
表达式可通过 &&、|| 或者 ! 进行组合,亦可通过名字引用切入点表达式,如下:
@Pointcut("excution(...)")
private void aFunction(){}
@Pointcut("excution(...)")
private void bFunction(){}
@Pointcut("aFunction() && bFunction()")
private void cFunction(){}
2.2.5 切入规范
- 选择特定类型的连接点,如:execution,get,set,call,handler
- 确定连接点范围,如:within,withincode
- 匹配上下文信息,入:this,target,@annotation
2.3 Advice 通知
2.3.1 使用实例
<aop:aspect id="AspectBeanAOP_id" ref="AspectBean_id">
<aop:pointcut expression="excution(public * *(..))" id=[POINTCUT_ID]/>
<!-- 执行时前置通知 -->
<aop:before method = [AspectBean_DO_FUNCTION] pointcut-ref=[POINTCUT_ID]/>
<!-- 返回时,受已成影响 -->
<aop:after-returning method = [AspectBean_DO_FUNCTION] pointcut-ref=[POINTCUT_ID]/>
<!-- 异常时 -->
<aop:after-throwing method = [AspectBean_DO_FUNCTION] pointcut-ref=[POINTCUT_ID]/>
<!-- 执行完后置通知,不受异常影响 -->
<aop:after method = [AspectBean_DO_FUNCTION] pointcut-ref=[POINTCUT_ID]/>
<!-- 同上不受异常干扰,多用于资源的释放 -->
<aop:finally method = [AspectBean_DO_FUNCTION] pointcut-ref=[POINTCUT_ID]/>
<!-- 环绕执行,即执行前于执行后。位于通知方法首个参数必需为 ProceedingJoinPoint 类型 -->
<aop:aspect method = [AspectBean_DO_FUNCTION] pointcut-ref=[POINTCUT_ID]/>
</aop:aspect>
或通过代码实现:
@Component
@Aspect
public class MoocAspect {
@Before("execution(...)")
public void befor() {}
@After("execution(...)")
public void after() {
// after、finally 的实现
}
@AfterReturning(pointcut="execution(...)", returning="returnValue")
public void afterReturning(Object returnValue) {
// retrunValue 具体返回值,类型可依据实际情况确认
}
@AfterThrowing(pointcut="execution(...)", throwing="e")
public void afterThrowing(Exception e) {
// e 具体异常类型信息,异常可依据实际情况确认
}
@Around("execution(...)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 此处执行于方法前
Object obj = pjp.proceed();// 真正执行的行为,obj 可能为 null
// 此处执行于方法后
return obj;
}
}
2.3.2 使用扩展
- 参数
@Component
@Aspect
public class MoocAspect {
@Before("execution(...) && args(arg)")
public void beforeWithParam(String arg) {
// arg 获取传入参数值
}
@Before("execution(...) && @annotation(moocMethod)")
public void beforeWithAnnotaion(MoocMethod moocMethod) {
// moocMethod 获取传入注解内容
}
@Before("execution(...) && args(arg) && @annotation(moocMethod)",
argName="arg, moocMethod")
public void beforeWithArgNames(String arg, MoocMethod moocMethod) {
// 指定注解方法的参数名
// JoinPoint,ProceedingJoinPoint,JoinPoint.StraticPart 可忽略
}
}
- 泛型
public interface Sample<T>{
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Coolection<T> param);
}
@Component
@Aspect
public class MoocAspect {
@Before("execution(..Sample.sampleGenericMethod(*)) && args(arg)")
public void beforeWithParam(String arg) {
// arg 指定泛型类型为 String
}
@Before("execution(..Sample.sampleGenericCollectionMethod(*)) && args(arg)")
public void beforeWithParam(Collection<String> arg) {
// arg 指定泛型集合类型为 String
}
}
2.4 Introduction 引入
允许一个切面声明一个实现指定接口的通知对象,并提供了一个接口实现类来代表这些对象。实现方式:
- 在 < aop:aspect > 声明 < aop:declare-parents > 匹配新的 parent
<aop:config>
<aop:aspect id="" ref="">
<aop:declare-parents
<!-- 指定接口对象 -->
implement-interface=""
<!-- 接口实现,代表以上对象 -->
defalult-impl=""/>
</aop:aspect>
</aop:config>
- 代码中使用 @DeclareParents 进行注解,且用来匹配拥有新的 parent
public interface Sample{
void sampleMethod();
}
@Aspect
public class IntroductionAspect {
@DeclareParents(value="execution(...)", defaultImpl=Sample.class)
public static Sample sample;
@Before("execution(...) && this(sample2)")
public void recordIntroduction(Sample sample2) {
sample2.sampleMethod();
}
}
2.5 Advisors
- 仅有一个 advice , 切面自身通过一个 bean 表示并实现相应的 advice 接口;
2.6 AOP Proxy 代理
2.6.1 Target Object 目标对象
- 默认 CGLIB 代理
- 实际通过 ProxyFatoryBean 对象的 getObject() 函数获取到代理对象
- 配置文件当中通过 < proerty name=”target”> 声明代理对象
- 继承接口 MethodInterceptor 可实现 *通配符设定
2.6.2 Proxy 的定义
<bean id="" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target"><bean ...></property>
<property name="interceptorNames"><list><value>...</list></property>
</bean>
可通过以下方式,简化以上定义:
<bean id="baseProxyBean" class="org.springframework.aop.framework.ProxyFactoryBean"
lazy-init="true" abstract="true"></bean>
<bean id="" parent="baseProxyBean">
<property name="target"><bean ...></property>
<property name="interceptorNames"><list><value>...</list></property>
</bean>
通过以下方式,实现自动代理 auto-proxy :
<bean id="" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value=[通配符定义]/>
<property name="interceptorNames"><list><value>...</list></property>
</bean>
同样的,可以通过以下方式简化以上自动代理(暂为理解,后继完善):
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
AOP API 的使用
api | method | impact |
---|---|---|
BeforeAdvice | void before(Method m, Object[] args, Object target) | 连接点插入前执行 |
ThrowsAdvice | void afterThrowing([Method, args, target], ThrowableSubclass) | 连接点返回后执行 |
AfterReturningAdvice | void afterReturning(Object returnValue, Method m, Object[] args, Object target) | 后置通知必需实现 |
Interceptor | Object invoke(MethodInvocation invocaton) | 针对不同的 advice 启用相同的切入点 |
IntroductionInterceptor | boolean implementsInterface(Class intf) | |
IntroductionAdvisor | void validateInterfaces() | |
IntroductionInfo | Class[] getInterfaces() | |
Lockable | void lock() void unlock() boolean locaked() Object invoke(MethodInvocation invocation) | 锁住 解锁 锁状态 根据锁状态的操作 |
DefaultIntroductionAdvisor | LockMixinAdvisor() | 持独立 LockMixin 实例的构造函数 |
注解 @AspectJ
注解类型 | 描述 |
---|---|
@Commponent | 声明可以被 bean 容器识别 |
@Aspect 切面 | 1.注解 bean 将被 Spring 自动识别并应用 2.可包括切入点 pointcut,通知 Advice 和引入 introduction 的声明 3.需配合 @Component 注释或 xml 配置 |
@Pointcut 切入点 | 方法返回类型必需是 void |
切面实例化模型
在对象执行并创建切面实例后跟随对象的失效而失效(fk:有句麻卖批不知当讲不当讲,不管先记录下再说),以下为具体实现
@Aspect("perthis(execution(...))")
public class MyAspect {}