1、实现MethodBeforeAdvice等接口
pom.xml添加spring核心依赖:
<dependencies> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> </dependencies>
在Spring中,org.springframework.aop包下有四个接口,分别是MethodBeforeAdvice(前置通知)、AfterReturningAdvice(后置通知)、MethodInterceptor(环绕通知)、ThrowsAdvice(异常通知),其中,前三个接口都有对应的实现方法,分别实现后就可以在对应的通知方法中添加功能,但是ThrowsAdvice异常通知没有实现方法,所以需要自定义一个方法,不过对方法名有规定,必须写成afterThrowing,代码如下:
定义一个切面类UserServiceAdvice:
public class UserServiceAspect implements MethodBeforeAdvice, AfterReturningAdvice, MethodInterceptor, ThrowsAdvice { /** * 后置通知 * @param o * @param method * @param objects * @param o1 * @throws Throwable */ @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println("后置通知"); } /** * 前置通知 * @param method * @param objects * @param o * @throws Throwable */ @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("前置通知。。。"); } @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("环绕通知前。。。"); Object returnVal = methodInvocation.proceed(); System.out.println("环绕通知后。。。"); return returnVal; } /** * 异常通知,参照MethodBeforeAdvice, * 该方法的方法名必须叫做afterThrowing, * method,args,target这三个参数可以省略,要么全部声明 * Exception必须保留 * @param e */ public void afterThrowing(Exception e){ System.out.println("产生了异常:"+e.getMessage()); } }
在resources目录下创建一个Spring配置文件applicationContext.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: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="userService" class="edu.nf.ch12.service.impl.UserServiceImpl" /> <!-- 切面 --> <bean id="userServiceAspect" class="edu.nf.ch12.service.aspect.UserServiceAspect" /> <!-- aop配置, --> <aop:config proxy-target-class="false"> <!-- 配置切入点,使用aspectJ的切入点表达式, 表达式语法: 1、execution(访问修饰符 方法返回值 包名.类名.方法名(参数类型)) execution是切入到方法级别的 2、within(访问修饰符 方法返回值 包名.类名) within是切入到类级别的 说明:访问修饰符可以省略, 方法返回值、包名、类名、方法名、可以使用*号进行统配, 方法参数可以指定参数的类型,也可以使用".."来标识任意类型和个数的参数 例如:execution(* edu.nf.ch12.service.impl.*.*(..)) 表示edu.nf.ch12.service.impl包下的所有类,以及任意返回值类和任意参数类型和个数的方法都会匹配--> <aop:pointcut id="myPointcut" expression="execution(* edu.nf.ch12.service.impl.UserServiceImpl.say(..))"/> <!-- 配置通知器(Advisor),其实就是切面 advice-ref引用上面定义的切面的id pointcut-ref引用上面定义的切入点的id--> <aop:advisor advice-ref="userServiceAspect" pointcut-ref="myPointcut"/> <!-- 当有多个切面,但是又不想共用一个切入点表达式的时候,那么使用pointcut属性来重新制定切入点表达式 --> <aop:advisor advice-ref="demoAspect" pointcut="execution(* edu.nf.ch12.service.impl.UserServiceImpl.run())"/> </aop:config> </beans>
2、使用AspectJ配置,不实现任何接口
(1)使用配置文件配置切面
当使用AspectJ配置时,不需要实现任何接口,因为对这些通知的方法都是在配置文件中配置和绑定的。
创建一个切面类:
public class UserServiceAspect { /** * 前置通知 * @param jp 连接点,通过这个连接点可以获取目标方法 */ public void before(JoinPoint jp){ System.out.println("前置通知,目标方法参数:" + jp.getArgs()[0]); } /** * 环绕通知 * @param pjp 连接点,可以获取目标方法参数以及方法信息以及调用目标方法等等 */ public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("环绕通知前。。。"); //获取目标方法的的Method对象 MethodSignature ms = (MethodSignature)pjp.getSignature(); Method method = ms.getMethod(); System.out.println("当前调用的目标方法:" + method.getName()); //调用目标方法 Object returnVal = pjp.proceed(); System.out.println("环绕通知后。。。"); return returnVal; } /** * 后置通知 * @param returnVal 目标方法的返回值 */ public void afterReturning(String returnVal){ System.out.println("后置通知,返回参数:" + returnVal); } /** * 异常通知 * @param e 目标方法产生的异常对象 */ public void afterThrowing(Throwable e){ System.out.println("异常通知,异常信息:" + e.getMessage()); } /** * 最终通知 */ public void after(){ System.out.println("最终通知"); } }
在resources目录中创建一个Spring配置文件applicationContext.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: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="userService" class="edu.nf.ch13.service.impl.UserServiceImpl"/> <!-- 定义切面 --> <bean id="userServiceAspect" class="edu.nf.ch13.service.aspect.UserServiceAspect"/> <!-- 配置AOP,基于AspectJ --> <aop:config> <aop:pointcut id="myPointcut" expression="execution(* edu.nf.ch13.service.impl.UserServiceImpl.*(..))"/> <!-- 装配切面,ref引用上面配置的切面的id --> <aop:aspect ref="userServiceAspect"> <!-- 装配通知,method对应通知的方法名,pointcut-ref引用上面定义的切入点的id 如果不同的通知想使用不同的切入点,那么使用pointcut属性进行自定义 --> <!-- 前置通知 --> <aop:before method="before" pointcut-ref="myPointcut"/> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="myPointcut"/> <!-- 后置通知,returning属性指定后置通知方法的参数名(参数名称要一致) --> <aop:after-returning method="afterReturning" pointcut-ref="myPointcut" returning="returnVal"/> <!-- 异常通知,throwing属性指定异常通知方法的参数名(名称要一致) --> <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="e"/> <!-- 最终通知 --> <aop:after method="after" pointcut-ref="myPointcut"/> </aop:aspect> </aop:config> </beans>
(2)使用注解和配置类配置切面
在上面的applicationContext.xml配置文件中,对基于AspectJ的aop配置完全可以使用注解来配置,
在切面类中:
/** * AspectJ提供了所有通知注解,这些注解都有一个value属性, * 属性指定的是@Pointcut注解所标注的方法名,即pointcut(), * 如:@Around("pointcut()") * 如果不同通知想使用不同的切入点那么可以直接在value属性中自定义 * 如:@Before("execution(* edu.nf.ch14.service.impl.UserServiceImpl.say())") */ @Component /** * @Aspect注解标识当前类为一个切面类 */ @Aspect public class UserServiceAspect extends AbstractAsPect { /** * 声明一个切入点,@Pointcut声明在一个方法上,在这里将这个方法放在了AbstractAspect这个类中 */ // @Pointcut("execution(* edu.nf.ch15.service.impl.UserServiceImpl.*(..))") // public void pointcut(){ // // } /** * 前置通知 * @param jp 连接点,通过这个连接点可以获取目标方法 * pointcut()指定的是切入点注解所标注的方法名 * @Before注解的value属性指定的是切入点注解所标注的方法名, * 结果不同通知想使用不同的切入点 */ @Before("execution(* edu.nf.ch15.service.impl.UserServiceImpl.say())") public void before(JoinPoint jp){ System.out.println("前置通知,目标方法参数:" + jp.getArgs()[0]); } /** * 环绕通知 * @param pjp 连接点,可以获取目标方法参数以及方法信息以及调用目标方法等等 */ @Around("pointcut()") public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("环绕通知前。。。"); //获取目标方法的的Method对象 MethodSignature ms = (MethodSignature)pjp.getSignature(); Method method = ms.getMethod(); System.out.println("当前调用的目标方法:" + method.getName()); //调用目标方法 Object returnVal = pjp.proceed(); System.out.println("环绕通知后。。。"); return returnVal; } /** * 后置通知 * @param returnVal 目标方法的返回值 */ @AfterReturning(value = "pointcut()", returning = "returnVal") public void afterReturning(String returnVal){ System.out.println("后置通知,返回参数:" + returnVal); } /** * 异常通知 * @param e 目标方法产生的异常对象 */ @AfterThrowing(value = "pointcut()", throwing = "e") public void afterThrowing(Throwable e){ System.out.println("异常通知,异常信息:" + e.getMessage()); } /** * 最终通知 */ @After("pointcut()") public void after(){ System.out.println("最终通知"); } }
创建一个配置类,可以做到0配置,
SpringConfig:
@Configuration @ComponentScan("edu.nf.ch15") /** * 启用AspectJ注解处理器,等同于xml中的<aop:aspectj-autoproxy/> */ @EnableAspectJAutoProxy public class SpringConfig { }