天天看点

最简单的基于注解进行面向切面AOP开发案例1. 前言2. 功能说明3. 项目结构4. 测试执行5. 写在最后的话

目录

1. 前言

2. 功能说明

3. 项目结构

3.1. pom.xml maven配置文件

3.2. application.xml Spring配置文件

3.3. TestTarget类文件

3.4. AopEnhance类文件

3.4.1. enhance1方法的@Before注解

3.4.2 enhance2方法的@Around注解

3.4.3 enhance3方法的@AfterThrowing注解

3.5. AopTest类文件

4. 测试执行

4.1 执行 target1 方法

4.2 执行 target2 方法

4.3 执行 target3 方法

5. 写在最后的话

1. 前言

Java开发的时候,面向切面的开发还是有点儿饶头的。

本文是一个简单的面向切面开发的案例。

本文的阅读对象是面向切面开发的初学者,旨在帮助初学者理解该基础。

本文并不适合Java技术高手。

本文放弃了一切多余的技术,旨在提供最单纯的面向切面开发的案例,帮助初学者快速理解面向切面开发。

本文的技术栈: Maven + Spring 

2. 功能说明

本文创建了一个项目,项目中有 一个类 TestTarget ,还有 切面类 AopEnhance ,

TestTarget 中有3个方法: target1()  , target2()  ,  target3() 

AopEnhance类中也有3个方法: enhance1() ,  enhance2() , enhance3() 

1 TestTarget.target1方法执行, 会在之前切入 AopEnhance.enhance1方法,即 target1方法执行前会先执行 enhance1 方法。

2 TestTarget.target2方法执行,会环绕切入 AopEnhance.enhance2方法,即 target2方法执行的时候,会环绕执行 enhance2 方法。

3 TestTarget.target3方法执行,如果有异常会切入 AopEnhance.enhance3方法,即 target3方法执行的时候如果出现了异常,就会先执行 enhance2方法。

3. 项目结构

项目的结构见下图

最简单的基于注解进行面向切面AOP开发案例1. 前言2. 功能说明3. 项目结构4. 测试执行5. 写在最后的话

看上图可以知道,该项目只有 5 个文件

pom.xml maven配置文件: 不赘述

application.xml spring配置文件: 不赘述

AopTest类文件: 是为了测试的类,里面有一个 main 方法,用于执行aop 相关的测试代码。 

TestTarget 类文件: 这个类里面有3个方法,target1 target2 和 target3 ,这三个方法会被面向切面切入。

AopEnhance增强类文件: 这个类是一个切面类,里面有三个方法 ,会切入到 TestTarget类相关的方法中。

3.1. pom.xml maven配置文件

<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.wanshi</groupId>
    <artifactId>week1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>week1</name>
    <url>http://maven.apache.org</url>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.10</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.10</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>

        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.7</version>

        </dependency>


    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <!-- 配置项目中需要的资源文件,不加这个配置, application.xml不会被编译打包 -->
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
</project>
           

3.2. application.xml 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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/aop
 		http://www.springframework.org/schema/aop/spring-aop.xsd">
	
	<!-- 开启包扫描 -->
	<context:component-scan base-package="com.wanshi.week1"></context:component-scan>
	
	<!-- 开启AOP注解 -->
	<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
	
</beans>
           

这个spring的配置信息也很简单,就做了两件事:

1 context:component-scan  : 扫描哪些包下的类。这个扫描的目的就是检索 哪些类上加了 Spring支持的注解。

2 aop:aspectj-autoproxy  : 开启了AOP注解支持,配置了这个,就可以通过注解方式进行AOP注入了。

既然有注解方式的AOP注入,就一定有配置文件方式进行AOP注入的,有兴趣的朋友请自行百度,我没计划写配置文件方式实现AOP的文章。

3.3. TestTarget类文件

package com.wanshi.week1.target;

import org.springframework.stereotype.Component;

@Component(value="testTarget")
public class TestTarget {

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

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

	public void target3() throws Exception {

		String a="a12";
		
		try{
			Integer i = Integer.valueOf(a);
			System.out.println(i);
		}catch(Exception err){
			throw err;
		}
		
		
	}

}
           

这个类定义了3个方法,前两个方法不说了。

target3() 方法,尝试将字符串 "a12" 转换为整型,一定会抛出异常。 这个方法出现异常后,会执行 AopEnhance.enhance3方法。

3.4. AopEnhance类文件

package com.wanshi.week1.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AopEnhance {

	@Before("execution(* com.wanshi.week1.target.TestTarget.target1(..))")
	public void enhance1() {
		System.out.println("=== run enhance1 ");
	}

	@Around("execution(* com.wanshi.week1.target.TestTarget.target2(..))")
	public void enhance2( ProceedingJoinPoint pjp ) {

		System.out.println("=== start Around ");
		try {
			pjp.proceed();
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("=== end Around");
	}

	@AfterThrowing(pointcut="execution(* com.wanshi.week1.target.TestTarget.target3(..))", throwing="ex")
	public void enhance3(JoinPoint pjp,Throwable ex) {
		String classname = pjp.getTarget().getClass().getName();//出错类名
	    String methodname = pjp.getSignature().getName();//出错方法名
	    String exname = ex.getClass().getName();//异常名称
	    String exmsg = ex.getMessage();//异常信息
	    
	    System.out.println("=== 执行"+classname+"."+methodname);
		System.out.println("=== 出现异常:"+exname +" ; 异常信息:"+ exmsg );
	}

}
           

这个类要好好说说,整个项目就这个类重要。

3.4.1. enhance1方法的@Before注解

@Before("execution(* com.wanshi.week1.target.TestTarget.target1(..))")
public void enhance1() {
	System.out.println("=== run enhance1 ");
}
           

@Before注解的意思是当某个目标方法执行之前,会先执行当前方法。

那么目标方法是哪个呢?从该注解的参数可以分析 当执行(execution) 的方法可以匹配 * com.wanshi.week1.target.TestTarget.target1(..) 的时候,就会执行该方法。

第一个 * 表示返回值是任何类型的都行。

target1(..) 里面的两个点儿表示,有参数或者没有参数,或者任何参数类型都行。

注意:这个AOP匹配表达式还可以使用更多的通配符,具体的写法可以自行百度。

因此,写了这个注解后,当执行 target1 方法前,会先执行 enhance1 方法

3.4.2 enhance2方法的@Around注解

@Around("execution(* com.wanshi.week1.target.TestTarget.target2(..))")
	public void enhance2( ProceedingJoinPoint pjp ) {

		System.out.println("=== start Around ");
		try {
			pjp.proceed();
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("=== end Around");
	}
           

@Around注解是环绕。

环绕的意思是,当执行目标方法的时候,切面会拦截目标方法的执行,转而交由切面方法决定什么时候执行目标方法,这样切面方法就可以在目标方法执行前和执行后分别执行一些自定义的程序。

具体本例子中,在切面方法 enhance2 拦截了 target2方法的执行,然后先打印了一行 文字(=== start Around),然后又打印了一行文字(=== end Around) 。

由于切面方法可以在目标方法之前和之后分别执行一些代码,这就形成了环绕的效果,因此实现了环绕切面。

3.4.3 enhance3方法的@AfterThrowing注解

@AfterThrowing(pointcut="execution(* com.wanshi.week1.target.TestTarget.target3(..))", throwing="ex")
	public void enhance3(JoinPoint pjp,Throwable ex) {
		String classname = pjp.getTarget().getClass().getName();//出错类名
	    String methodname = pjp.getSignature().getName();//出错方法名
	    String exname = ex.getClass().getName();//异常名称
	    String exmsg = ex.getMessage();//异常信息
	    
	    System.out.println("=== 执行"+classname+"."+methodname);
		System.out.println("=== 出现异常:"+exname +" ; 异常信息:"+ exmsg );
	}
           

@AfterThrowing 当目标方法出现异常的时候,才会执行。

@AfterThrowing 有一个参数 throwing ,这个参数是异常对象的命名,必须与 enhance3方法中的形参 Throwable 的命名一致。否则会无法注入异常,这是因为所有的注入都是基于反射的,而反射是无法获取方法的参数命名的。

这个方法将拿到目标类、目标方法、以及异常对象,并打印一行异常信息。

3.5. AopTest类文件

这是一个测试类

package com.wanshi.week1;

import com.wanshi.week1.target.TestTarget;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopTest
{
    public static void main( String[] args )
    {
        // 加载spring的配置文件 application.xml 文件
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("classpath:application.xml");
        context.start();
        
        // 从spring中获取 testTarget对象
        TestTarget testTarget = (TestTarget) context.getBean("testTarget");
        
        try {
            // 执行测试,分别执行 target1 target2 target3 这三个方法
            testTarget.target1();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			
		}
    }
}
           

这个方法就是分别调用 三个方法,完成测试执行。

4. 测试执行

4.1 执行 target1 方法

执行结果如下:

=== run enhance1 
run target1
           

可以看到 在 run target1 之前,先执行了 === run enhance1 。

即在 target1 方法之前,先执行了 enhance1 方法。

4.2 执行 target2 方法

执行结果

=== start Around 
run target2
=== end Around
           

可以看到,在 run target2 之前和之后,分别打印了一行内容。

这就实现了环绕切面。

4.3 执行 target3 方法

执行结果:

=== 执行com.wanshi.week1.target.TestTarget.target3
=== 出现异常:java.lang.NumberFormatException ; 异常信息:For input string: "a12"
           

target3 代码会出现异常,然后 enhance3 方法捕获到了异常,并打印出异常信息。

5. 写在最后的话

文章就写到这里了,希望对您有帮助。