AOP概念详解笔记

Wesley13
• 阅读 538

切面(Aspect)

一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是一个很好的横切关注点例子。切面用Spring的Advisor或拦截器实现, 然后可以通过@Aspect标注或在applictionContext.xml中进行配置: 

<aop:aspect id="fourAdviceAspect" ref="fourAdviceBean" order="2"> 

连接点(Joinpoint)

程序执行过程中的行为,如方法的调用或特定的异常被抛出,在代码上有JoinPoint类和ProceedingJoinPoint类,如下所示,可以通过JoinPoint获取很多参数,JoinPoint一般用在Advice实现方法中作为参数传入,ProceedingJoinPoint用于实现围绕Advice的参数传入。  通过下面JoinPoint的接口可以看出通过JoinPoint可以得到代理对象和Target对象。

package org.aspectj.lang;  
import org.aspectj.lang.reflect.SourceLocation;  
public interface JoinPoint {  
    String toString();         //连接点所在位置的相关信息  
    String toShortString();     //连接点所在位置的简短相关信息  
    String toLongString();     //连接点所在位置的全部相关信息  
    Object getThis();         //返回AOP代理对象  
    Object getTarget();       //返回目标对象  
    Object[] getArgs();       //返回被通知方法参数列表  
    Signature getSignature();  //返回当前连接点签名  
    SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置  
    String getKind();        //连接点类型  
    StaticPart getStaticPart(); //返回连接点静态部分  
}  
  
public interface ProceedingJoinPoint extends JoinPoint {  
    public Object proceed() throws Throwable;  
    public Object proceed(Object[] args) throws Throwable;  
}  

切入点(Pointcut)

指定一个Adivce将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点,例如,使用正则表达式。

xml中配置:

<aop:pointcut id="myPointcut" expression="execution(* com.wicresoft.app.service.impl.*.*(..))" method="release" /> 

或使用

Annoation :@pointcut("execution * transfer(..)")

并用一个返回值为void,方法体为空的方法来命名切入点如:  

private void anyOldTransfer(){}

之后就可以在Advice中引用,如: 

@AfterReturning(pointcut="anyOldTransfer()", returning="reVal")

package org.springframework.aop;  
public interface Pointcut {  
    ClassFilter getClassFilter();  
    MethodMatcher getMethodMatcher();  
    Pointcut TRUE = TruePointcut.INSTANCE;  
}  
package org.springframework.aop;  
public interface ClassFilter {  
     boolean matches(Class<?> clazz);//如果clazz与我们关注的现象相符时返回true,负责返回false  
     ClassFilter TRUE = TrueClassFilter.INSTANCE;//静态参数 如果类型对于要扑捉的Pointcut来说无所谓,可将此参数传递给Pointcut  
}  
package org.springframework.aop;  
public interface MethodMatcher {  
   boolean matches(Method method, Class<?> targetClass);  
  
 /** 
  * 是否对参数值敏感 
  * 如果为false表明匹配时不需要判断参数值(参数值不敏感),称之为StaticMethodMatcher,这时只有 
  * matches(Method method, Class<?> targetClass); 被执行,执行结果可以缓存已提高效率。 
  * 如果为true表明匹配时需要判断参数值(参数值敏感),称之为DynamicMethodMatcher,这时先执行 
  * matches(Method method, Class<?> targetClass);如果返回true,然后再执行 
  * boolean matches(Method method, Class<?> targetClass, Object[] args);已做进一步判断 
  *  
  */  
 boolean isRuntime();  
 boolean matches(Method method, Class<?> targetClass, Object[] args);  
 MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;  
}  

关于PointCut中使用的execution的说明:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) 

modifiers-pattern:方法的操作权限

ret-type-pattern:返回值

declaring-type-pattern:方法所在的包

name-pattern:方法名

parm-pattern:参数名

throws-pattern:异常

记忆法则就是Java定义一个方法时的样子:public boolean produceValue(int oo) throws Exception, 只要在方法名前加上包名就可以了。

其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。

常见的PointCut结构图

AOP概念详解笔记

通知(Advice)

在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Advice中必须用到PointCut

     在xml中配置,配置中的method为Aspect实现类中的方法名,使用pointcut自定义或pointcut-ref进行引用已有pointcut

<aop:before pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="authority" /> 
<aop:after pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="release" /> 
<aop:after-returning pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="log" />
<aop:around pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))" method="processTx" /> 
<aop:after-throwing pointcut-ref="myPointcut" method="doRecovertyActions" throwing="ex" />     

或使用Annoation:   

@Before("execution(* com.wicresoft.app.service.impl.*.*(..))")
@AfterReturning(returning="rvt", pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))")
@AfterThrowing(throwing="ex", pointcut="execution(* com.wicresoft.app.service.impl.*.*(..))")
@After("execution(* com.wicresoft.app.service.impl.*.*(..))")
@Around("execution(* com.wicresoft.app.service.impl.*.*(..))")  

注意

  • AfterReturning 增强处理处理只有在目标方法成功完成后才会被织入。
  • After 增强处理不管目标方法如何结束(保存成功完成和遇到异常中止两种情况),它都会被织入。

使用方法拦截器的around通知,需实现接口MethodInterceptor:

public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation invocation) throws Throwable;
}

invoke()方法的MethodInvocation 参数暴露将被调用的方法、目标连接点、AOP代理和传递给被调用方法的参数。 invoke()方法应该返回调用的结果:连接点的返回值。

一个简单的MethodInterceptor实现看起来如下:

public class DebugInterceptor implements MethodInterceptor {  
    public Object invoke(MethodInvocation invocation) throws Throwable {  
        System.out.println("Before: invocation=[" + invocation + "]");  
        Object rval = invocation.proceed();  
        System.out.println("Invocation returned");  
        return rval;  
    }  
}  

注意MethodInvocation的proceed()方法的调用。这个调用会应用到目标连接点的拦截器链中的每一个拦截器。大部分拦截器会调用这个方法,并返回它的返回值。但是, 一个MethodInterceptor,和任何around通知一样,可以返回不同的值或者抛出一个异常,而不调用proceed方法。但是,没有好的原因你要这么做。

Before通知:需实现MethodBeforeAdvice接口

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method m, Object[] args, Object target) throws Throwable;
}

Throw通知,需实现ThrowsAdvice接口

After Returning通知须直线AfterReturningAdvice接口

public interface AfterReturningAdvice extends Advice {
   void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable;
}

引入(Introduction)

添加方法或字段到被通知的类,引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现IsModified接口,来简化缓存。使用introduction要有三个步骤:

(1)声明新接口

(2)创建自己的IntrouductionInterceptor通过Implements IntroductionInterceptor或extends DelegatingIntroductionInterceptor 并同时implements(1)中声明的接口

(3)将新接口和自定义的IntroductionInterceptor配置到DefaultIntroductionAdvisor中,然后将前三者配置到ProxyFactoryBean中。

public interface IOtherBean {  
    public void doOther();  
}  
  
public class SomeBeanIntroductionInterceptor implements IOtherBean, IntroductionInterceptor {  
  
    public void doOther() {  
        System.out.println("doOther!");  
    }  
  
    public Object invoke(MethodInvocation invocation) throws Throwable {  
          
        //判断调用的方法是否为指定类中的方法  
        if ( implementsInterface(invocation.getMethod().getDeclaringClass()) ) {  
            return invocation.getMethod().invoke(this, invocation.getArguments());  
        }  
          
        return invocation.proceed();  
    }  
      
    /** 
     * 判断clazz是否为给定接口IOtherBean的实现 
     */  
    public boolean implementsInterface(Class clazz) {  
          
        return clazz.isAssignableFrom(IOtherBean.class);  
    }  
}  

<!-- 目标对象 -->  
<bean id="someBeanTarget" class="aop.spring.introduction.SomeBeanImpl" />  
<!-- 通知 -->  
<bean id="someBeanAdvice" class="aop.spring.introduction.SomeBeanIntroductionInterceptor" />  
<!-- 通知者,只能以构造器方法注入-->  
<bean id="introductionAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor">  
    <constructor-arg ref="someBeanAdvice" />  
    <constructor-arg value="aop.spring.introduction.IOtherBean" />      
</bean>  
      
<!-- 代理 (将我们的切面织入到目标对象)-->  
<bean id="someBeanProxy" class="org.springframework.aop.framework.ProxyFactoryBean">  
    <!-- 若目标对象实现了代理接口,则可以提供代理接口的配置 -->  
    <property name="proxyInterfaces"  value="aop.spring.introduction.ISomeBean" />  
    <!-- 配置目标对象 -->  
    <property name="target" ref="someBeanTarget" />  
    <!-- 配置切面 -->  
    <property name="interceptorNames">  
        <list>  
            <value>introductionAdvisor</value>  
        </list>  
    </property>  
</bean>  

拦截器(Advisor )

常用的有PointCutAdvisor和IntroudtionAdvisor。前者Advisor有PointCut和Advice组成,满足Poincut(指定了哪些方法需要增强),则执行相应的Advice(定义了增强的功能),后者由Introduction构成。PointCutAdvisor主要是根据PointCut中制定的Target Objects的方法在调用(前,后,around,throws, after-return等)时引入新的Aspect中的methods, 而IntroductionAdvisor主要是引入新的接口到Targets对象中。

public interface PointcutAdvisor {  
    Pointcut getPointcut();  
    Advice getAdvice();  
}  

1、PointcutAdvisor:

 Advice和Pointcut,默认实现为DefaultPointcutAdvisor, 还有NameMatchMethodPointcutAdvisor,RegexpMethodPointcutAdvisor等。 

 其中NameMacthMethodPointCutAdvisor、RegexpMethodPointCutAdvisor 可以对比常用的PointCut类有NameMatchedMethodPointCut和JdkRegexMethodPointCut。
 前者需要注入mappedName和advice属性,后者需要注入pattern和advice属性。其中mappedName和pattern是直接配置的值,而advice需要自己实现具体的advice,可见实现advisor的时候,不需要实现PointCut,一般PointCut只需要配置就好了,不需要具体实现类
 mappedName指明了要拦截的方法名,pattern按照正则表达式的方法指明了要拦截的方法名,advice定义了一个增强(需要自己实 现MethodBeforeAdvice、  MethodAfterAdvice、ThrowsAdvice、MethodInterceptor接口之 一)。然后在ProxyFactoryBean的拦截器(interceptorNames)中注入这个PointCutAdvisor即可,如上面这个ProxyFactoryBean是一个静态代理,只能代理一个类给加上AOP,那么这个静态代理需要注入有目标对象,目标对象的接口,和interceptorsNames

2、 IntroductionAdvisor :

默认实现为DefaultIntroductionAdvisor,这个主要与Introduction有关,可以参考上面的例子

— 目标对象(Target Object):包含连接点的对象,也被称作被通知或被代理对象。

— AOP代理(AOP Proxy):AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。如ProxyFactory,ProxyFactoryBean, 下面会进行详细说明    

— 编织(Weaving):组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。将Aspect加入到程序代码的过程,对于Spring AOP,由ProxyFactory或者ProxyFactoryBean负责织入动作。 

通过ProxyFactory可以将对符合条件的类调用时添加上Aspect。

或者 可使用XML声明式 ProxyFactoryBean:需要设定 target,interceptorNames(可以是Advice或者Advisor,注意顺序, 对接口代理需设置proxyInterfaces 

注意:一个ProxyFactoryBean只能指定一个代理目标,不是很方便,这就产生了自动代理。通过自动代理,可以实现自动为多个目标Bean实现AOP代理、避免客户端直接访问目标Bean(即getBean返回的都是Bean的代理对象)。spring的自动代理是通过BeanPostProcessor实现的,容器载入xml配置后会修改bean为代理Bean,而id不变。 

ApplicationContext可以直接检测到定义在容器中的BeanPostProcessor,BeanFactory需要手动添加。 
有2种常用的BeanPostProcessor: 

1.BeanNameAutoProxyCreator :

故名思议,BeanName需要注入的两个属性有BeanNames和interceptorNames

<bean id="loginBeforeAdvisor" .../>    
<bean id="loginThrowsAdvisor" .../>    
<bean  class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">    
        <!-- 注入目标Bean -->    
        <property name="beanNames" value="*Service">    
        </property>    
        <property name="interceptorNames">    
            <list>    
                <value>loginBeforeAdvisor</value>    
                <value>loginThrowsAdvisor</value>    
            </list>    
        </property>    
</bean> 

2.DefaultAdvisorAutoProxyCreator:

DefaultAdvisorAutoProxyCreator和BeanNameAutoProxyCreator不同的是,前者只和Advisor 匹配, 该类实现了BeanPostProcessor接口。当应用上下文读入所有的Bean的配置信息后,该类将扫描上下文,寻找所有的Advisor,他将这些Advisor应用到所有符合切入点的Bean中。所以下面的xml中没有绑定也无需绑定DefaultAdvisorAutoProxyCreator与Advisor的关系。

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">  
<beans>  
     <bean id="kwikEMartTarget" class="demo.ApuKwikEMart"></bean>  
     <bean id="performanceThresholdInterceptor" class="demo.advice.PerformanceThresholdInterceptor">  
          <constructor-arg>  
               <value>5000</value>  
          </constructor-arg>  
     </bean>  
     <!-- 使用RegexpMethodPointcutAdvisor来匹配切入点完成个一个Advisor; -->  
     <bean id="regexpFilterPointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">  
          <property name="pattern">  
               <!-- 匹配的名字为方法名-->  
               <value>.*buy.*</value>  
          </property>  
          <property name="advice">  
               <ref bean="performanceThresholdInterceptor"/>  
          </property>  
     </bean>  
     <bean id="defaultAdvisorAutoProxyCreator"  
     class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />  
</beans>

在使用Aonnotation的时候,需要进行在ApplicationContext.xml中进行配置:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:aop="http://www.springframework.org/schema/aop"        
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
           http://www.springframework.org/schema/aop  
       http://www.springframework.org/schema/beans/spring-aop-3.0.xsd">  
        <!-- 启动对@AspectJ注解的支持 -->  
        <aop:aspectj-autoproxy/>  
</beans>  

综上,Spring下AOP的配置与实现,BeanNameAutoProxyCreator,DefaultAdvisorAutoProxyCreator已经部分简化了AOP配置,然而还是很繁琐: 首先要编写xxxAdvice类(需要自己实现MethodBeforeAdvice、MethodAfterAdvice、 ThrowsAdvice、MethodInterceptor接口之一),然后还要在xml中配置Advisor,还要在Advisor中注入 Advice,最后还要将Advisor加入ProxyFactoryBean、BeanNameAutoProxyCreator或者 DefaultAdvisorAutoProxyCreator中。

实际上AOP不止Spring进行了实现,还有AspectJ,后者对AOP中的概念实现比较彻底,可以看上面,而Spring中对AOP的方方面面进行简化,拿上面定义的regexpFilterPointcutAdvisor是一种Advisor包含了PointCut和Advice,而此处的PointCut就是pattern属性的值了,没有特定的PointCut Bean定义,而advice定义了Bean。而其他概念Aspect, JoinPoint都融汇于Advice的实现中即Advisor(MethodBeforeAdvice等和MethodIntector接口的实现类)或IntroductionInterceptor了

较详细的介绍AOP http://blog.chinaunix.net/uid-21547257-id-97998.html

利用Spring Interceptor 来缓存指定方法结果

http://www.blogjava.net/kapok/archive/2005/04/17/3388.html

Advice的类型及实现细节:

http://blog.csdn.net/saintlu/article/details/3340190

http://www.cnblogs.com/tazi/articles/2306160.html  

http://blog.csdn.net/a906998248/article/details/7514969

http://sishuok.com/forum/blogPost/list/2466.html;jsessionid=AC7C2BDCBF5B19BE4327AD26D40C3CFF

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这