【Spring进阶】spring对AOP的支持-注解方式和配置方式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010028869/article/details/51286731

上篇博客中讲解了一些AOP的基础概念和Spring AOP的基础知识点。现在来具体实践一下,一探究竟。

在spring中,常用的AOP实现方式有两种:一种是基于xml配置文件方式的实现,另一种是基于注解的实现。接下来以日志服务类为例,来看看这两种方式的具体实现。


业务类准备

用户服务接口

package com.tgb.spring;

/**
* @ClassName: UserManager
* @Description: 用户业务类接口
* @author 牛迁迁
* @date 2016-4-27 下午8:09:07
*/
public interface UserManager {

    public void addUser(String username, String password);

    public void delUser(int userId);

    public String findUserById(int userId);

    public void modifyUser(int userId, String username, String password);
}


用户服务实现类

package com.tgb.spring;

public class UserManagerImpl implements UserManager {

    public void addUser(String username, String password) {
        System.out.println("---------UserManagerImpl.add()--------");
    }

    public void delUser(int userId) {
        System.out.println("---------UserManagerImpl.delUser()--------");
    }

    public String findUserById(int userId) {
        System.out.println("---------UserManagerImpl.findUserById()--------");
        return "张三";
    }

    public void modifyUser(int userId, String username, String password) {
        System.out.println("---------UserManagerImpl.modifyUser()--------");
    }
}


配置文件方式实现AOP

日志切面类

package com.tgb.spring;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 日志切面类
 * 
 * @author 牛迁迁
 */
public class LogAspect {
    // 任何通知方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint类型
    public void before(JoinPoint call) {
        // 获取目标对象对应的类名
        String className = call.getTarget().getClass().getName();
        // 获取目标对象上正在执行的方法名
        String methodName = call.getSignature().getName();
        System.out.println("前置通知:" + className + "类的" + methodName + "方法开始了");
    }

    public void afterReturn() {
        System.out.println("后置通知:方法正常结束");
    }

    public void after() {
        System.out.println("最终通知:不管方法有没有正常执行完成,一定会返回的");
    }

    public void afterThrowing() {
        System.out.println("异常抛出后通知:方法执行时出异常了");
    }

    // 用来做环绕通知的方法可以第一个参数定义为org.aspectj.lang.ProceedingJoinPoint类型
    public Object doAround(ProceedingJoinPoint call) throws Throwable {
        Object result = null;
        this.before(call);// 相当于前置通知
        try {
            result = call.proceed();
            this.afterReturn(); // 相当于后置通知
        } catch (Throwable e) {
            this.afterThrowing(); // 相当于异常抛出后通知
            throw e;
        } finally {
            this.after(); // 相当于最终通知
        }
        return result;
    }
}

这个类就是日志服务类,是Spring中的切面类Aspect,它定义了许多通知:Before()、afterReturn()、after()和afterThrowing()这些方法都是通知。通知可以看做是原始方法的增强

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:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
    <bean id="userManager" class="com.tgb.spring.UserManagerImpl" />
    <!-- 日志切面类 -->
    <bean id="logAspectBean" class="com.tgb.spring.LogAspect" />
    <!-- 第1步: AOP的配置 -->
    <aop:config>
        <!-- 第2步:配置一个切面 -->
        <aop:aspect id="logAspect" ref="logAspectBean">
            <!-- 第3步:定义切入点,指定切入点表达式 -->
            <aop:pointcut id="allMethod" expression="execution(* com.tgb.spring.*.*(..))" />
            <!-- 第4步:应用前置通知 -->
            <!--  <aop:before method="before" pointcut-ref="allMethod" />  -->
            <!-- 第4步:应用后置通知 -->
            <!-- <aop:after-returning method="afterReturn" pointcut-ref="allMethod" /> -->
            <!-- 第4步:应用最终通知 -->
            <!-- <aop:after method="after" pointcut-ref="allMethod"/> -->
            <!-- 第4步:应用抛出异常后通知 -->
            <!-- <aop:after-throwing method="afterThrowing" pointcut-ref="allMethod"/> -->
            <!-- 第4步:应用环绕通知 -->
              <aop:around method="doAround" pointcut-ref="allMethod" /> 
        </aop:aspect>
    </aop:config>
</beans>

注意:上面配置了前置,后置,最终,环绕等各种通知,但是启动时只能配置一个通知,其他的应该注释掉。

客户端测试

测试环绕通知:

public class Client {

    public static void main(String[] args) {
        BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserManager userManager = (UserManager)factory.getBean("userManager");
        userManager.delUser(1);
    }
}

打印:

前置通知:com.tgb.spring.UserManagerImpl类的delUser方法开始了
---------UserManagerImpl.delUser()--------
后置通知:方法正常结束
最终通知:不管方法有没有正常执行完成,一定会返回的


基于注解的AOP的实现

Spring提供了基于注解的AOP支持,大大简化AOP的配置。具体的步骤有两步。

开发一个基于注解的切面类LogAspect

package com.tgb.spring;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

/** 
 * 日志切面类 
 * 
 * @author 牛迁迁 
 */
@Aspect
// 定义切面类
public class LogAspect {
    // 定义切入点,提供一个方法,这个方法的名字就是改切入点的id
    @Pointcut("execution(* com.tgb.spring.*.*(..))")
    //@Pointcut("execution(* del*(..))")
    private void allMethod() {
    }

    // 针对指定的切入点表达式选择的切入点应用前置通知
    //@Before("allMethod()")
    public void before(JoinPoint call) {
        String className = call.getTarget().getClass().getName();
        String methodName = call.getSignature().getName();
        System.out.println("【注解-前置通知】:" + className + "类的" + methodName
                + "方法开始了");
    }

    // 访问命名切入点来应用后置通知
    //@AfterReturning("allMethod()")
    public void afterReturn() {
        System.out.println("【注解-后置通知】:方法正常结束了");
    }

    // 应用最终通知
    //@After("allMethod()")
    public void after() {
        System.out.println("【注解-最终通知】:不管方法有没有正常执行完成," + "一定会返回的");
    }

    // 应用异常抛出后通知
    //@AfterThrowing("allMethod()")
    public void afterThrowing() {
        System.out.println("【注解-异常抛出后通知】:方法执行时出异常了");
    }

    // 应用周围通知
    @Around("allMethod()")
    public Object doAround(ProceedingJoinPoint call) throws Throwable {
        Object result = null;
        this.before(call);// 相当于前置通知
        try {
            result = call.proceed();
            this.afterReturn(); // 相当于后置通知
        } catch (Throwable e) {
            this.afterThrowing(); // 相当于异常抛出后通知
            throw e;
        } finally {
            this.after(); // 相当于最终通知
        }
        return result;
    }
}

@Aspect 表示该类是一个切面类

@Pointcut 定义切入点,并且需要提供一个方法,这个方法的名字就是改切入点的id,这个方法仅作为一个标识,没有实际代码。allMethod()

@Before,@AfterReturning,@Around定义通知,方法执行的时机


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:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
    <bean id="userManager" class="com.tgb.spring.UserManagerImpl" />
    <!-- 日志切面类 -->
    <bean id="logAspectBean" class="com.tgb.spring.LogAspect" />
    <!-- 启用spring对AspectJ注解的支持 -->
    <aop:aspectj-autoproxy/>
</beans>

客户端测试

public class Client {

    public static void main(String[] args) {
        BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserManager userManager = (UserManager)factory.getBean("userManager");
        userManager.delUser(1);
    }
}

输出:

【注解-前置通知】:com.tgb.spring.UserManagerImpl类的delUser方法开始了
---------UserManagerImpl.delUser()--------
【注解-后置通知】:方法正常结束了
【注解-最终通知】:不管方法有没有正常执行完成,一定会返回的


在做注解开发的时候遇到了一个错误:

错误:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userManager' defined in class path resource [applicationContext.xml]: Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: error at ::0 can't find referenced pointcut allMethod
Caused by: java.lang.IllegalArgumentException: error at ::0 can't find referenced pointcut allMethod
        at org.aspectj.weaver.tools.PointcutParser.parsePointcutExpression(PointcutParser.java:315)
        at org.springframework.aop.aspectj.AspectJExpressionPointcut.buildPointcutExpression(AspectJExpressionPointcut.java:172)
        at org.springframework.aop.aspectj.AspectJExpressionPointcut.checkReadyToMatch(AspectJExpressionPointcut.java:162)
        at org.springframework.aop.aspectj.AspectJExpressionPointcut.getClassFilter(AspectJExpressionPointcut.java:103)
        at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:171)
        at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:231)
        at org.springframework.aop.support.AopUtils.findAdvisorsThatCanApply(AopUtils.java:256)

详细检查了代码和applicationContext.xml配置文件,发现并没有错!而其他Spring项目能运行,因此出错原因可以肯定是aspectj两个jar包的问题。网上很多说法是spring 2.0的版本中的的aspectjrt.jar和jdk不兼容,我的JDK是1.7的,于是尝试使用不同版本的aspectjrt.jar文件,发现错误仍然存在!所以不是aspectjrt.jar的问题。

于是下载了最新版本的aspectjweaver.jar并替换了原来的版本,错误成功解决了,程序正常运行。


小结

文章讲解了Spring AOP的两种实现方式,xml配置和注解方式,具体选择哪种看实际情况。要想工作量小,开发效率高就用注解方式,要想更易于扩展就选择配置方式。本文仅仅讲解了Spring的AOP是怎么回事,AOP作为一种思想一种编程方式,实现的方式是多种多样的,比如之前的拦截器,过滤器,动态代理等都是AOP的思想的实现“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

猜你喜欢

转载自blog.csdn.net/u010028869/article/details/51286731