Spring 使用AspectJ 实现 AOP(基于注解)

开发配置:Eclipse + jdk 1.8 + Tomcat 7.0

Spring AOP自身也有一个实现aop的框架,但这里使用的是AspectJ来实现aop。

使用AspectJ来实现aop有两种方法,一种是注解的方式,另一种是xml的方式,这里说的是基于注解的方式。

AOP有五种通知,分别是

@Befor:前置通知,在方法执行之前执行
@After:后置通知,在方法执行之后执行
@AfterRunning:返回通知,在方法返回结果之后执行
@AfterThrowing:异常通知,在方法抛出异常之后执行
@Around:环绕通知,围绕着方法执行

项目结构:


第一个红框是源码,第二个红框是spring的配置文件,第三个红框是jar包。

一、需要的jar包:

这三个是AspectJ的jar包:
aopalliance-1.0.jar
aspectjweaver-1.9.0.jar
spring-aspects-4.3.8.RELEASE.jar

这些事spring的核心包:
commons-logging-1.2.jar
spring-aop-4.3.8.RELEASE.jar
spring-beans-4.3.8.RELEASE.jar
spring-context-4.3.8.RELEASE.jar
spring-core-4.3.8.RELEASE.jar

spring-expression-4.3.8.RELEASE.jar

二、bean.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://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 
    http://www.springframework.org/schema/tx       
    http://www.springframework.org/schema/tx/spring-tx.xsd ">

	<!-- 扫描com.qw.apo.impl包及其子包下面的所有的类的注解 -->
	<context:component-scan base-package="com.qw.aop.impl"/>
	
	<!-- 使 AspjectJ 的注解其作用:自动为匹配的类生成代理对象 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	
</beans>

三、接口类、接口实现类、切面类、测试类

接口类:AtithmeticCalculator

package com.qw.aop.impl;

public interface AtithmeticCalculator {
	
    int add(int i,int j);
	
  int sub(int i,int j);
}

接口实现类:

package com.qw.aop.impl;

import org.springframework.stereotype.Component;

@Component
public class AtithmeticCalculatorImpl implements AtithmeticCalculator {

	@Override
	public int add(int i, int j) {
		int result = i + j;
		return result;
	}

	@Override
	public int sub(int i, int j) {
		int result = i - j;
		return result;
	}

}

切面类:

package com.qw.aop.impl;

import java.util.Arrays;
import java.util.List;

import org.aspectj.lang.JoinPoint;
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;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 申明这个类是切面,需要两个注解,切面可以有多个通知
 * @Component:把该类放入到ioc容器中
 * @Aspect:申明该类为切面
 * @Order :指定切面的优先级,数字越小,优先级越高。
 * @author 12873
 *
 */
@Order(1)
@Aspect
@Component
public class LoggingAspect {
	
	/**
	 * 定义一个方法,用于声明切入点表达式,一般的,该方法中再不需要填入其他的代码
	 * 这个就相当于定义了一个公共的常亮,其他的切面的类也可以使用,只要把类名加上即可,如果不在同一个包,报名也要加上。
	 * 如:同包的切面类引用:LoggingAspect.declareJointPointExpression()
	 */
	@Pointcut("execution(public int com.qw.aop.impl.AtithmeticCalculator.add(int, int))")
	public void declareJointPointExpression(){}
	
	/**
	 * @Before:申明该方法是前置通知:在目标方法开始之前执行
	 * execution:执行条件,当有方法匹配这里面的条件之后,就执行该方法
	 * JoinPoint:获取执行方法中的各种参数
	 */
	@Before("declareJointPointExpression()")//这个里面是引用了上面的那个方法
	public void beforeMethod(JoinPoint joinPoint){
		//执行的方法名
		String methodName = joinPoint.getSignature().getName();
		//执行的方法的参数
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		
		//执行的方法所在的类的名称
		String className = joinPoint.getTarget().getClass().getName();
		
		System.out.println("前置通知,执行的方法名:"+ methodName + ",方法的参数:"+ args + ",方法所在的类名:" + className);
	}
	
	
	//后置通知:在目标方法执行后(无论是否发生异常),执行的通知
	//在后置通知中还不能访问目标方法执行的结果
	@After("execution(public int com.qw.aop.impl.AtithmeticCalculator.add(int, int))")
	public void afterMethod(JoinPoint joinPoint){
		//执行的方法名
		String methodName = joinPoint.getSignature().getName();
		//执行的方法的参数
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		
		System.out.println("后置通知,执行的方法名:"+ methodName + ",方法的参数:"+ args);
	}
	
	/**
	 * 返回通知:
	 * 1、在方法正常结束后执行的通知
	 * 2、返回通知是可以访问到方法的返回值的
	 * @param joinPoint
	 */
	@AfterReturning(value="execution(public int com.qw.aop.impl.AtithmeticCalculator.add(int, int))",returning="result")
	public void afterRunning(JoinPoint joinPoint,Object result){
		//执行的方法名
		String methodName = joinPoint.getSignature().getName();
		//执行的方法的参数
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		
		System.out.println("返回通知,执行的方法名:"+ methodName + ",方法的参数:"+ args +",返回值是:"+result);
	}
	
	/**
	 * 异常通知,可以访问到目标方法的异常对象,并且可以指定出现特定异常时才执行该通知代码。
	 * @param joinPoint
	 */
	@AfterThrowing(value="execution(public int com.qw.aop.impl.AtithmeticCalculator.*(int, int))",throwing="ex")
	public void afterThrowing(JoinPoint joinPoint,Exception ex){
		//执行的方法名
		String methodName = joinPoint.getSignature().getName();
		//执行的方法的参数
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		
		System.out.println("异常通知,执行的方法名:"+ methodName + ",方法的参数:"+ args + ",异常:"+ex);
	}
	
	/**
	 * 环绕通知
	 * 环绕通知需要携带ProceedingJoinPoint 类型参数
	 * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint 类型的参数可以决定是否执行目标方法
	 * 且环绕通知必须有返回值,返回值即为目标方法的返回值
	 */
	@Around("execution(public int com.qw.aop.impl.AtithmeticCalculator.*(int, int))")
	public Object around(ProceedingJoinPoint pjd){
		Object result = null;
		
		try {
			//前置通知
			System.out.println("前置通知:"+pjd.getSignature().getName());
			//执行目标方法
			result = pjd.proceed();
			//返回通知
			System.out.println("返回通知:"+pjd.getSignature().getName());
		} catch (Throwable e) {
			//异常通知
			System.out.println("异常通知:"+pjd.getSignature().getName() + ",异常:" + e);
			throw new RuntimeException(e);
		}
		//后置通知
		System.out.println("后置通知:"+pjd.getSignature().getName());
		
		return result;
	}
}

测试类:

package com.qw.aop.impl;

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

public class Main {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
		AtithmeticCalculator calculator = context.getBean(AtithmeticCalculator.class);
		int result = calculator.add(1, 4);
		System.out.println("result:"+result);
		result = calculator.sub(1, 4);
		System.out.println("result:"+result);
	}
}

执行结果:

四月 17, 2018 11:11:05 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3f91beef: startup date [Tue Apr 17 23:11:05 CST 2018]; root of context hierarchy
四月 17, 2018 11:11:05 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [bean.xml]
方法执行之前执行,执行的方法名:add,方法的参数:[1, 4]
result:5
result:-3

第一个add的方法执行了切面类里面的前置通知方法了,后面的sub方法没有执行,是因为add的方法匹配了切面类里面的前置通知方法,而sub方法没有匹配到。


源码资源:https://download.csdn.net/download/java_xuetu/10358576

2018-04-18补充,源码里面没有的一点小补充,一点补充:

1、execution切入点表达式

@Before("execution(public int com.qw.aop.impl.AtithmeticCalculator.add(int, int))")
其中execution是切入点表达式。
public int com.qw.aop.impl.AtithmeticCalculator.add(int, int) 是表达式的内容
1) public:匹配所有的目标类的public方法,不写则匹配所有的访问权限
2) int:方法的返回值类型,* 代表所有的类型
3) com.qw.aop.impl.AtithmeticCalculator.add:匹配这个包下面的这个类的add方法,可以在其中任意位置替换成 * ,如:com.qw.aop.impl.AtithmeticCalculator.* :匹配这个包下面这个类的所有方法。
4) (int, int) :匹配的参数,必须是两个int的参数才行,如果想要匹配任意参数,可以写成  (..)

比如:execution(public int com.qw.aop.impl.AtithmeticCalculator.*(int, int))
匹配AtithmeticCalculator类下面的任意参数为两个int的方法。

猜你喜欢

转载自blog.csdn.net/java_xuetu/article/details/79982462