开发配置: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的方法。