AOP常用的实现方式有两种,一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ)。
首先复习下AOP中一些比较重要的概念:
Joinpoint(连接点):程序执行时的某个特定的点,在Spring中就是某一个方法的执行 。
Pointcut(切点):说的通俗点,spring中AOP的切点就是指一些方法的集合,而这些方法是需要被增强、被代理的。一般都是按照一定的约定规则来表示的,如正则表达式等。切点是由一类连接点组成。
Advice(通知):还是说的通俗点,就是在指定切点上要干些什么。
Advisor(通知器):其实就是切点和通知的结合 。
一、基于XML配置的Spring AOP
采用声明的方式实现(在XML文件中配置),大致步骤为:配置文件中配置pointcut, 在java中用编写实际的aspect 类, 针对对切入点进行相关的业务处理。
业务接口:
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();
}
}
切面类:
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文件
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)
二、使用注解配置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)
来源:oschina
链接:https://my.oschina.net/u/2331760/blog/3020591