Spring 面向切面编程(AOP)

基于 XML 配置文件的 AOP

一、创建切入点类

public class PointCutBean {

    public void sayHello() {
        System.out.println("Hello World!");
    }
}

二、创建通知类

public class AdviceBean {

    public void writeLine() {
        System.out.println("------------------------------");
    }
}

三、创建 Spring 配置文件

<?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.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联 -->
            <aop:before method="writeLine" pointcut="execution(* chu.yi.bo.PointCutBean.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

四、测试

public void testAOP() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Application.xml");
    PointCutBean pointCutBean = (PointCutBean)applicationContext.getBean("pointCutBean");
    pointCutBean.sayHello();
}

五、切入点表达式

      切入点表达式用来进行切入点(方法)的匹配,语法结构是:execution([修饰符] 返回值类型 包名.类名.方法名(参数))。修饰符是非必选项,返回值类型、包名的任意一级、类名、方法名、参数,都可以用通配符 * 来代替。包名、参数名也可以用通配符 . . 来代替,例子如下:

  • 无省略模式
    execution(public void chu.yi.bo.PointCutBean.sayHello())
  • 省略修饰符
    execution(void chu.yi.bo.PointCutBean.sayHello())
  • 使用 * 号代替返回值,表示任意返回类型
    execution(*  chu.yi.bo.PointCutBean.sayHello())
  • 使用 * 号替代任意一级包
    execution(*   *.*.*.PointCutBean.sayHello())
  • 使用 .. 表示当前包以及子包
    execution(*   chu..PointCutBean.sayHello())
  • 使用 * 号代替类名,表示任意类
    execution(*   chu..*.sayHello())
  • 使用 * 号代替方法名,表示任意方法
    execution(*   chu..*.*())
  • 方法有参数时,可以使用 * 替代任意类型参数
    execution(*   chu..*.*(*))
  • 无论方法有无参数都可以使用 .. 代替任意类型参数
    execution(*   chu..*.*(..))
  • 全使用通配符
    execution(*   *..*.*(..))

配置 AOP 相关标签

标签 说明 属性
aop:config 用于声明开始 AOP 的配置
aop:aspect 用于配置切面 id:给切面提供唯一标识
ref:引用配置好的通知 bean 的 id
aop:pointcut 指定对哪些类的哪些方法进行增强,配置切入点表达式 expression:用于定义切入点表达式
id:给切入点表达式提供唯一标识
aop:before 配置前置通知。指定增强的方法在切入点方法之前执行 method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
poinitcut:用于指定切入点表达式
aop:after-returning 配置后置通知,切入点方法正常执行之后。它和异常通知只能有一个执行 同上
aop:after-throwing 配置异常通知,切入点方法执行产生异常后执行。它和后置通知只能执行一个 同上
aop:after 配置最终通知,无论切入点方法执行时是否有异常,它都会在其后面执行 同上
aop:around 配置环绕通知 同上

一、aop:pointcut 标签使用

<?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.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="pt" expression="execution(* chu.yi.bo.PointCutBean.*(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联 -->
            <aop:before method="writeLine" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>
</beans>

      aop:pointcut 标签用于配置切入点,该标签写在 aop:aspect 标签内部只能在当前切面使用,写在 aop:aspect 标签外部可以在所有切面使用。

通知类型

一、前置通知

1、切入点类

public class PointCutBean {

    public void beforePointCut() {
        System.out.println("beforePointCut");
    }
}

2、通知类

public class AdviceBean {

    public void beforeAdvice() {
        System.out.println("beforeAdvice");
    }
}

3、Spring 配置文件

<?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.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="beforePt" expression="execution(* chu.yi.bo.PointCutBean.beforePointCut(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联 -->
            <aop:before method="beforeAdvice" pointcut-ref="beforePt"/>
        </aop:aspect>
    </aop:config>
</beans>

二、后置通知

1、切入点类

public class PointCutBean {

    public void afterReturningPointCut() {
        System.out.println("afterReturningPointCut");
    }
}

2、通知类

public class AdviceBean {

    public void afterReturningAdvice() {
        System.out.println("afterReturningAdvice");
    }
}

3、Spring 配置文件

<?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.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="afterReturningPt" expression="execution(* chu.yi.bo.PointCutBean.afterReturningPointCut(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联 -->
            <aop:after-returning method="afterReturningAdvice" pointcut-ref="afterReturningPt"/>
        </aop:aspect>
    </aop:config>
</beans>

三、异常通知

1、切入点类

public class PointCutBean {

    public void afterThrowingPointCut() {
        System.out.println("afterThrowingPointCut");
        throw new RuntimeException();
    }
}

2、通知类

public class AdviceBean {

    public void afterThrowingAdvice() {
        System.out.println("afterThrowingAdvice");
    }
}

3、Spring 配置文件

<?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.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="afterThrowingPt" expression="execution(* chu.yi.bo.PointCutBean.afterThrowingPointCut(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联 -->
            <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="afterThrowingPt"/>
        </aop:aspect>
    </aop:config>
</beans>

四、最终通知

1、切入点类

public class PointCutBean {

    public void afterPointCut() {
        System.out.println("afterPointCut");
    }
}

2、通知类

public class AdviceBean {

    public void afterAdvice() {
        System.out.println("afterAdvice");
    }
}

3、Spring 配置文件

<?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.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="afterPt" expression="execution(* chu.yi.bo.PointCutBean.afterPointCut(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联 -->
            <aop:after method="afterAdvice" pointcut-ref="afterPt"/>
        </aop:aspect>
    </aop:config>
</beans>

五、环绕通知

1、切入点类

public class PointCutBean {

    public void aroundPointCut() {
        System.out.println("aroundPointCut");
    }
}

2、通知类

public class AdviceBean {

    public Object aroundAdvice(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        try {
            Object[] args = pjp.getArgs();// 得到方法执行所需的参数
            System.out.println("beforeAdvice 前置");
            rtValue = pjp.proceed(args);// 明确调用业务层方法(切入点方法)
            System.out.println("afterReturningAdvice 后置");
            return rtValue;
        } catch (Throwable t) {
            System.out.println("afterThrowingAdvice 异常");
            throw new RuntimeException(t);
        } finally {
            System.out.println("afterAdvice 最终");
        }
    }
}

      Spring 框架提供了 ProceedingJoinPoint 接口,程序运行时,Spring 框架会创建该接口的实例,并作为参数传递给通知方法。调用该实例的 proceed() 方法可以执行切入点方法,调用该实例的 getArgs() 方法可以获取切入点方法的参数列表。

3、Spring 配置文件

<?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.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="adviceBean" class="chu.yi.bo.AdviceBean"/>
    <bean id="pointCutBean" class="chu.yi.bo.PointCutBean"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="aroundPt" expression="execution(* chu.yi.bo.PointCutBean.aroundPointCut(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="writeLineAdvice" ref="adviceBean">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联 -->
            <aop:around method="aroundAdvice" pointcut-ref="aroundPt"/>
        </aop:aspect>
    </aop:config>
</beans>

AOP 相关术语

术语 说明
Joinpoint(连接点) 连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
Pointcut(切入点) 所谓切入点是指要对哪些 Joinpoint 进行拦截的定义
Advice(通知/ 增强) 通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Target(目标对象) 代理的目标对象
Weaving(织入) 把增强应用到目标对象来创建新的代理对象的过程
Proxy (代理) 一个类被 AOP 织入增强后,就产生一个结果代理类
Aspect(切面) 是切入点和通知的结合

基于注解实现 AOP

一、Spring 配置文件

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

    <!-- 配置 spring 创建容器时要扫描的包 -->
    <context:component-scan base-package="chu.yi.bo"/>

    <!-- 配置 spring 开启注解 AOP 的支持 -->
    <aop:aspectj-autoproxy/>
</beans>

二、前置通知

1、切入点类

@Component("pointCutBean")
public class PointCutBean {

    public void beforePointCut() {
        System.out.println("beforePointCut");
    }
}

2、通知类

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @Before("execution(* chu.yi.bo.PointCutBean.beforePointCut(..))")
    public void beforeAdvice() {
        System.out.println("beforeAdvice");
    }
}

三、后置通知

1、切入点类

@Component("pointCutBean")
public class PointCutBean {

    public void afterReturningPointCut() {
        System.out.println("afterReturningPointCut");
    }
}

2、通知类

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @AfterReturning("execution(* chu.yi.bo.PointCutBean.afterReturningPointCut(..))")
    public void afterReturningAdvice() {
        System.out.println("afterReturningAdvice");
    }
}

四、异常通知

1、切入点类

@Component("pointCutBean")
public class PointCutBean {

    public void afterThrowingPointCut() {
        System.out.println("afterThrowingPointCut");
        throw new RuntimeException();
    }
}

2、通知类

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @AfterThrowing("execution(* chu.yi.bo.PointCutBean.afterThrowingPointCut(..))")
    public void afterThrowingAdvice() {
        System.out.println("afterThrowingAdvice");
    }
}

五、最终通知

1、切入点类

@Component("pointCutBean")
public class PointCutBean {

    public void afterPointCut() {
        System.out.println("afterPointCut");
    }
}

2、通知类

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @After("execution(* chu.yi.bo.PointCutBean.afterPointCut(..))")
    public void afterAdvice() {
        System.out.println("afterAdvice");
    }
}

六、环绕通知

1、切入点类

@Component("pointCutBean")
public class PointCutBean {

    public void aroundPointCut() {
        System.out.println("aroundPointCut");
    }
}

2、通知类

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @Around("execution(* chu.yi.bo.PointCutBean.aroundPointCut(..))")
    public Object aroundAdvice(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();// 得到方法执行所需的参数

            System.out.println("beforeAdvice 前置");

            rtValue = pjp.proceed(args);// 明确调用业务层方法(切入点方法)

            System.out.println("afterReturningAdvice 后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("afterThrowingAdvice 异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("afterAdvice 最终");
        }
    }
}

七、@Pointcut 注解配置切入点

1、切入点类

@Component("pointCutBean")
public class PointCutBean {

    public void beforePointCut() {
        System.out.println("beforePointCut");
    }
}

2、通知类

@Component("adviceBean")
@Aspect
public class AdviceBean {

    @Pointcut("execution(* chu.yi.bo.PointCutBean.beforePointCut(..))")
    private void pt(){}

    @Before("pt()")
    public void beforeAdvice() {
        System.out.println("beforeAdvice");
    }
}

动态代理

      Spring 面向切面编程,底层的实现原理是动态代理。

一、接口

public interface HelloInterface {
    void sayHello();
}

二、被代理类

public class Hello implements HelloInterface {

    public void sayHello() {
        System.out.println("Hello World!");
    }
}

三、Java 官方动态代理

public void proxy() {
    final HelloInterface hello = new Hello();

    HelloInterface proxyHello = (HelloInterface) Proxy.newProxyInstance(
            hello.getClass().getClassLoader(),
            hello.getClass().getInterfaces(),
            new InvocationHandler() {

                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("Before invoke "  + method.getName());
                    Object rtValue = method.invoke(hello, args);
                    System.out.println("After invoke " + method.getName());
                    return rtValue;
                }
            }
    );
    proxyHello.sayHello();
}

三、使用 CGLib 实现动态代理

public void proxy() {
    final HelloInterface hello = new Hello();

    HelloInterface proxyHello = (HelloInterface) Enhancer.create(hello.getClass(),
            new MethodInterceptor() {

                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    System.out.println("Before invoke "  + method.getName());
                    Object rtValue = method.invoke(hello, objects);
                    System.out.println("After invoke " + method.getName());
                    return rtValue;
                }
            });
    proxyHello.sayHello();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章