Spring学习(六)-面向切面编程(AOP)

本文介绍了 Spring 中面向切面编程(AOP)的基本概念及其两种实现方式:XML配置和注解方式。通过示例展示了如何使用 AOP 来处理日志记录、事务管理等横切关注点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在软件系统中,存在一些功能需要用到应用程序的多个地方,但是我们又不想在每个点都去调用它,比如日志,事务管理,安全等。如果我们让应用对象只关注于自己所针对的业务领域问题,而上述这些功能由其他应用对象自行处理。为解决上述问题,Spring提出了面向切面的编程思想。在给出AOP的具体实现之前,先给出AOP相关的一些具体概念。

1、基本概念
(1)横切关注点
在软件开发过程中,散布于应用中多处的功能。面向切面编程的目的就是解决把横切关注点和业务逻辑相分离。
切面:把横切关注点模块化成为一些特殊的类。切面告诉我们是什么,在何时何处完成 其功能。

(2)通知
在AOP中,切面的工作称之为通知。通知主要定义了切面是什么,以及何时使用。
Spring切面可以应用5种类型的通知:

  • 前置通知(Before):在目标方法被调用之前调用通知功能;
  • 后置通知(After):在目标方法完成之后调用,此时不会关心方法的输出是什么;
  • 返回通知(After-returning):在目标方法执行成功后调用通知;
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

(3)切点
切点定义了通知在何处执行。切点和通知结合构成了一个完整的切面。

2、AOP具体实现
除了Spring的基础jar外,还需要额外导入aspectjweaver.jar,aopalliance.jar,aspectjrt.jar

(1)在XML中声明切面
定义一个接口IHelloWorldService :

package com.wygu.service;

public interface IHelloWorldService {

    public void sayBefore();

    public void sayAfter();

    public boolean sayAfterReturning();

    public void sayAfterThrow();

    public void sayAround();    

}

编写接口的实现HelloWorldService :

package com.wygu.impl;

import com.wygu.service.IHelloWorldService;

public class HelloWorldService implements IHelloWorldService{

    @Override
    public void sayBefore() {
        System.out.println("Before Hello World!");

    }

    @Override
    public void sayAfter() {
        System.out.println("After Hello World!");   

    }

    @Override
    public boolean sayAfterReturning() {
        System.out.println("After Returning Hello World!"); 
        return true;
    }

    @Override
    public void sayAfterThrow() {
        System.out.println("After Throw Hello World!"); 
        throw new RuntimeException("后置抛出异常");
    }

    @Override
    public void sayAround() {
        System.out.println("Around Hello World!");  

    }
}

定义切面HelloWorldAspect:

package com.wygu.aop;

import org.aspectj.lang.ProceedingJoinPoint;

public class HelloWorldAspect {

    public void beforeAdvice(){
        System.out.println("*******************前置通知");
    }

    public void afterAdvice(){
        System.out.println("*******************后置通知");
    }

    public void afterReturnAdvice(Object retVal){
        System.out.println("*******************返回通知"+":"+retVal);
    }

    public void afterThrowingAdvice(Exception exception){
        System.out.println("*******************异常通知");
        exception.printStackTrace();
    }

    public void aroundAdvice(ProceedingJoinPoint pjp){
        try {
            System.out.println("******************环绕之前通知");  
            //执行环绕方法
            pjp.proceed();
            System.out.println("******************环绕之后通知");  
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("******************执行环绕方法失败");
        }  

    }

}

定义配置文件spring-aop.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-4.3.xsd  
           http://www.springframework.org/schema/aop  
           http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">  


        <!-- 定义Bean实例 -->  
        <bean id="helloWorldService"  class="com.wygu.impl.HelloWorldService"/> 
        <!-- -定义切面Bean实例--> 
        <bean id="helloAspect"  class="com.wygu.aop.HelloWorldAspect"/>

         <aop:config>

            <!-- -声明切面 -->
            <aop:aspect id="ascept" ref="helloAspect">

                <!-- -声明切点 -->
                <aop:pointcut id="pointcut1" expression="execution(* com.wygu.impl.*.sayBefore(..))" />
                <aop:pointcut id="pointcut2" expression="execution(* com.wygu.impl.*.sayAfter(..))" />
                <aop:pointcut id="pointcut3" expression="execution(* com.wygu.impl.*.sayAfterReturning(..))" />
                <aop:pointcut id="pointcut4" expression="execution(* com.wygu.impl.*.sayAfterThrow(..))" />
                <aop:pointcut id="pointcut5" expression="execution(* com.wygu.impl.*.sayAround(..))" />

                <!-- -前置通知 -->
                <aop:before method="beforeAdvice" pointcut-ref="pointcut1"/>
                <!-- -后置通知 -->
                <aop:after method="afterAdvice" pointcut-ref="pointcut2" />
                 <!-- -后置返回通知 -->
                <aop:after-returning method="afterReturnAdvice" pointcut-ref="pointcut3"
                                     arg-names="retVal" returning="retVal" />
                <!-- -后置异常通知 -->                     
                <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pointcut4"
                                    arg-names="exception"  throwing="exception" />
                <!-- -环绕通知 -->                  
                 <aop:around method="aroundAdvice" pointcut-ref="pointcut5"/>  

            </aop:aspect>
        </aop:config>     

</beans>  

程序测试:

package com.wygu.main;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.wygu.service.IHelloWorldService;

public class TestAOP {

    public static void main(String[] args) throws InterruptedException {
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop.xml");
        IHelloWorldService helloWorldService = (IHelloWorldService) context.getBean("helloWorldService");
        helloWorldService.sayBefore();
        helloWorldService.sayAfter();
        helloWorldService.sayAfterReturning();
        try {
            helloWorldService.sayAfterThrow();
        } catch (Exception e) {
            Thread.sleep(1000);
        }

        helloWorldService.sayAround();

    }

}

最终执行结果为:

八月 28, 2017 8:23:51 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@55801443: startup date [Mon Aug 28 20:23:51 GMT+08:00 2017]; root of context hierarchy
八月 28, 2017 8:23:51 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring-aop.xml]
*******************前置通知
Before Hello World!
After Hello World!
*******************后置通知
After Returning Hello World!
*******************返回通知:true
After Throw Hello World!
*******************异常通知
java.lang.RuntimeException: 后置抛出异常
    at com.wygu.impl.HelloWorldService.sayAfterThrow(HelloWorldService.java:28)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy2.sayAfterThrow(Unknown Source)
    at com.wygu.main.TestAOP.main(TestAOP.java:18)
******************环绕之前通知
Around Hello World!
******************环绕之后通知

(2)使用注解创建切面
我们利用XML方式实现了AOP的五种通知类型,然而在实际开发中,使用比较多的还是利用注解的方式实现注入,下面利用注解实现切面的五种通知类型。
接口IHelloWorldService和接口的实现类HelloWorldService同上一个实例。

修改切面HelloWorldAspect

package com.wygu.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class HelloWorldAspect {

    @Before("execution(* com.wygu.impl.*.sayBefore(..))")
    public void beforeAdvice(){
        System.out.println("*******************前置通知");
    }

    @After("execution(* com.wygu.impl.*.sayAfter(..))")
    public void afterAdvice(){
        System.out.println("*******************后置通知");
    }

    @AfterReturning(value="execution(* com.wygu.impl.*.sayAfterReturning(..))", returning="retVal")
    public void afterReturnAdvice(Object retVal){
        System.out.println("*******************返回通知"+":"+retVal);
    }

    @AfterThrowing(value="execution(* com.wygu.impl.*.sayAfterThrow(..))",throwing="exception" )
    public void afterThrowingAdvice(Exception exception){
        System.out.println("*******************异常通知");
        exception.printStackTrace();
    }

    @Around("execution(* com.wygu.impl.*.sayAround(..))")
    public void aroundAdvice(ProceedingJoinPoint pjp){
        try {
            System.out.println("******************环绕之前通知");  
            //执行环绕方法
            pjp.proceed();
            System.out.println("******************环绕之后通知");  
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("******************执行环绕方法失败");
        }  

    }
}

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

        <!-- 启用AspectJ自动代理 --> 
        <aop:aspectj-autoproxy />

        <!-- 定义Bean实例 -->  
        <bean id="helloWorldService"  class="com.wygu.impl.HelloWorldService"/> 
        <!-- -定义切面Bean实例--> 
        <bean id="helloAspect"  class="com.wygu.aop.HelloWorldAspect"/>

</beans>  

在xml中声明aspect的代理对象,使得在初始化spring 容器的时候,spring会自动对切点生成代理对象。如下:

<!-- 启用AspectJ自动代理 --> 
        <aop:aspectj-autoproxy />

运行结果为:

*******************前置通知
Before Hello World!
After Hello World!
*******************后置通知
After Returning Hello World!
*******************返回通知:true
After Throw Hello World!
*******************异常通知
java.lang.RuntimeException: 后置抛出异常
    at com.wygu.impl.HelloWorldService.sayAfterThrow(HelloWorldService.java:28)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:62)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy10.sayAfterThrow(Unknown Source)
    at com.wygu.main.TestAOP.main(TestAOP.java:19)
******************环绕之前通知
Around Hello World!
******************环绕之后通知
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值