1.Spring AOP简介

AOP是Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行时期动态代理实现程序功能的统一维护的一种技术。

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

 

作用:

AOP可以在程序的运行期间,不修改源码的情况下对方法进行功能增强(通过Spring的配置项);减少项目中重复代码,提高了程序的开发效率及可维护性。

 

底层实现:

AOP底层是通过Spring提供的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态地生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象方法,从而完成功能的增强。

 

2.动态代理技术

常用的动态代理技术有两种:

  •   JDK代理:基于接口的动态代理技术
  •   cglib代理:基于父类的动态代理技术

 

JDK动态代理实现代码(部分):

 

//目标对象
final Target target = new Target();
//增强对象
final Advice advice = new Advice();
TargetInterface proxy = (TargetInterface)Proxy.newProxyInstance(
        target.getClass().getClassLoader(),  //目标对象类加载器
        target.getClass().getInterfaces(),  //目标对象相同的接口字节码对象数组
        new InvocationHandler() {
            //调用代理对象执行invoke反射回调目标方法
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //前置增强方法
                advice.before();
                Object invoke = method.invoke(target, args);
                //后置增强方法
                advice.after();
                return invoke;
            }
        }
);
//调用代理对象的方法
proxy.save();

 

cglib实现动态代理代码(部分):

 

//目标对象
final Target target = new Target();
//增强对象
final Advice advice = new Advice();

//1.创建增强器
Enhancer enhancer = new Enhancer();
//2.设置父类(目标)
enhancer.setSuperclass(Target.class);
//3.设置回调
enhancer.setCallback(new MethodInterceptor() {
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //前置增强方法
        advice.before();
        Object invoke = method.invoke(target, args);
        //后置增强方法
        advice.after();
        return invoke;
    }
});
//4.创建代理对象
  Target proxy = (Target)enhancer.create();
  //5.调用测试目标方法
  proxy.save();

 

3.Spring AOP相关概念

Spring的AOP实现底层其实就是基于上述的动态代理进行一系列封装,封装后只需要开发者对关注的部分进行代码编写,并通过配置方式实现目标方法的增强。

 

其中涉及AOP的相关术语如下:

  •   Target(目标对象):代理的目标对象
  •   Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
  •   Joinpoint(连接点):连接点指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点,说简单点,就是可以被增强的方法。
  •   Pointcut(切入点):切入点就是实际代码中真正被配置、真正被增强的方法
  •   Advice(通知/增强):封装增强业务逻辑的方法
  •   Aspect(切面):是切入点+通知(引介)的结合
  •   Weaving(织入):将切点与通知结合的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

 

3.1基于XML实现Spring AOP

AOP基于XML实现步骤:

①导入AOP相关坐标

②创建目标接口和目标类(内部有切点)

③创建切面类(内部有增强方法)

④将目标类和切面类的对象创建权交给Spring

⑤在ApplicationContext.xml中配置织入关系

⑥编写测试代码

 

AOP相关坐标配置:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>

 

接口与目标类:

public interface TargetInterface {
    void save();
}

public class Target implements TargetInterface {
    public void save() {
        System.out.println("save running...");
    }
}

 

切面类:

public class MyAspect {
    public void before(){
        System.out.println("前置代码增强....");
    }

    public void afterReturning(){
        System.out.println("后置代码增强....");
    }

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前增强...");
        //切点方法
        Object proceed = pjp.proceed();
        System.out.println("环绕后增强...");
        return proceed;
    }

    public void afterThrowing(){
        System.out.println("异常抛出增强...");
    }

    public void after(){
        System.out.println("最终增强...");
    }
}

 

配置ApplicationContext.xml,注意添加aop命名空间:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置目标类-->
    <bean id="target" class="com.fengye.aop.Target"></bean>
    <!--配置切面类-->
    <bean id="myAspect" class="com.fengye.aop.MyAspect"></bean>
    <!--配置织入:告诉Spring框架哪些方法需要进行哪些增强(前置、后置)-->
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
        <!--切面:切点+通知(即save()方法需要执行前置before增强,增强方法为before,增强方法before在MyAspect类中)-->
<aop:before method="before" pointcut="execution(public void com.fengye.aop.Target.save())"></aop:before>
<aop:after-returning method="afterReturning" pointcut="execution(* com.fengye.aop.*.*(..))"></aop:after-returning>
<aop:around method="around" pointcut="execution(* com.fengye.aop.*.*(..))"></aop:around>
<!--当有异常时执行此方法-->
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.fengye.aop.*.*(..))"></aop:after-throwing>
<!--最终增强:不管有无异常,始终会执行此类方法-->
<aop:after method="after" pointcut="execution(* com.fengye.aop.*.*(..))"></aop:after>
        </aop:aspect>
    </aop:config>

</beans>

 

其中切点表达式的写法:

execution([修饰符]返回值类型 包名.类名.方法名(参数))
  •   访问修饰符可以省略
  •   返回值类型、包名、类名、方法名可以使用星号*代表任意
  •   包名与类名之间一个点.代表当前包下的类,两个点..表示当前包及其子包下的类
  •   参数列表可以使用两个点..表示任意个数,任意类型的参数列表

 

通知配置的语法:

<aop:通知类型 method=”切面类中方法名”  pointcut=”切点表达式”></aop:通知类型>

 

通知配置表说明如下:

名称  标签 说明
前置通知 <aop:before> 用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知 <aop:after-returning> 用于配置后置通知。指定增强的方法在切入点方法之后执行
环绕通知 <aop:around> 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行
异常抛出通知 <aop:throwing> 用于配置异常抛出通知。指定增强的方法在出现异常时执行
最终通知 <aop:after> 用于配置最终通知。无论增强方法执行是否有异常都会执行

 

3.2.XML中切点表达式的抽取

 

多个切点表达式在xml中配置如果都是同一个类中的切点方法或者都有公共的写法,则可以抽取成一个固定的表达式,便于维护和引用,具体配置如下:

 

<!--配置织入:告诉Spring框架哪些方法需要进行哪些增强(前置、后置)-->
<aop:config>
    <!--声明切面-->
    <aop:aspect ref="myAspect">
        <!--抽取切点表达式-->
        <aop:pointcut id="commonPointcut" expression="execution(* com.fengye.aop.*.*(..))"></aop:pointcut>
        <!--切面:切点+通知(即save()方法需要执行前置before增强,增强方法为before,增强方法before在MyAspect类中)-->
<!--具体引用公共切点表达式:pointcut-ref-->
        <aop:before method="before" pointcut-ref="commonPointcut"></aop:before>
    </aop:aspect>
</aop:config>

 

3.3.基于注解实现Spring AOP

AOP基于注解实现步骤:

①创建目标接口和目标类(内部有切点)

②创建切面类(内部有增强方法)

③将目标类和切面类的对象创建权交给Spring

④在切面类中使用注解配置织入关系

⑤在配置文件中开启组件扫描和AOP的自动代理

⑥测试

 

其中①、②步骤与XML实现创建方式相同,步骤③中配置Bean管理注解如下:

 

@Component("target")
public class Target implements TargetInterface {
    public void save() {
        System.out.println("save running...");
    }
}

@Component("myAspect")
@Aspect  //标注当前MyAspect是一个切面类
public class MyAspect {
//方法配置暂略
  } 

 

对应ApplicationContext.xml中需要注意一定要配置注解扫描和aop自动代理:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--开启spring组件扫描-->
    <context:component-scan base-package="com.fengye" />
    <!--aop自动代理:识别切面类中的特有aop注解标签-->
    <aop:aspectj-autoproxy/>

</beans>

 

3.4.注解配置AOP类型

注解通知的类型:

通知的注解配置语法:@通知注解("切点表达式”)

名称 注解 说明
前置通知 @Before 用于配置前置通知。指定增强的方法在切入点方法之前执行
后置通知 @AfterReturning 用于配置后置通知。指定增强的方法在切入点方法之后执行
环绕通知 @Around 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行
异常抛出通知 @AfterThrowing 用于配置异常抛出通知。指定增强的方法在出现异常时执行
最终通知 @After 用于配置最终通知。无论增强方式执行是否有异常都会执行

 

具体配置如下:

 

@Component("myAspect")
@Aspect  //标注当前MyAspect是一个切面类
public class MyAspect {

    @Before("execution(* com.fengye.anno.*.*(..))")
    public void before(){
        System.out.println("前置代码增强....");
    }

    @AfterReturning("execution(* com.fengye.anno.*.*(..))")
    public void afterReturning(){
        System.out.println("后置代码增强....");
    }

    @Around("execution(* com.fengye.anno.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前增强...");
        //切点方法
        Object proceed = pjp.proceed();
        System.out.println("环绕后增强...");
        return proceed;
    }

    @AfterThrowing("execution(* com.fengye.anno.*.*(..))")
    public void afterThrowing(){
        System.out.println("异常抛出增强...");
    }

    @After("execution(* com.fengye.anno.*.*(..))")
    public void after(){
        System.out.println("最终增强...");
    }
}

 

3.5注解配置中切点表达式的抽取

 

注解中抽取是在切面类中自定义一个方法(作为@Pointcut的标注方法),在该方法上使用@Pointcut注解定义切点表达式,然后在增强注解中进行引用。具体如下:

@After("commonMethod()")
//@After("MyAspect.commonMethod()")
public void after(){
    System.out.println("最终增强...");
}

@Pointcut("execution(* com.fengye.anno.*.*(..))")
//公共方法作为注解 @Pointcut提取公共切面参数
public void commonMethod(){
}

 

本节Spring相关示例代码已上传至GitHub地址:

https://github.com/devyf/SpringReview/tree/master/fengye_spring_aop

 

posted on 2021-02-14 11:40  人无名,则可专心练剑  阅读(121)  评论(0编辑  收藏  举报