Spring-AOP

AOP概述

AOP 即 Aspect Oriented Program 面向切面编程,首先,在面向切面编程的思想里面,把功能分为核心业务功能辅助功能

  • 核心业务:比如登陆,增加数据,删除数据都叫核心业务
  • 辅助功能:比如性能统计,日志,事务管理等等

面向切面的编程(AOP)是一种编程范式,旨在通过允许横切关注点的分离,提高模块化。AOP提供切面来将跨越对象关注点模块化。
AOP要实现的是在我们写的代码的基础上进行一定的包装,如在方法执行前、或执行后、或是在执行中出现异常后这些地方进行拦截处理或叫做增强处理

Aop的概念

  • Pointcut切点一个基于正则表达式的表达式。通常一个pointcut,会选取程序中的某些我们感兴趣的执行点,或者说是程序执行点的集合。
  • JoinPoint:通过pointcut选取出来的集合中的具体的一个执行点,我们就叫JoinPoint。
  • Advice通知。在选取出来的JoinPoint上要执行的操作逻辑。5种类型:环绕通知(@Around)、前置通知(@Before)、后置通知(@After)、最终通知(@AfterRunning)、异常通知(@AfterThrowing)。
  • Aspect切面。这个关注点可能会横切多个对象和模块,事务管理是横切关注点的很好的例子。由pointcut 和advice组成
  • Weaving织入。把切面应用到目标对象来创建新的 advised 对象的过程。

AOP现有两个主要的流行框架,即SpringAOPAspectJ

SpringAOP和AspectJ区别:

  • 织入的时期不同:SpringAop采用的动态织入,而Aspectj是静态织入。SpringAop基于动态代理来实现,默认如果使用接口的,用JDK提供的动态代理实现,如果是方法则使用CGLIB实现;而Aspectj静态织入,通过修改代码来实现,编译前、后都可以进行织入,而且类加载后也可以织入。
  • 使用对象不同:SpringAOP需要依赖IOC容器来管理,并且只能作用于Spring容器中的SpringBean对象;而AspectJ可以在任何Java对象上应用通知
  • 功能上:Spring AOP的功能不如AspectJ全面。
  • 性能上:Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如AspectJ好

SpringAop概述

在Spring-AOP思想里,辅助功能被定义为切面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这种可选择性的,低耦合的把切面和核心业务功能结合在一起的编程思想,就叫做切面编程(AOP)。

利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

底层原理

AOP底层使用动态代理:

  • 有接口情况,使用JDK动态代理。
    • 1、创建方法拦截器类(JDK动态代理实现接口InvocationHandler.invoke()),实现相应接口方法。
    • 2、使用方法拦截器类,创建动态代理类(JDK动态代理使用Proxy.newProxyInstance(ClassLoader var0, Class<?>[] var1, InvocationHandler var2))。
    • 3、使用动态代理类调用方法即可。
  • 没有接口情况,使用CGLIB动态代理。
    • 1、创建方法拦截器类(CGLIB动态代理实现接口MethodInterceptor.intercept()),实现相应接口方法。
    • 2、使用方法拦截器类,创建动态代理类(CGLIB动态代理使用Enhancer.setCallback(Callback callback))。
    • 3、使用动态代理类调用方法即可。

代码详见《Java-动态代理》

AspectJ概述

AspectJ全称是Eclipse AspectJ,AspectJ不是Spring的组成部分。
AspectJ是一个易用的功能强大的AOP框架,可以单独使用,也可以整合到其它框架中。一般把AspectJ和Spirng框架一起使用,Spring框架一般都是基于AspectJ实现AOP操作

基于AspectJ实现AOP操作:

  • 基于xml配置文件实现
  • 基于注解方式实现(常用)

基于xml配置文件方式

引用AspectJ包

业务类 ProductService:

package springAop.AOP.service;

public class ProductService {

    public void doSomeService(){

        System.out.println("doSomeService");

    }
}

切面类 LoggerAspect:

package springAop.AOP.aspect;
import org.aspectj.lang.ProceedingJoinPoint;

public class LoggerAspect {
    
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("start log:" + joinPoint.getSignature().getName());
        
        Object object = joinPoint.proceed();
        
        System.out.println("end log:" + joinPoint.getSignature().getName());
        return object;
    }
}

配置文件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/aop/spring-aop-3.0.xsd">

    <!-- 声明业务对象 -->
    <bean name="s" class="springAop.AOP.service.ProductService" />

    <!-- 声明日志切面 -->
    <bean id="loggerAspect" class="springAop.AOP.aspect.LoggerAspect"/>
    
    <!--aop:config 把业务对象与辅助功能编织在一起-->
    <aop:config>
        <!-- 指定切点-核心业务功能-->
        <aop:pointcut id="productServiceCutpoint"
                      expression="execution(* springAop.AOP.service.ProductService.*(..)) "/>

        <!-- 指定切面-辅助功能-->
        <aop:aspect id="logAspect" ref="loggerAspect">
            <aop:around pointcut-ref="productServiceCutpoint" method="log"/>
        </aop:aspect>
    </aop:config>

</beans>
  • aop:pointcut:指定切点-核心业务功能,指定被代理的类方法。
  • aop:aspect:指定切面-辅助功能,指定增强类方法以及切点。
  • aop:config:把切点与切面编织在一起。

execution(* com.how2java.service.ProductService.*(…)) :

  • 第一个 * :表示返回任意类型
  • 第二个 * :表示任意方法
  • (…) :表示参数是任意数量和任意类型
  • com.how2java.service.ProductService.* :表示包名以 com.how2java.service.ProductService 开头的类的任意方法

TestSpring:

package springAop.AOP;

import configTest.AOP.service.ProductService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"springAop/AOP/applicationContext.xml"});
        ProductService s = (ProductService) context.getBean("s");
        s.doSomeService();
    }
}

运行结果:

基于注解方式(常用)

注解业务类 ProductService:

package springAop.AOP.service;
import org.springframework.stereotype.Component;

@Component("s")
public class ProductService {
    public void doSomeService(){
        System.out.println("doSomeService");
    }
}

注解切面类 LoggerAspect:

package springAop.AOP.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggerAspect {

    @Around(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("start log:" + joinPoint.getSignature().getName());
        Object object = joinPoint.proceed();
        System.out.println("end log:" + joinPoint.getSignature().getName());
        return object;
    }
}

@Aspect 注解表示这是一个切面
@Component 表示这是一个bean,由Spring进行管理。
@Around(value = “execution(* springAop.AOP.service.ProductService.*(..))”) 表示对com.how2java.service.ProductService 这个类中的所有方法进行切面操作

语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]) )

  • execution(* springAop.BookDao.add(..)):对springAop.BookDao类里面的add进行增强
  • execution(* springAop.BookDao.* (..)):对springAop.BookDao类里面的所有的方法进行增强
  • execution(* springAop.*.* (..)):对springAop包里面所有类,类里面所有方法进行增强

配置文件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"
       xmlns:context="http://www.springframework.org/schema/context"
       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/aop/spring-aop-3.0.xsd
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="springAop.AOP.service"/>
    <context:component-scan base-package="springAop.AOP.aspect"/>
    <!-- 开启Aspect生成代理对象-->
    <aop:aspectj-autoproxy/>
</beans>

运行结果:

相同的切入点抽取

注解切面类 LoggerAspect:

package springAop.AOP.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggerAspect {
    /* 定义切点*/
    @Pointcut(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public void productServicePointcut(){}

    @Around(value = "productServicePointcut()")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("start log:" + joinPoint.getSignature().getName());
        Object object = joinPoint.proceed();
        System.out.println("end log:" + joinPoint.getSignature().getName());
        return object;
    }
}

运行结果:

@Pointcut(value = “execution(* springAop.AOP.service.ProductService.*(..))”)
public void productServicePointcut(){}

定义切点 productServicePointcut() ,通知类方法可以直接使用。也可以专门创建切点类,统一管理所有切点。

多个切面类增强一个方法

增加统计增强类ReportAspect:

package springAop.AOP.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(1)
@Aspect
@Component
public class ReportAspect {
    /* 定义切点*/
    @Pointcut(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public void productServicePointcut(){}

    @Around(value = "productServicePointcut()")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("report:" + joinPoint.getSignature().getName());
        Object object = joinPoint.proceed();
        System.out.println("report:" + joinPoint.getSignature().getName());
        return object;
    }
}

切面类 LoggerAspect增加优先级注解@Order(2):

@Order(2)
@Aspect
@Component
public class LoggerAspect {
    ...
}

运行结果:

在增强切面类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高

五种通知注解

  • @Before: 前置通知,在方法执行之前执行。value
  • @After: 后置通知,在方法执行之后执行。无论连接点方法执行成功还是出现异常,都将执行后置方法。value
  • @Around: 环绕通知,围绕着方法执行。环绕通知需要携带 ProceedingJoinPoint 类型的参数,且环绕通知必须有返回值返回值即为连接点方法的返回值value
  • @AfterRunning: 返回通知,在方法返回结果之后执行。当连接点方法成功执行后,返回通知方法才会执行。所以,返回通知方法可以拿到连接点方法执行后的结果。value、pointcut、returning
  • @AfterThrowing: 异常通知,在方法抛出异常之后。当连接点方法执行异常,异常通知方法才会执行value、pointcut、throwing

LoggerAspect:

package springAop.AOP.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggerAspect {
    //@Before 注解表示作为前置通知
    @Before(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public void beforMethod(JoinPoint point){
		String methodName = point.getSignature().getName();
		List<Object> args = Arrays.asList(point.getArgs());
		System.out.println("调用前连接点方法为:" + methodName + ",参数为:" + args);
		System.out.println("Before...");
    }

    //@After 后置通知
    @After(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public void afterMethod(JoinPoint point) {
		String methodName = point.getSignature().getName();
		List<Object> args = Arrays.asList(point.getArgs());
		System.out.println("调用前连接点方法为:" + methodName + ",参数为:" + args);
        System.out.println("After...");
    }

    //@Around: 环绕通知
    @Around(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("Around环绕之前...");
        //被增强的方法执行
        proceedingJoinPoint.proceed();
        System.out.println("Around环绕之后...");
    }

    //@AfterRunning: 返回通知
    @AfterReturning(value = "execution(* springAop.AOP.service.ProductService.*(..))",
			returning="result")
    public void afterReturning(JoinPoint point, Object result) {
        String methodName = point.getSignature().getName();
        List<Object> args = Arrays.asList(point.getArgs());
		System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",目标方法执行结果为:" + result);
        System.out.println("AfterReturning...");
    }

    //@AfterThrowing: 异常通知
    @AfterThrowing(value = "execution(* springAop.AOP.service.ProductService.*(..))",
			throwing="ex")
    public void afterThrowing(JoinPoint point, Exception ex) {
		String methodName = point.getSignature().getName();
		List<Object> args = Arrays.asList(point.getArgs());
		System.out.println("连接点方法为:" + methodName + ",参数为:" + args + ",异常为:" + ex);
		System.out.println("AfterThrowing...");
    }
}

运行结果:

五种通知注解的执行顺序

正常情况

通过上面的运行结果:

可以看出正常情况的执行顺序: @Around -> @Before -> 切点方法 -> @Around -> @After -> @AfterReturning

异常情况

业务类 ProductService增加异常抛出

package springAop.AOP.service;
import org.springframework.stereotype.Component;

@Component("s")
public class ProductService {
    public void doSomeService(){
        System.out.println("doSomeService");
        throw new NullPointerException();
    }
}

运行结果:

可以看出异常情况的执行顺序: @Around -> @Before -> 切点方法 -> @After -> @AfterThrowing

多个增强类增强一个方法的执行顺序

日志切面LoggerAspect

package springAop.AOP.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(2)
@Aspect
@Component
public class LoggerAspect {
    //@Before 注解表示作为前置通知
    @Before(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public void before() {
        System.out.println("[LoggerAspect]Before...");
    }
    //@After 后置通知
    @After(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public void after() {
        System.out.println("[LoggerAspect]After...");
    }
    //@Around: 环绕通知
    @Around(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[LoggerAspect]Around环绕之前...");
        //被增强的方法执行
        proceedingJoinPoint.proceed();
        System.out.println("[LoggerAspect]Around环绕之后...");
    }
    //@AfterRunning: 返回通知
    @AfterReturning(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public void afterReturning() {
        System.out.println("[LoggerAspect]AfterReturning...");
    }
    //@AfterThrowing: 异常通知
    @AfterThrowing(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public void afterThrowing() {
        System.out.println("[LoggerAspect]AfterThrowing...");
    }
}

统计切面ReportAspect

package springAop.AOP.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(1)
@Aspect
@Component
public class ReportAspect {
    /* 定义切点*/
    @Pointcut(value = "execution(* springAop.AOP.service.ProductService.*(..))")
    public void productServicePointcut(){}

    //@Before 注解表示作为前置通知
    @Before(value = "productServicePointcut()")
    public void before() {
        System.out.println("[ReportAspect]Before...");
    }
    //@After 后置通知
    @After(value = "productServicePointcut()")
    public void after() {
        System.out.println("[ReportAspect]After...");
    }
    //@Around: 环绕通知
    @Around(value = "productServicePointcut()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[ReportAspect]Around环绕之前...");
        proceedingJoinPoint.proceed();
        System.out.println("[ReportAspect]Around环绕之后...");
    }
    //@AfterRunning: 返回通知
    @AfterReturning(value = "productServicePointcut()")
    public void afterReturning() {
        System.out.println("[ReportAspect]AfterReturning...");
    }
    //@AfterThrowing: 异常通知
    @AfterThrowing(value = "productServicePointcut()")
    public void afterThrowing() {
        System.out.println("[ReportAspect]AfterThrowing...");
    }
}

运行结果:

优先级高的拦截器先执行@Around -> @Before 然后低优先级的拦截器的通知方法全部执行后 再执行高优先级拦截器的@Around -> @After -> @AfterReturning

完全使用注解开发

创建配置类,不再需要xml配置文件

package springAop.AOP;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = {"springAop.AOP"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}

@EnableAspectJAutoProxy(proxyTargetClass = true)

  • proxyTargetClass = false:使用JDK的基于接口的方式进行织入的。这时候代理生成的是一个接口对象,将这个接口对象强制转换为实现该接口的一个类;
  • proxyTargetClass = true: 使用cglib的动态代理方式。

小结

  1. AOP面向切面编程,在不修改原来代码前提下,对原逻辑进行拦截增强处理,提高模块化,降低耦合性。
  2. Pointcut(切点)、JoinPoint(具体执行点)、Advice(通知)、Aspect(切面)、Weaving(织入)
  3. @Aspect标识类是切面类
  4. @Around(value = “execution(…)”)标识切面方法
  5. @Pointcut(value = “execution(…)”)标识切点
  6. 多个切面类增强一个方法时,通过@Order(数字类型值)标识切面类,数字类型值越小优先级越高。
  7. 五种通知注解:@Before、@After、@Around、@AfterRunning、@AfterThrowing
  8. 完全使用注解开发:@EnableAspectJAutoProxy(proxyTargetClass = true)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会叫的狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值