关于Spring AOP的一些知识点整理

前言

AOP(Aspect Oriented Programming ):面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP作为Spring的三大经典思想(IOC、DI、AOP)之一,本文将从以下几个方面整理一下个人对其在Spring中作用和应用的理解,以作为个人学习记录:
- AOP的编程思想
- AOP的一些功能术语
- Spring AOP的代理模式
- Spring的传统AOP编程
- Spring整合AspectJ框架实现的AOP

AOP的编程思想

AOP是为了弥补面向对象的不足,在OOP的基础上进行横向开发。底层实现机制是动态代理。
AOP的编程思路和机制

我们将方法的调用当作一个连接点,那么由连接点随时间的顺序串起来的程序执行流就是整个程序的执行过程。AOP将每个方法的调用,即连接点作为编程的入口,针对方法的调用进行编程。

SpringAOP的理解

AOP的一些功能术语

  1. 目标类(target):就是被增强的类。
  2. 代理类(proxy): 使用动态代理产生目标类的代理。
  3. 增强类:在实际开发中,一些经常出现的公共逻辑,如日志、事务、统计方法执行时间等,这些逻辑可以抽取出来放入一个类中,该类就是增强类。
  4. 连接点(joinpoint):就是目标类中的方法。
  5. 切入点(pointcut):就是目标类中要被增强的方法。
  6. 切面(aspect):实质上就是增强类。
  7. 通知(advice):增强类(切面类)中的方法,用于增强目标方法。
  8. 织入(weaving): 将通知方法加到目标方法中的过程。在Spring AOP中,是在目标对象的运行期织入的。
  9. 引入(introduction): 在目标类引入新的属性或者新的方法

Spring AOP的代理模式

AOP分为静态AOP和动态AOP。静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP为动态AOP,实现的技术为: JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)

Spring AOP是基于动态代理实现的。对于动态代理,首先得有一个代理类,然后再在目标类上实现代理。根据目标类是否实现了接口来完成动态代理。而在Spring AOP中,根据目标类是否实现了接口分为以下两种情况:
1. 如果目标类实现了接口,我们可以使用JDK代理实现动态代理(基于接口实现动态代理)。这时候要求代理类需要实现InvocationHander接口:

InvocationHandler(真正地给目标类的目标方法进行增强)
    method.invoke(目标类,目标方法参数)
    Proxy.newProxyInstance(); 

注:JDK产生的代理对象实现了目标类中所有接口。
2. 如果目标类没有实现接口,我们可以使用CGLIB实现动态代理(基于继承父类而实现代理对象)。当然,就算目标类实现了接口,我们同样可以使用CGLIB来实现动态代理,其本质是使用继承来实现增强的。要使用CGLIB来实现动态代理,要求代理类需要实现MethodInteceptor接口,代理类产生的对象可以通过Enhancer来产生目标对象的代理对象:

 MethodInteceptor 
    method.invoke();   //采用反射机制来完成方法调用
    Enhancer enhancer=new Enhancer();
    enhancer.setSupperClass();
    enhancer.setCallBack();
    enhancer.create();  

Spring的传统AOP编程

在开发前,我们需要知道Spring对AOP的支持很多方面借鉴了Aspect项目。为了准确的寻找到切入点,借鉴了Aspect的指示器,使用最多的指示器还是execution()。

切点表达式语法:execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)。

例:一些常用的切点表达式:

1. execution(public * *(..))   所有public方法
2. execution(* *.*(..))        所有类所有方法
3. execution(* com.xyz.User.*(..)) User下所有方法
4. execution(* com.xyz..*.*(..)) xyz包下所有子包中的所有方法
5. execution(* save*(..)) 区配所有的以save开头的方法

在传统的spring aop开发中它支持增强(advice)有五种:
1. 前置通知:目标方法执行前增强 org.springframework.aop.MethodBeforeAdvice
2. 后置通知:目标方法执行后增强 org.springframework.aop.AfterReturningAdvice
3. 环绕通知:目标方法执行前后进行增强 org.aopalliance.intercept.MethodInterceptor
4. 异常抛出通知:目标方法抛出异常后的增强 org.springframework.aop.ThrowsAdvice
5.引介通知:在目标类中添加一些新的方法或属性 org.springframework.aop.IntroductionInterceptor

开发步骤:
1. 导入相关jar包
2. 编写目标类(target),并交给spring管理

//目标类
public class BookServiceImpl implements BookService {

    @Override
    public void addBook() {
        System.out.println("bookService add...");
    }

}
<!-- 目标类target -->
<bean id="bookService" class="com.xyz.aop.BookServiceImpl"></bean>

3 .编写通知(adivce),并交给spring管理

//通知
public class BookHelper implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置通知...");
    }

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("后置通知...");
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {

        System.out.println("环绕前....");
        Object value = mi.proceed();
        System.out.println("环绕后....");
        return value;

    }

}
<!-- 通知adivce -->
<bean id="bookServiceAdivce" class="com.xyz.aop.BookHelper"></bean>

4 .在配置文件(applicationContext.xml)中配置切面(切面=切点+通知)

1)手动配置切点的方式

<!-- 定义切点 pointcut -->
<bean id="bookServicePointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="pattern" value=".*Book"></property>
</bean>
<!-- 切面aspect=pointcut+advice -->
<bean id="bookServiceAspect" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    <property name="advice" ref="bookServiceAdvice"/>
    <property name="pointcut" ref="bookServicePointCut"/>       
</bean> 
<!-- 代理 proxy -->
<bean id="bookServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="bookService"/>
    <property name="interceptorNames" value="bookServiceAspect"/>
    <property name="proxyInterfaces" value="com.xyz.aop.BookService"/>
</bean>

2)基于aspectJ切点的方式

<aop:config>
       <aop:pointcut expression="execution(* com.xyz..*.(..))" id="mypointcut"/>
       <aop:advisor advice-ref="增强类的名称" pointcut-ref="mypointcut" />
</aop:config>
<aop:config>来声明要对aop进行配置
<aop:pointcut>用于声明切点(对哪些方法拦截)
<aop:advisor>定义传统切面(只包含一个切点,一个增强)
<aop:aspect>定义aspectj框架切面(可以包含多个切点,多个通知)

Spring整合AspectJ框架实现的AOP

AspectJ框架是一个第三方面向切面框架,spring从2.0后可以使用aspectJ框架的部分语法。

AspectJ框架它定义的通知类型有6种
1. 前置通知:Before 相当于BeforeAdvice
2. 后置通知:AfterReturning 相当于AfterReturningAdvice
3. 环绕通知:Around 相当于MethodInterceptor
4. 抛出通知:AfterThrowing 相当于ThrowAdvice
5. 引介通知:DeclareParents 相当于IntroductionInterceptor
6. 最终通知:After 不管是否异常,该通知都会执行
相比spring 的传统AOP Advice多了一个最终通知。一般用于释放资源

1、基于XML的方式
  1. 编写目标类(target)及通知(advice),并交给spring管理
  2. 在配置文件中配置aop相关配置
<!-- 声明要对aop进行配置 -->
<aop:config 手动设置动态代理的方式 如果目标类实现了接口,默认采用的是jdk>
    <!-- 配置切面 -->
    <aop:aspect ref引入增强类>
        <!-- 配置切点 -->
        <aop:pointcut 定义切入点的表达式 id=""/>
        <!-- 前置通知 -->
        <aop:before method="" pointcut=""/>
        <!-- 后置通知 -->
        <aop:after-returning method="" pointcut=""/>
        <!-- 环绕通知 -->
        <aop:around method="" pointcut=""/>
        <!-- 抛出通知 -->
        <aop:after-throwing method="" pointcut=""/>
        <!-- 引介通知 -->
        <aop:dclare-parents method="" pointcut=""/>
        <!-- 最终通知 -->
        <aop:after method="" pointcut=""/>
    </aop:aspect>
</aop:config>
2、基于注解的方式
  1. 编写目标类并在applicationContext.xml中配置注解扫描
<context:component-scan base-package="com.xyz"/>

2 .编写增强(advice)

//通知
@Component
@Aspect    //声明当前bean是切面 
public class BookHelper{
    // 前置通知
    @Before("execution(* *.s*(..))")  //切点表达式
    public void before(){

    }
}

注:其他通知类型及参数

//后置通知 
@AfterReturning(value="切点表达式")
public void afterReturning(JoinPoint jp,Object value){
    System.out.println("后置通知,目标方法返回的是" + value);
}
//环绕通知
@Around("切入点表达式")
public Object around(ProceedingJoinPoint pjp) throws Throws Throwable{
    System.out.println("环绕前");
    Object value = pjp.proceed();
    System.out.println("环绕后");
    return value;
}
//异常抛出通知
@AfterThrowing(value="切入点表达式",throwing="ex")
public void afterThrowing(JoinPoint jp,Throwable ex){
    System.out.println("异常抛出通知:" + ex);
}
//最终通知
@After("切入点表达式")
public void after() {
    System.out.println("最终通知");
}

通过JoinPoint参数,可以获取目标相关的信息

另:在每一个通知中定义切点,工作量大,不方便维护,我们可以在一个空的方法上使用@Pointcut来声明切点:

@Pointcut("execution(* *.s*(..))")
private void mypointcut(){}

@Before("mypointcut()")
public void before(){

}

切点允许逻辑运算如@Pointcut(“mypointcut()||mypointcut2()”)

3 .在applicationContext.xml中开启aspectJ注解自动代理功能:

<!-- 开启aspectJ注解自动代理 -->
<aop:aspectj-autoproxy>

注:该标签中proxy-target-class属性默认值是false,代表的是如果目标是有接口的使用proxy代理,如果没有接口使用cglib。如果将proxy-target-class设为true,那么不管目标是否有接口,都会使用cglib进行代理。

结语

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。主要应用在日志记录,性能统计,安全控制,事务处理,异常处理等等方面上。

猜你喜欢

转载自blog.csdn.net/weixin_42969319/article/details/81809183