Spring——AOP之Spring 2.0 中的配置

Posted KLeonard

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring——AOP之Spring 2.0 中的配置相关的知识,希望对你有一定的参考价值。

Spring 2.0 AOP

Spring2.0提供了一种更简便也更强大的方式来编写切面,可以通过基于schema的方式,也可以通过@AspectJ注解的方式,这两种方式都提供了完整的AspectJ切入点语言中的通知和使用方法,但是依然使用的是Spring AOP的织入方式,也就是通过代理的方式进行织入(不同于AspectJ在编译期织入)。

Spring 2.0 AOP完全兼容Spring 1.2 AOP。

0.使用@AspectJ注解方式进行AOP开发

@AspectJ注解方式是AspectJ项目在第5个版本中引进的。Spring使用了AspectJ5中的相同的注解,通过使用AspectJ提供的一个库来进行切入点的解析和匹配。但AOP仍然是采用代理的方式进行织入的,也就是Spring AOP不依赖于AspectJ的编译器和织入器。

0.0 添加@AspectJ支持

要在Spring中使用@AspectJ注解,需要在Spring中添加基于@AspectJ切面的支持,并且配置自动代理。自动代理意味着如果Spring判断出一个Bean被一个或者更多的切面通知了,它就会自动生成该Bean的代理,来拦截方法的执行,确保通知按照我们所需被执行。

为了添加@AspectJ支持,我们需要将AspectJ中的Jar包aspectjweaver.jar引入项目,该Jar包在Aspect项目目录下的lib目录下,可以在Maven中央仓库中找到。

配置自动代理一般通过在Spring的配置文件中进行配置,添加如下元素:

<aop:aspectj-autoproxy/>

当然,添加这个配置,需要导入支持aop命名空间的标签。

0.1 一个示例

下面通过一个示例来介绍如何在Spring中通过注解的方式来进行AOP开发。

首先我们创建一个简单的Maven项目,引入Spring依赖,并且引入上面所说的aspectjweaver.jar依赖。这些基本的库文件已经满足我们所需了。如下是pom.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.gavin</groupId>
    <artifactId>SpringAop</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.1</version>
        </dependency>

    </dependencies>
</project>

然后我们添加Spring的配置文件spring-config.xml,并且导入支持aop命名空间的标签,用该标签配置自动代理:

接下来我们创建业务类com.gavin.service.LoginService,如下:

package com.gavin.service;

public class LoginService 
    public String login(String username, String password) 
        System.out.println(username + "正在登录,他的密码是:" + password);
        return "success";
    

可以看到我们在LoginService中写了一个login方法,该方法有两个String参数,并且有一个String返回值。

然后我们在spring-config.xml注入该Bean,此时的配置文件为:

<?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">

    <aop:aspectj-autoproxy/>

    <bean id="loginService" class="com.gavin.service.LoginService"/>
</beans>

接着创建一个测试类com.gavin.test.Main,进行简单的测试:

package com.gavin.test;

import com.gavin.service.LoginService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main 
    public static void main(String[] args) 
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
        LoginService loginService = applicationContext.getBean("loginService", LoginService.class);
        loginService.login("Gavin", "123");
    

运行结果为:

运行结果符合我们的预期。接下来我们使用注解的方法来创建一个切面com.gavin.aspect.LoginAdvice,如下:

package com.gavin.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class LoginAdvice 
    @Pointcut("execution(* com.gavin.service.LoginService.login(..))")
    public void loginPointcut()

    @Before("loginPointcut() && args(username, password)")
    public void beforeLogin(JoinPoint thisJoinPoint, String username, String password) 
        System.out.println("thisJoinPoint : " + thisJoinPoint);
        System.out.println("在登录之前记录的日志...");
        System.out.println("username = " + username);
        System.out.println("password = " + password);
        System.out.println();
    

    @AfterReturning(pointcut = "loginPointcut()", returning = "returnVal")
    public void afterLogin(String returnVal) 
        System.out.println();
        System.out.println("返回值是:" + returnVal);
    

我们使用@Aspect注解该类,表示其是一个切面。在切面中,我们定义了名字为loginPointcut的切入点,并为该切入点定义了前置通知和后置通知。在前置通知中,通过args原生切入点获取了方法的参数。在后置通知中,我们获取了方法的返回值。

最后我们需要在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">

    <aop:aspectj-autoproxy/>

    <bean id="loginService" class="com.gavin.service.LoginService"/>
    <bean class="com.gavin.aspect.LoginAdvice"/>
</beans>

再次运行程序,此时的运行结果为:

0.2 需要注意的地方

通过上例可以看出,Spring AOP使用注解的语法与前面所介绍的AspectJ中的注解语法是一模一样的。

Spring AOP 支持AspectJ中的大部分语法,也支持一些原生的切入点,比如thisargs等,但是有一些是不支持的,比如callgetsetpreinitializationstaticinitializationinitializationhandlerwithincodecflowcflowbelowif@this@withincode等。在Spring AOP中使用这些原生切入点将会抛出IllegalArgumentException异常。

那么总结一下,Spring AOP支持的切入点标识符有:executionwithinthistargetargs@annotation以及@target@args@within。这些都是AspectJ中所具有的,除此之外,Spring AOP加入了一个额外的切入点标识符:bean(idOrNameOfBean),它用来匹配特定名称的Bean对象的执行方法。

对于这些标识符,我们只要关心一些常用的即可。

0.3 使用自定义注解作为execution的表达式

上例我们使用常规的execution表达式来声明切入点,除此之外,也可以使用自定义注解,通过注解的方法来声明切入点,只有添加了注解的方法才能被增强。

我们在上个例子的基础上进行修改,首先我们自定义注解UserLogin,如下:

package com.gavin.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLogin 

接着我们对业务类需要增强的方法login添加我们自定义的注解:

package com.gavin.service;

import com.gavin.annotation.UserLogin;

public class LoginService 

    @UserLogin
    public String login(String username, String password) 
        System.out.println(username + "正在登录,他的密码是:" + password);
        return "success";
    

此时修改切面,我们只需要修改切入点表达式,使用@annotation,如下:

package com.gavin.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class LoginAdvice 
    @Pointcut("@annotation(com.gavin.annotation.UserLogin)")
    public void loginPointcut()

    @Before("loginPointcut() && args(username, password)")
    public void beforeLogin(JoinPoint thisJoinPoint, String username, String password) 
        System.out.println("thisJoinPoint : " + thisJoinPoint);
        System.out.println("在登录之前记录的日志...");
        System.out.println("username = " + username);
        System.out.println("password = " + password);
        System.out.println();
    

    @AfterReturning(pointcut = "loginPointcut()", returning = "returnVal")
    public void afterLogin(String returnVal) 
        System.out.println();
        System.out.println("返回值是:" + returnVal);
    

可以看到,我们只修改了切入点声明,其他的都没有更改。此时运行程序会得到一样的结果。

1.使用基于schema的方法进行AOP开发

Spring 2.0 也支持通过schema的方式进行AOP的配置。我们仍然通过上述例子来进行介绍。

这里我们在test2包下进行开发:

代码与上面的例子基本类似,业务类LoginService如下:

package com.gavin.test2;

public class LoginService 
    public String login(String username, String password) 
        System.out.println(username + "正在登录,他的密码是:" + password);
        return "success";
    

切面类也是一样的,只不过我们删除配置AOP的注解:

package com.gavin.test2;

import org.aspectj.lang.JoinPoint;

public class LoginAdvice 

    public void beforeLogin(JoinPoint thisJoinPoint, String username, String password) 
        System.out.println("thisJoinPoint : " + thisJoinPoint);
        System.out.println("Before 登录 记录的日志...");
        System.out.println("username = " + username);
        System.out.println("password = " + password);
        System.out.println();
    

    public void afterLogin(String returnVal) 
        System.out.println();
        System.out.println("返回值是:" + returnVal);
    

此时我们在Spring的配置文件spring-config.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: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="loginService" class="com.gavin.test2.LoginService"/>
    <!--切面类-->
    <bean id="loginAdvice" class="com.gavin.test2.LoginAdvice"/>

    <!--AOP配置-->
    <aop:config>
        <aop:aspect id="loginAdvice" ref="loginAdvice">
            <aop:pointcut id="loginPointcut" expression="execution(* com.gavin.test2.LoginService.login(..)) and args(username, password)"/>
            <aop:before method="beforeLogin" arg-names="thisJoinPoint, username, password" pointcut-ref="loginPointcut"/>
            <aop:after-returning method="afterLogin" arg-names="returnVal" returning="returnVal" pointcut="execution(* com.gavin.test2.LoginService.login(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

其实与通过注解的方式配置是一样的,只不过我们把注解里面的东西都放进了XML文件中。这种方式没有注解的方法简洁。

主方法也没有改变,如下:

package com.gavin.test2;

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

public class Main 
    public static void main(String[] args) 
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
        LoginService loginService = applicationContext.getBean("loginService", LoginService.class);
        loginService.login("Gavin", "123");
    

运行之后得到运行结果,可以看到运行结果与上面通过注解的方式是完全一样的:

以上是关于Spring——AOP之Spring 2.0 中的配置的主要内容,如果未能解决你的问题,请参考以下文章

spring aop 问题,不能进入切点

Spring——AOP之Spring1中的配置

Java实战之03Spring-05Spring中的事务控制(基于AOP)

每日一学之认识Spring中的AOP

Spring繁华的AOP王国---第五讲之应用案例和扩展

Spring核心之AOP