Beginning Spring学习笔记——第8章 Spring AOP

本文详细介绍了Spring的面向切面编程(AOP),包括接合点、通知类型、切入点定义、注解使用及AspectJ整合。通过实例展示了如何在Spring中配置和使用AOP,以及不同类型的Before、After、After Returning、After Throwing和Around通知。
摘要由CSDN通过智能技术生成

AOP


AOP(Aspect Oriented Programming)面向方面编程,关注软件系统中的横切关注点,通过分离这些横切关注点而增加模块化。
相关术语:

  • 接合点(Join-point):实际代码中的点,通过在其上执行方面向应用程序中插入额外逻辑。
  • 通知(Advice):特定接合点中由“方面”所执行的行为。
  • 切入点(Point-cut):用来选择一组接合点的表达式。
  • 目标(Target):执行流被”方面”更改的对象。
  • 编织(Weaving):将”方面”和对象结合的过程,分编译时编织、加载时编织和运行时编织。

在Spring中开始使用AOP


Spring AOP仅使用了运行时编织,是一种很动态的方法,通过运行时创建代理类实现。尽管它底层使用的AspectJ,但并没有保留后者的编译时编织和加载时编织。

方面、切入点和通知的定义

本章项目需要依赖如下:

<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>org.springframework.samples</groupId>
  <artifactId>executionTimeLoggingAspectJ</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <properties>

        <!-- Generic properties -->
        <java.version>1.6</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <!-- Spring -->
        <spring-framework.version>4.3.10.RELEASE</spring-framework.version>

        <!-- Hibernate / JPA -->
        <hibernate.version>5.2.10.Final</hibernate.version>

        <!-- Logging -->
        <logback.version>1.2.3</logback.version>
        <slf4j.version>1.7.25</slf4j.version>

        <!-- Test -->
        <junit.version>4.12</junit.version>

        <!-- AspectJ -->
        <aspectj.version>1.8.10</aspectj.version>

    </properties>

    <dependencies>
        <!-- Spring and Transactions -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>


        <!-- Logging with SLF4J & LogBack -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
            <scope>runtime</scope>
        </dependency>

        <!-- Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>


        <!-- Test Artifacts -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring-framework.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- AspectJ -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
        </dependency>

    </dependencies> 
</project>

项目目录结构如图:
目录结构
首先创建如下接口和类:

public interface MyBean {

    void sayHello();
}

@Component
public class MyBeanImpl implements MyBean {

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

public interface MyOtherBean {

    void sayHelloDelayed() throws InterruptedException;
}

@Component
public class MyOtherBeanImpl implements MyOtherBean {

    @Override
    public void sayHelloDelayed() throws InterruptedException {
        Thread.sleep(1000);
        System.out.println("Hello..!");
    }
}

如图创建了两个类,一个类可以打印一行消息,另一个类则延时打印消息,接下来创建executionTimeLoggingSpringAOP通知Bean来计算每个方法执行用时。

public class ExecutionTimeLoggingSpringAOP implements MethodBeforeAdvice, AfterReturningAdvice {

    long startTime = 0;

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        startTime = System.nanoTime();
    }

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        long elapsedTime = System.nanoTime() - startTime;
        String className = target.getClass().getCanonicalName();
        String methodName = method.getName();
        System.out.println("Execution of " + className + "#" + methodName + " ended in " + new BigDecimal(elapsedTime).divide(new BigDecimal(1000000)) + " milliseconds");
    }
}

其中before和afterReturning两个方法分别在接合点方法执行前和返回值后立即执行。
然后创建上下文配置文件定义通知和接合点:

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


    <context:component-scan base-package="com.wiley.beginningspring.ch8" />
    <context:annotation-config />

    <bean id="executionTimeLoggingSpringAop" class="com.wiley.beginningspring.ch8.aspect.ExecutionTimeLoggingSpringAOP" />

    <aop:config>
        <aop:pointcut id="executionTimeLoggingPointcut"
                      expression="execution(public * *(..))" />

        <aop:advisor id="executionTimeLoggingAdvisor"
                     advice-ref="executionTimeLoggingSpringAop"
                     pointcut-ref="executionTimeLoggingPointcut" />
    </aop:config>

</beans>

其中<aop:pointcut>标签的id属性定义了该切点的名字,expression标签定义了切点方法的类型。之后的<aop:advisor>标签的advice-ref指向通知Bean,而pointcut-ref指向刚刚定义的切点名。这样将切点和通知结合在一起。
最后就可以在Main方法中测试函数了。

public class Main {

    public static void main(String... args) throws InterruptedException {
        ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml", Main.class);
        MyBean myBean = context.getBean(MyBean.class);
        myBean.sayHello();

        MyOtherBean myOtherBean = context.getBean(MyOtherBean.class);
        myOtherBean.sayHelloDelayed();
    }
}

运行得到结果:
运行结果
可以看到两个函数的运行时间被分别打印了出来。

通知类型


Before

在实际方法调用前被调用。该方面应该实现MethodBeforeAdvice接口。

After Returning

在实际方法执行并返回值后调用。应该实现AfterReturningAdvice接口。

After Throwing

在方法抛出异常并被调用方法捕获之前执行,实现ThrowsAdvice接口。

After

不管接合点返回值或者抛出异常都会执行的通知。

Around

在接合点之前和之后执行。可以替换Before+AfterReturning:

public class ExecutionTimeLoggingWithAroundAdvice {

    public void executiontimeLogging(ProceedingJoinPoint jp) throws Throwable {
        long startTime = System.nanoTime();
        String className = jp.getTarget().getClass().getCanonicalName();
        String methodName = jp.getSignature().getName();

        jp.proceed();

        long elapsedTime = System.nanoTime() - startTime;
        System.out.println("Execution of " + className + "#" + methodName + " ended in " + new BigDecimal(elapsedTime).divide(new BigDecimal(1000000)) + " milliseconds");
    }
}

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


    <context:component-scan base-package="com.wiley.beginningspring.ch8" />
    <context:annotation-config />

    <bean id="executionTimeLoggingWithAroundAdvice" class="com.wiley.beginningspring.ch8.aspect.ExecutionTimeLoggingWithAroundAdvice" />

    <aop:config>
        <aop:aspect ref="executionTimeLoggingWithAroundAdvice">
            <aop:pointcut id="logPointCut" expression="execution(public * *(..))" />
            <aop:around pointcut-ref="logPointCut" method="executiontimeLogging" />
        </aop:aspect>
    </aop:config>

</beans>

定义切入点指示符


类型签名表达式

Within(<type name>/<package name>/<class name>)
匹配某个类型/包/类中的所有方法。

  • within(com.wiley..*):匹配com.wiley包及其子包中所有类的所有方法。
  • within(com.wiley.spring.ch8.MyService):匹配MyService类的所有方法。
  • within(MyServiceInterface+):匹配MyServiceInterface接口的所有实现类的所有方法。
  • within(com.wiley.spring.ch8.MyBaseService+):匹配MyBaseService类及其所有子类的所有方法
方法签名表达式

execution(<scope><reuturn-type><fully-qualified-class-name>.*(parameters))

  • execution(*com.wiley.spring.ch8.MyBean.*(..)):匹配MyBean中所有方法。
  • execution(public *com.wiley.spring.ch8.MyBean.*(..)):匹配MyBean中所有公共方法。
  • execution(public String com.wiley.spring.ch8.MyBean.*(..)):匹配MyBean中所有返回String的公共方法。
  • execution(public * com.wiley.spring.ch8.MyBean.*(long, ..)):匹配MyBean中第一个参数为long 的所有公共方法。
其他替代切入点指示符
  • bean(*Service):与名称后缀为Service的Bean匹配。
  • @annotation(com.wiley.spring.ch8.MarkerMethodAnnotation):匹配使用了MarkerMethodAnnotation注解的方法。
  • @within(com.wiley.spring.ch8.MarkerMethodAnnotation):within指示的类、包、接口中筛选出该注解注释的方法。
  • This(someInterface):=within(someInterface+)。

使用注解定义AOP


@Before

产生实际方法调用之前的通知方法。可以访问之际方法的传入参数。

@Component
@Aspect
@Order(100)
public class ExecutionOrderBefore {

    @Before(value = "execution(public * *(..))")
    public void before(JoinPoint joinPoint) {
        System.out.println("===1. Before Advice.");
    }
}

value属性定义了切点方法的信息,而@Order注解表明了执行优先级。越小越先执行。
除了在value属性中定义以外,还可以额外定义切点:

@PointCut
@Component
@Aspect
@Order(110)
public class ExecutionOrderBeforeWithPointCut {

    @Pointcut("execution(public * *(..))")
    public void anyPublicMethod()  {
    }

    @Before("anyPublicMethod()")
    public void beforeWithPointCut(JoinPoint joinPoint) {
        System.out.println("===1.1. Before Advice with @PointCut.");
    }
}

此时@PointCut注解的方法名可直接使用为切点名,其代指的方法在属性中写出。该注解的属性中采用其他切点指示符以及使用Boolean表达式创建更大切点:

@Component
@Aspect
@Order(110)
public class ExecutionOrderAfterWithMultiplePointCut {

    @Pointcut("execution(public * *(..))")
    public void anyPublicMethod()  {
    }

    @Pointcut("@annotation(com.wiley.beginningspring.ch8.MarkerAnnotation)")
    public void annotatedWithMarkerAnnotation()  {
    }

    @After(value = "anyPublicMethod() & annotatedWithMarkerAnnotation()")
    public void afterWithMultiplePointcut(JoinPoint joinPoint) {
        System.out.println("===5.1. After Advice with Multiple Pointcut applied on method.");
    }
}

其中MarkerAnnotation定义如下:

@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MarkerAnnotation {
}
@After

产生实际方法调用之后的通知方法。

@Component
@Aspect
@Order(150)
public class ExecutionOrderAfter {

    @After(value = "execution(public * *(..))")
    public void after(JoinPoint joinPoint) {
        System.out.println("===5. After Advice.");
    }
}
@AfterReturning

定义接合点执行完毕且成功返回值后执行的通知。可以访问返回值。

@Component
@Aspect
@Order(100)
public class ExecutionOrderAfterReturning {

    @AfterReturning(value = "execution(public * *(..))")
    public void afterReturning(JoinPoint joinPoint) {
       System.out.println("===6. After Returning Advice.");
    }
}
@AfterThrowing

定义在异常抛出后调用方法捕捉之前执行的通知方法。

@Aspect

在Bean的类级别上使用该注解将其声明为一个方面。此外还应该随之使用@Component及其派生注解类。

@Around

定义在目标方法执行之前和之后启动的通知方法。

@Component
@Aspect
@Order(200)
public class ExecutionOrderAround {

    @Around("execution(public * *(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("===2. Before proceeding part of the Around advice.");
        jp.proceed();
        System.out.println("===4. After proceeding part of the Around advice.");
    }
}
@DeclaredParents

用于动态继承接口。

最后查看注解配置类和Main类:

@Configuration
@ComponentScan(basePackages = {"com.wiley.beginningspring.ch8"})
@EnableAspectJAutoProxy
public class ApplicationConfig {
}

public class Main {

    public static void main(String... args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);

        MyBean myBean = context.getBean(MyBean.class);
        myBean.sayHello();
    }
}

运行得到结果:
运行结果
项目目录结构如下:
项目结构

AspectJ和Spring整合


几个Bean和他们的接口定义不变。采用AspectJ通知Bean:

@Component
@Aspect
public class ExecutionTimeLoggingAspectJ {

    @Around("execution(public * *(..))")
    public Object profile(ProceedingJoinPoint pjp) throws Throwable {
        long startTime = System.nanoTime();

        String className = pjp.getTarget().getClass().getCanonicalName();
        String methodName = pjp.getSignature().getName();

        Object output = pjp.proceed();
        long elapsedTime = System.nanoTime() - startTime;
        System.out.println("Execution of " + className + "#" + methodName + " ended in " + new BigDecimal(elapsedTime).divide(new BigDecimal(1000000)) + " milliseconds");

        return output;
    }
}

上下文配置文件如下,采用方面的自动代理:

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

    <context:component-scan base-package="com.wiley.beginningspring.ch8" />
    <context:annotation-config/>

    <aop:aspectj-autoproxy/>

</beans>

创建Main类来测试程序:

public class Main {

    public static void main(String... args) throws InterruptedException {
        ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml", Main.class);
        MyBean myBean = context.getBean(MyBean.class);
        myBean.sayHello();

        MyOtherBean myOtherBean = context.getBean(MyOtherBean.class);
        myOtherBean.sayHelloDelayed();
    }
}

结果还是运行时间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值