Spring【八】AOP

目录

一、AOP基本介绍

1.应用场景

2.基本术语解释

二、Java AOP框架---AspectJ

1.Eclipse IDE中安装AJDT的步骤:

2.使用Eclipse AJDT 开发AspectJ实例

3.AspectJ 切入点执行表达式

4.AspectJ注解开发

三、Spring AOP

1.Spring AOP框架的主要接口和类

2.ProxyFactoryBean的使用

2.1.基于接口的代理

2.2.基于类的代理

2.3.使用拦截器替代增强

3.增强器(Advisor)

4.基于XML的AOP配置

4.1.切面标签

4.2.增强器标签

4.3.和比较

5.基于注解的AOP配置


一、AOP基本介绍

AOP(Aspect Orented Programming,面向切面编程)是在OOP(Object Orented Programming,面向对象编程)之上延伸出的一种编程思想OOP通过 封装、继承和多态的概念构建类的层级结构,完美地解决纵向共用性,提高了代码地重用性和可维护性。但OOP编程存在着两个不足:对于没有继承关系类的横向共用性无法处理,也无法动态增加类的功能。AOP是在不修改源码的前提下添加功能的一种编程思想,其目的是降低系统业务逻辑之间的耦合性,提高代码的重用性和开发的效率性。AOP和OOP并不冲突,AOP是OOP的补充,两者的角度不同,OOP是纵向结构的类层级,AOP是横向结构的方法层级,两者的关系如下图:

1.应用场景

AOP可以扩展横向共用性,会使得逻辑代码变得干净整洁,它适合的应用场景有很多,常用的如下:

  • 日志记录:不需要在业务代码中混杂日志代码,可以在项目后期织入日志功能。
  • 安全控制:对系统的访问进行登录验证,对请求进行权限验证。
  • 参数检验:对请求的参数进行统一转换或验证。
  • 事务处理:统一处理数据库事务的提交和回滚,比如在方法执行完成后自动提交事务,出现异常时自动回滚。
  • 统一异常处理:不需要在每个方法中使用try catch捕捉异常,使用AOP集中处理。
  • 缓存:使用AOP实现缓存存取和清除。
  • 统一发信、通知,对某些方法执行或者异常拦截发送通知。

2.基本术语解释

  • Aspect:切面,由切点和增强组成,如在Java代码中对应一个切面类。
  • JointPoint:连接点。程序执行的某个位置,如函数调用、函数执行、构造函数调用、获取或设置变量、类初始化等。
  • Pointcut:切入点,也就是特定的连接点。一个程序有多个连接点,但并不是所有连接点都是需要关心的,找到合适的连接点进行切入。切入点就是选取的合适连接点的描述。
  • Advice:增强。织入到目标连接点的代码,也就是额外功能的代码。
  • Weaving:织入。将增强添加到目标连接点的过程。
  • Before(前置)、After(后置)和Around(环绕):织入方位可前、可后也可以是两者的合集。

二、Java AOP框架---AspectJ

JDK的反射与代理特性及第三方动态代理框架提供了AOP实现的基础,但是与实际的AOP开发需要的功能还相差甚远。在Java代理技术的基础上,出现了一些AOP框架,包括Aspect、Javassist及Spring AOP等。其中,AspectJ是目前使用较为广泛的Java AOP框架,Spring AOP框架与其有一定的关系。使用AspectJ开发需要下载AspectJ运行环境和AspectJ依赖包,在基于Eclipse IDE的开发中,安装AJDT(AspectJ Development Tools)插件可以简化AspectJ AOP开发。

1.Eclipse IDE中安装AJDT的步骤:

  • 打开Eclipse,操作如下:

  • 接着将第一步中的URL直接粘贴到下图中的输入框,进行如下几步:

 完成之后Eclipse会提示你重启,重启之后查看Windows--Preferences是否安装成功,如下出现AspectJ Compiler 即为安装成功:

2.使用Eclipse AJDT 开发AspectJ实例

  1. 在Eclipse中建立AspectJ类型项目,New--->Other---->AspectJ---->AspectJ Project;
  2. 定义被代理的类,此处为UserService类
    public class UserService {
        public void doSomthing() {
            System.out.println("-----执行doSomthing方法-----");
        }
    }
    

     

  3.  创建AspectJ的切面文件UserServiceAspect.aj,内容如下:
    public aspect UserServiceAspect {	//定义切面
    	
    	//定义切入点
    	pointcut UserServicePointCut() : execution(* com.mec..*.doSomthing(..));
    	
    	//定义前置增强
    	before(): UserServicePointCut(){
    		System.out.println("前置增强");
    	}
    	
    	//定义后置增强
    	after(): UserServicePointCut(){
    		System.out.println("后置增强");
    	}
    }
    

    使用aspect关键字定义切面,pointcut关键字定义切入点,execution()用于定义切入点表达式,before()after()定义前置和后置增强。

  4. 测试UserService的doSomthing()方法:Run As--->AspectJ/Java Application命令运行,输出如下:

3.AspectJ 切入点执行表达式

AspectJ提供了一套切点表达式,该表达式也被Spring所沿用,表达式语法如下图所示:

  • 位置1:限定符,代表方法权限修饰符,此处匹配public;
  • 位置2:方法返回类型,*号代表所有类型;
  • 位置3:拦截的类所在的包名,..表示当前包及其所有子包;
  • 位置4:类名,*号代表所有类;
  • 位置5:方法名;
  • 位置6:该方法的参数。..代表所有参数,不限个数,不限类型。

常见的AspectJ切点表达式示例如下:

  • execution(public * *(..)):所有public的方法;(第一个*号为匹配所有返回类型,第二个*号匹配所有方法)。
  • execution(* *Somthing(..)):所有add后缀的方法;(第一个*匹配所有返回类型,*Somthing匹配所有以Somthing为后缀的方法)。
  • execution(* com..*Service.do*(..)):匹配com包及其子包下所有以Service为后缀的类中的所有以do为前缀的方法。
  • execution(* doSomthing(String,*)):匹配有两个参数的doSomthing方法,第一个参数为String类型,第二个参数为任意类型。

4.AspectJ注解开发

除了使用.aj文件进行切面的定义,AspectJ从1.5开始提供了注解的开发方式,使用在Bean类和方法中。注解包括:

  • @Apsect:注解切面类;
  • @Pointcut:切点及表达式定义;
  • @Before:前置增强;
  • @After:后置增强;
  • @Around:环绕增强;
  • @AfterReturning:方法返回结果之后执行;
  • @AfterThrowing:方法抛出异常之后执行。
@Aspect
public class UserServiceAspectAnno {    
        @Pointcut("execution(* com.mec..*.doSomthing(..))")
            public void userServicePointCut() {
        }

//	@Before("userServicePointCut()")
//	public void beforeAdvice() throws Throwable {
//		System.out.println("[UserServiceAspectAnno][beforeAdvice]前置增强");
//	}
        
        //目标方法之后执行,不论执行成功或者失败(抛出异常)都执行
	@After("userServicePointCut()")
	public void afterAdvice() throws Throwable {
		System.out.println("[UserServiceAspectAnno][afterAdvice]后置增强");
	}

	@Around("userServicePointCut()")
	public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("[UserServiceAspectAnno][aroundAdvice]环绕增强,方法执行前");
		// 这里利用joinPoint执行原始方法
		Object result = joinPoint.proceed();
		System.out.println("[UserServiceAspectAnno][aroundAdvice]环绕增强,方法执行后");
                //这里如果不能顺利执行return result(抛出异常),那么也算方法没有返回结果,就不能触发afterRunningAdvice()
		return result;
	}

	@AfterReturning("userServicePointCut()")
	public void afterRunningAdvice() throws Throwable {
		System.out.println("[UserServiceAspectAnno][afterRunningAdvice]返回结果之后");
	}
        
        //通过throwing属性可以访问抛出的异常对象
	@AfterThrowing(pointcut = "userServicePointCut()", throwing = "a")
	public void afterThrowingAdvice(Throwable a) throws Throwable {
		System.out.println("[UserServiceAspectAnno][afterThrowingAdvice]抛出异常之后");
		a.printStackTrace();
	}
}

由于@Before与@Around不能同时使用,会编译错误,所以将@Before注释起来。

三、Spring AOP

AOP和IOC是Spring框架的两个核心编程思想。Spring IOC容器将对象间的依赖从对象本身转移到外部容器进行控制,Spring AOP则将非业务代码从业务代码中分离出来,进行统一处理。使用Apring IOC容器,AOP不是必要的,但Spring自身的很多特性与功能是基于AOP的方式实现,比如声明式事务管理

Spring AOP同时支持基于接口的JDK代理和基于类的CGLIB代理,这两种方式各有优劣。CGLIB创建的动态代理的对象性能要比JDK创建的好,但CGLIB创建代理对象所花费的时间比JDK动态代理要多,所以CGLIB适合单例对象或者池对象的代理,因为不需要频繁创建新实例,反之则适用于JDK动态代理技术。

1.Spring AOP框架的主要接口和类

如果使用Maven管理依赖,spring-aop模块会在导入spring-context模块时一并导入。Spring AOP模块主要包括三部分内容:

  • Spring AOP框架核心:包含org.springframework.aop目录下的接口类及除aspectj子目录下的其他接口和类。
  • AspectJ的支持:包括AspectJ类型的代理对象、切点和增强等支持。此外,Spring还提供了单独的AspectJ的集成模块spring-aspects。
  • aopalliance接口的包装:aopallicance是AOP联盟为了规范AOP开发而定义的规范接口,Spring AOP框架基于该规范接口提供实现。

aopalliance定义的主要接口包括:

  • org.aopalliance.aop.Advice:增强接口。
  • org.aopalliance.intercept.Interceptor:拦截器接口。该接口继承自Advice。拦截器是增强的一个子集,关注的是某些特定的连接点(比如属性访问、构造器调用以及方法调用)时的动作。
  • org.aopalliance.intercept.Joinpoint:连接点接口
  • org.aopalliance.intercept.Invocation:调用接口。继承自Joinpoint接口,Invocation是可以被拦截器拦截的Joinpoint。

Spring AOP基于Advice、Interceptor和Joinpoint等接口提供了子接口及实现类,通过动态代理实现AOP功能,AopProxy是代理对象接口ProxyFactoryBean用于对单个目标对象代理

Spring继承aopalliance的Advice和Interceptor,定义了一系列增强和拦截器子接口。应用程序可实现增强接口完成自身的增强逻辑类的定义,常用的增强接口如下:

  • MethodBeforeAdvice接口:目标方法执行前增强。实现类完成before()方法。
  • AfterReturningAdvice接口:目标方法执行后增强(无异常状况下)。实现类完成afterReturning()方法。
  • ThrowsAdvice接口:抛出异常时增强。
  • IntroductionAdvice接口:引介增强,在不改变目标类的状况下,动态增加新的属性和方法。
  • MethodInterceptor接口:方法拦截器,相当于环绕增强,在方法执行前后执行增强。
  • ConstructorInterceptor接口:构造器拦截器。

aopalliance定义了连接点接口(Joinpoint),但没有提供切点(Pointcut)接口,于是Spring定义了该类型的接口,如下:

public interface Pointcut {
    ClassFilter getClassFilter();    //对应类层级的匹配
    MethodMatcher getMethodMatcher();    //对应方法层级的匹配
    Pointcut TRUE = TruePointcut.INSTANCE;
}

切点Bean的配置可以使用ComposablePointcutNameMatchMethodPointcut等,但使用AspectJ表达式定义切点的方式更加简单。

Spring AOP还提供了用于获取代理对象的接口AopProxy,该接口定义了获取代理对象的方法getProxy()。框架默认提供的实现类包括JdkDynamicAopProxyCglibAopProxy及其子类ObjenesisCglibAopProxy,分别对应JDK和CGLIB的动态代理。在Spring AOP中,类的代理对象不会直接使用JdkDynamicAopProxy或ObjenesisCglibAopProxy创建,而是统一通过代理工厂Bean(ProxyFactoryBean)创建。

public class ProxyFactoryBean extends ProxyCreatorSupport 
        implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware
  • 实现FactoryBean接口,具备工厂Bean特性,通过getObject()方法得到实际对象。工厂Bean可以更加灵活地扩展该类地功能。
  • 实现BeanClassLoaderAware接口:可以使用类加载器地功能。
  • 实现BeanFactoryAware接口,具备Spring容器对象(BeanFactory)的功能。
  • ProxyCreatorSupport类实现了代理创建、增强等功能,在该类中使用DefaultAopProxyFactory的createAopProxy()创建代理对象。默认使用JDK代理,也可以指定使用CGLIB代理。

2.ProxyFactoryBean的使用

ProxyFactoryBean可以实现单个Bean的代理并织入增强功能,通过在配置文件中 配置代理Bean的目标对象及拦截器即可。目标对象类可以继承接口,也可以不继承接口,框架可以自动选择代理实现方式。

2.1.基于接口的代理

定义IUserService接口及其实现类UserService,如下:

public interface IUserService {
    public void doSomthing();
}

public class UserService implements IUserService {
    @Override
    public void doSomthing() {
        System.out.println("----执行doSomthing方法----");
    }
}

方法增强实现类MyMethodBeforeAdvice,如下:

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置增强");
    }
}

相关的Bean配置:

<bean id="userService" class="com.mec.spring.aop.UserService" />
<bean id="myBeforeAdvice" class="com.mec.spring.aop.MyMethodBeforeAdvice" />

<!--userServiceProxy即为代理对象Bean,该Bean中的方法是增强后的方法-->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!--代理的接口,基于JDK接口的代理时配置,不配置容器也可以自行处理。-->
    <property name="interfaces" value="com.mec.spring.aop.IUserService" />
    <property name="target">
        <!--委托类的Bean-->
        <ref bean="userService"/>
    </property>
    <property name="interceptorNames">
        <list>
            <!--代理对象绑定的增强或拦截器的Bean-->
	    <value>myBeforeAdvice</value>
	</list>
    </property>
</bean>

2.2.基于类的代理

如果目标对象没有实现任何接口,ProxyFactoryBean会自动使用CGLIB代理,也可以设置proxyTargetClass属性值为true的方式强制指定。

<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref bean="userService"/>
    </property>
    <property name="interceptorNames" value="myBeforeAdvice" />
    <property name="proxyTargetClass" value="true"></property>
</bean>

ProxyFactoryBean还可以配置的属性有:

  • singleton:生成的代理Bean是否单例,默认是单例;
  • autodetectInterfaces:自动侦测接口,默认值为true,上面基于接口的代理中不强制要求配置接口的原因就是如此。

2.3.使用拦截器替代增强

MethodInterceptor类似于环绕增强,如下:

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("前置拦截");
        Object res = invocation.proceed();
        System.out.println("后置拦截");
        return res;
    }
}

在配置中,将代理Bean的interceptorNames配置为拦截器的Bean的id即可。

3.增强器(Advisor)

上面的ProxyFactoryBean代理中没有任何过滤条件,所有方法执行都会调用配置的增强功能。interceptorNames除了可以指定Advice类型的Bean,还可以指定Advisor类型的Bean。Advisor是切点(Pointcut)和增强(Advice)的适配器,即Advisor用来指定满足切点定义的方法增强。修改配置如下:

<bean id="userService" class="com.mec.spring.aop.UserService" />
<bean id="myMethodInterceptor" class="com.mec.spring.aop.MyMethodInterceptor"></bean>
<!-- 配置切点类型的Bean,该类会匹配方法名。以直接使用NameMatchMethodPointcut,也可以继承该类实现自定义的匹配逻辑 -->
<bean id="namePointCut" class="org.springframework.aop.support.NameMatchMethodPointcut">
    <property name="mappedNames">
        <list>
	    <!-- 匹配以do为前缀的方法名 -->
	    <value>do*</value>
	</list>
    </property>
</bean>
	
<bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    <property name="pointcut"> <!-- 切点 -->
        <ref bean="namePointCut" />
    </property>
    <property name="advice"><!-- 增强 -->
        <ref bean="myMethodInterceptor" />
    </property>
</bean>
	
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref bean="userService"/>
    </property>
    <property name="interceptorNames" value="defaultAdvisor" />
    <property name="proxyTargetClass" value="true"></property>
</bean>

通过以上配置,在执行增强代码之前,都会首先判断是否应该拦截该方法,只对拦截的方法织入增强。

Advisor是增强器的接口,对应两个子接口IntroductionAdvisor(引介增强器)和PointcutAdvisor(切入点增强器)。Spring AOP提供了许多接口的实现类,其中常用的如下:

  • DefaultPointcutAdvisor:默认的切点增强器,该类型实现切点Bean配置拦截规则,开发较为繁琐,但比较灵活。
  • RegexpMethodPointcutAdvisor:方法名正则表达式切点增强器,使用patterns属性配置正则表达式匹配拦截的方法。
  • NameMatchMethodPointcutAdvisor:方法名匹配切点增强器,直接通过mappedNames属性设置需要拦截的方法名匹配,也可以通过*通配符。

4.基于XML的AOP配置

Spring AOP的实现虽然与AspectJ无关,但使用了aspectjweaver的一些功能,所以Spring AOP需要导入aspectjweaver依赖包。aspectjweaver主要提供类加载期织入切面的功能。Spring提供了AOP的相关标签来简化开发,导入Spring AOP和aspectjweaver包并在配置文件的根节点加入aop命名空间之后,就可以在配置文件中使用<aop:config>标签进行AOP配置。

4.1.<aop:aspect>切面标签

如下通过一个示例展示AOP的配置方式,先定义切面类,如下:

//切面类不需要继承特定的接口或类,唯一要求是增强方法必须要包含一个org.aspectj.lang.JoinPoint类型的参数
public class MyAspect {
    // JoinPoint用于获取被切入点传入的参数,任何切入方法的第一个参数都可以是JoinPoint
    // 该参数对象可以获取目标对象和方法名等信息    
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("前置增强");
    }
}

配置如下:

<!--委托类Bean(目标Bean)-->
<bean id="userService" class="com.mec.spring.aop.UserService" />
<!--切面Bean-->
<bean id="myAspect" class="com.mec.spring.aop.MyAspect" />
<!-- AOP配置根标签 -->
<aop:config>
    <!-- 切面Bean的id -->
    <aop:aspect ref="myAspect">
        <!-- 定义切点 -->
        <aop:pointcut id="myPointcut" expression="execution(* com.mec.spring.aop.UserService.*(..))"/>
        <!-- 
	    引用上面切点配置,method指定切面类中的方法名 
	    同样的,还有
		1.<aop:after>(配置后置增强的标签)
		2.<aop:around>(配置环绕增强的标签)
		3.<aop:after-returning>(方法返回之后的增强)
		4.<aop:after-throwing>(抛出异常之后的增强)
	-->
        <!--这里可以通过pointcut-ref引用上面的切点配置,也可以使用pointcut属性直接配置切点表达式-->
        <aop:before pointcut-ref="myPointcut" method="beforeAdvice"/>
    </aop:aspect>
</aop:config>

通过以上配置,获取userService的Bean,调用其中方法就是增强之后的方法。

4.2.<aop:advisor>增强器标签

 增强器(Advisor)包括切点和增强两部分,所以在<aop:advisor>标签中需要使用advice-ref和pointcut-ref(或pointcut)属性配置增强Bean和切点。<aop:advisor>可以看成是<aop:aspect>一种特殊类型,同样配置在<aop:config>标签下。

<bean id="userService" class="com.mec.spring.aop.UserService"></bean>
<bean id="myBeforeAdvice" class="com.mec.spring.aop.MyMethodBeforeAdvice"></bean>
<!-- AOP配置根标签 -->
<aop:config>
    <aop:pointcut expression="execution(* com.mec.spring.aop.UserService.*(..))" id="myPointcut"/>
    <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="myPointcut"/>
</aop:config>

4.3.<aop:aspect>和<aop:advisor>比较

  • <aop:aspect>标签的ref属性指定切面类的Bean名称,该切面类没有特殊要求;但<aop:advisor>的advice-ref属性的Bean就需要实现Advice接口。
  • 若<aop:config>下同时定义了<aop:aspect>和<aop:advisor>,那么<aop:advisor>需要放在前面。
  • <aop:advisor>一般用在事务处理中,而<aop:aspect>一般用在缓存和日志等功能中。如下为事务处理的AOP配置:
<!-- 事务增强 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 所有get前缀的方法开启事务 -->
        <tx:method name="get*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>
<!-- 切面定义 -->
<aop:config>
    <aop:pointcut id="interceptorPointCuts" expression="execution(* com.mec.spring.db.UserDaoImpl.getUser(int))"/>
    <!-- 织入增强 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointCuts"/>
</aop:config>

5.基于注解的AOP配置

在使用组件扫描的组件注解开发方式下,使用AspectJ提供的注解可以完全替代<aop:aspect>和<aop:advisor>等标签配置。结合组件扫描的配置,只需要在配置文件中加上如下配置即可:

<context:component-scan base-package="com.mec.spring.aop" />
<!-- proxy-target-class设置为true即表示强制使用CGLIB代理,若不设置,则框架可以根据该类是否继承接口自动选择代理方式 -->
<aop:aspectj-autoproxy proxy-target-class="true" />

对应AOP标签配置元素,Spring AOP支持的AspectJ注解包括:

  • @Aspect:切面类注解,使用在类上,相当于<aop:config>;
  • @Pointcut:注解在方法上,使该方法成为一个切入点,对应<aop:pointcut>;
  • @Before:前置增强注解;
  • @After:后置增强注解,目标方法之后执行,不论执行成功或者失败(抛出异常)都执行;
  • @AfterReturning:成功执行后置注解,目标方法执行成功后执行,异常不执行;
  • @Around:环绕增强注解,执行前、执行后都可以执行;
  • @AfterThrowing:异常增强注解,发生异常执行。

@Aspect仅仅是切面注解,不会自动注册成Bean,需要结合@Component及其子注解使用。这些注解的使用方法与前面AspectJ介绍中的一致。需要注意的是:增强方法中需要包含一个JoinPoint类型的参数,如果一个类被@Aspect标注,则这个类就不能作为目标类进行增强,使用@Aspect注解后,这个类就会被排除在auto-proxying机制之外。

在完全注解的开发中,使用@EnableAspectJAutoProxy注解在配置类上,可替代<aop:aspectj-autoproxy>标签。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值