AOP经常使用的实现方式有两种,一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ)。java
首先复习下AOP中一些比较重要的概念:正则表达式
Joinpoint(链接点):程序执行时的某个特定的点,在Spring中就是某一个方法的执行 。
Pointcut(切点):说的通俗点,spring中AOP的切点就是指一些方法的集合,而这些方法是须要被加强、被代理的。通常都是按照必定的约定规则来表示的,如正则表达式等。切点是由一类链接点组成。
Advice(通知):仍是说的通俗点,就是在指定切点上要干些什么。
Advisor(通知器):其实就是切点和通知的结合 。spring
1、基于XML配置的Spring AOPexpress
采用声明的方式实现(在XML文件中配置),大体步骤为:配置文件中配置pointcut, 在java中用编写实际的aspect 类, 针对对切入点进行相关的业务处理。springboot
业务接口:ide
package com.springboottime.time.service; public interface AdviceService { /*查找用户*/ public String findUser(); /*添加用户*/ public void addUser(); }
业务实现:测试
package com.springboottime.time.service.serviceImpl; import com.springboottime.time.service.AdviceService; import lombok.Data; @Data public class AdviceServiceImpl implements AdviceService { private String name; @Override public String findUser() { System.out.println("***************执行业务方法findUser,查找的用户名字为:"+name+"****************"); return name; } @Override public void addUser() { System.out.println("***************执行业务方法addUser****************"); throw new RuntimeException(); } }
切面类:spa
package com.springboottime.time.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; public class AopAspect { /** * 前置通知:目标方法调用以前执行的代码 * @param jp */ public void doBefore(JoinPoint jp){ System.out.println("===========执行前置通知============"); } /** * 后置返回通知:目标方法正常结束后执行的代码 * 返回通知是能够访问到目标方法的返回值的 * @param jp * @param result */ public void doAfterReturning(JoinPoint jp,String result){ System.out.println("===========执行后置通知============"); System.out.println("返回值result==================="+result); } /** * 最终通知:目标方法调用以后执行的代码(不管目标方法是否出现异常均执行) * 由于方法可能会出现异常,因此不能返回方法的返回值 * @param jp */ public void doAfter(JoinPoint jp){ System.out.println("===========执行最终通知============"); } /** * * 异常通知:目标方法抛出异常时执行的代码 * 在目标方法执行的时候,若是抛出异常,当即进入此方法 * 能够访问到异常对象 * @param jp * @param ex */ public void doAfterThrowing(JoinPoint jp,Exception ex){ System.out.println("===========执行异常通知============"); } /** * 环绕通知:目标方法调用先后执行的代码,能够在方法调用先后完成自定义的行为。 * 包围一个链接点(join point)的通知。它会在切入点方法执行前执行同时方法结束也会执行对应的部分。 * 主要是调用proceed()方法来执行切入点方法,来做为环绕通知先后方法的分水岭。 * * 环绕通知相似于动态代理的全过程:ProceedingJoinPoint类型的参数能够决定是否执行目标方法。 * 并且环绕通知必须有返回值,返回值即为目标方法的返回值 * @param pjp * @return * @throws Throwable */ public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("======执行环绕通知开始========="); // 调用方法的参数 Object[] args = pjp.getArgs(); // 调用的方法名 String method = pjp.getSignature().getName(); // 获取目标对象 Object target = pjp.getTarget(); // 执行完方法的返回值 // 调用proceed()方法,就会触发切入点方法执行 Object result=pjp.proceed(); //若是调用pjp.proceed()执行业务方法的时候抛出异常,那么下面的代码将不会执行 System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result); System.out.println("======执行环绕通知结束========="); return result; } }
Spring的AOP配置:spring-aop.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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 声明一个业务类 --> <bean id="userManager" class="com.springboottime.time.service.serviceImpl.AdviceServiceImpl"> <property name="name" value="lixiaoxi"></property> </bean> <!-- 声明通知类 --> <bean id="aspectBean" class="com.springboottime.time.aop.AopAspect" /> <aop:config> <aop:aspect ref="aspectBean"> <aop:pointcut id="pointcut" expression="execution(* com.springboottime.time.service.serviceImpl.AdviceServiceImpl..*(..))"/> <aop:before method="doBefore" pointcut-ref="pointcut"/> <aop:after-returning method="doAfterReturning" pointcut-ref="pointcut" returning="result"/> <aop:after method="doAfter" pointcut-ref="pointcut" /> <aop:around method="doAround" pointcut-ref="pointcut"/> <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="ex"/> </aop:aspect> </aop:config> </beans>
springboot启动类设置:注意要引入xml文件code
package com.springboottime.time; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ImportResource; @ImportResource("classpath:spring-aop.xml") //@ImportResource("classpath:spring-aop-aspectJ.xml") @SpringBootApplication public class TimeApplication { public static void main(String[] args) { SpringApplication.run(TimeApplication.class, args); } }
测试类:
package com.springboottime.time; import com.springboottime.time.service.AdviceService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class TimeApplicationTests { @Autowired private AdviceService adviceService; @Test public void contextLoads() { } @Test public void test(){ String user = adviceService.findUser(); System.out.println("<><><><><><><><><><><><><>"); adviceService.addUser(); } }
测试结果:
===========执行前置通知============ ======执行环绕通知开始========= ***************执行业务方法findUser,查找的用户名字为:lixiaoxi**************** 输出,方法名:findUser;目标对象:AdviceServiceImpl(name=lixiaoxi);返回值:lixiaoxi ======执行环绕通知结束========= ===========执行最终通知============ ===========执行后置通知============ 返回值result===================lixiaoxi <><><><><><><><><><><><><> ===========执行前置通知============ ======执行环绕通知开始========= ***************执行业务方法addUser**************** ===========执行异常通知============ ===========执行最终通知============ java.lang.RuntimeException at com.springboottime.time.service.serviceImpl.AdviceServiceImpl.addUser(AdviceServiceImpl.java:20)
2、使用注解配置AOP
采用注解来作aop, 主要是将写在spring 配置文件中的链接点写到注解里面。
业务接口和业务实现与上边同样,具体切面类以下:
package com.springboottime.time.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; @Aspect public class AopAspectJ { /** * 必须为final String类型的,注解里要使用的变量只能是静态常量类型的 */ public static final String EDP="execution(* com.springboottime.time.service.serviceImpl.AdviceServiceImpl..*(..))"; /** * 切面的前置方法 即方法执行前拦截到的方法 * 在目标方法执行以前的通知 * @param jp */ @Before(EDP) public void doBefore(JoinPoint jp){ System.out.println("=========AopAspectJ执行前置通知=========="); } /** * 在方法正常执行经过以后执行的通知叫作返回通知 * 能够返回到方法的返回值 在注解后加入returning * @param jp * @param result */ @AfterReturning(value=EDP,returning="result") public void doAfterReturning(JoinPoint jp,String result){ System.out.println("===========AopAspectJ执行后置通知============"); } /** * 最终通知:目标方法调用以后执行的通知(不管目标方法是否出现异常均执行) * @param jp */ @After(value=EDP) public void doAfter(JoinPoint jp){ System.out.println("===========AopAspectJ执行最终通知============"); } /** * 在目标方法非正常执行完成, 抛出异常的时候会走此方法 * @param jp * @param ex */ @AfterThrowing(value=EDP,throwing="ex") public void doAfterThrowing(JoinPoint jp,Exception ex) { System.out.println("===========AopAspectJ执行异常通知============"); } /** * 环绕通知:目标方法调用先后执行的通知,能够在方法调用先后完成自定义的行为。 * @param pjp * @return * @throws Throwable */ @Around(EDP) public Object doAround(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("======AopAspectJ执行环绕通知开始========="); // 调用方法的参数 Object[] args = pjp.getArgs(); // 调用的方法名 String method = pjp.getSignature().getName(); // 获取目标对象 Object target = pjp.getTarget(); // 执行完方法的返回值 // 调用proceed()方法,就会触发切入点方法执行 Object result=pjp.proceed(); System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result); System.out.println("======AopAspectJ执行环绕通知结束========="); return result; } }
spring的配置:spring-aop-aspectJ.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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 声明spring对@AspectJ的支持 --> <aop:aspectj-autoproxy/> <!-- 声明一个业务类 --> <bean id="userManager" class="com.springboottime.time.service.serviceImpl.AdviceServiceImpl"> <property name="name" value="lixiaoxi"></property> </bean> <!-- 声明通知类 --> <bean id="aspectBean" class="com.springboottime.time.aop.AopAspectJ" /> </beans>
springboot启动类配置:
package com.springboottime.time; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ImportResource; //@ImportResource("classpath:spring-aop.xml") @ImportResource("classpath:spring-aop-aspectJ.xml") @SpringBootApplication public class TimeApplication { public static void main(String[] args) { SpringApplication.run(TimeApplication.class, args); } }
测试结果:
======AopAspectJ执行环绕通知开始========= =========AopAspectJ执行前置通知========== ***************执行业务方法findUser,查找的用户名字为:lixiaoxi**************** 输出,方法名:findUser;目标对象:AdviceServiceImpl(name=lixiaoxi);返回值:lixiaoxi ======AopAspectJ执行环绕通知结束========= ===========AopAspectJ执行最终通知============ ===========AopAspectJ执行后置通知============ <><><><><><><><><><><><><> ======AopAspectJ执行环绕通知开始========= =========AopAspectJ执行前置通知========== ***************执行业务方法addUser**************** ===========AopAspectJ执行最终通知============ ===========AopAspectJ执行异常通知============ java.lang.RuntimeException at com.springboottime.time.service.serviceImpl.AdviceServiceImpl.addUser(AdviceServiceImpl.java:20)