如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用。
AOP 即 Aspect Oriented Program 面向切面编程
首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。
● 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
● 所谓的周边功能,比如性能统计,日志,事务管理等等
周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面。
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。
AOP 的目的
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
AOP行话:
● 连接点(JointPoint):目标对象中,所有可以增强的方法
● 切入点(Pointcut):目标对象中,已经增强的方法
● 通知(Advice):增强的代码
● 目标对象(Target Object):被代理的对象
● 织入(Weaving):将通知应用到切入点的过程
● 代理(AOP Proxy):将通知织入到目标对象之后,形成的对象
● 切面(Aspect):切入点+通知
AOP所需的包:
aop核心包:spring-aspects 和 spring-aop
aop联盟包:springsource.org.aopalliance
aop织入包:springsource.org.aspectj.weaver
① 准备目标对象(被代理对象,被通知的对象,被增强的类对象)
package com.sikiedu.service; public interface UserService { // 增 void save(); // 删 void delete(); // 改 void update(); // 查 void find(); }
package com.sikiedu.service; public class UserServiceImpl implements UserService { @Override public void save() { System.out.println("save...run"); } @Override public void delete() { System.out.println("delete...run"); } @Override public void update() { System.out.println("update...run"); } @Override public void find() { System.out.println("find...run"); } }
② 准备通知(被增强方法的代码,想要实现功能的方法代码)
package com.sikiedu.aop; import org.aspectj.lang.ProceedingJoinPoint; /** * 自定义通知类 * * @author ASUS * */ public class MyAdvice { // before 前置通知 - 在目标方法前调用 public void befor() { System.out.println("前置通知"); } // afterReturning 成功通知(后置通知)- 在目标方法执行后,并且执行成功,如果方法出现异常则不调用 public void afterReturning() { System.out.println("成功通知"); } // around 环绕通知 - 需要我们手动调用目标方法,并且可以设置通知 public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕通知 - 前"); Object proceed = pjp.proceed(); System.out.println("环绕通知 - 后"); return proceed; } // afterThrowing 异常通知(后置通知)- 在目标方法执行出现异常的时候才会调用 public void afterThrowing() { System.out.println("异常通知"); } // after 最终通知(后置通知)- 在目标方法后调用,无论是否出现异常都会执行,finally public void after() { System.out.println("最终通知"); } }
③ 配置 applicationContext.xml
1 - 导入aop(约束)命名空间
2 - 配置目标对象
3 - 配置通知对象
4 - 配置将通知织入目标对象
<?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-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 配置目标对象 --> <bean name="userService" class="com.sikiedu.service.UserServiceImpl"> </bean> <!-- 配置通知对象 --> <bean name="myAdvice" class="com.sikiedu.aop.MyAdvice"></bean> <!-- 织入 - 将通知织入到目标对象中 --> <aop:config> <!-- 切入点 1、expression:切入点表达式,可以配置要增强的方法 ① public void com.sikiedu.service.UserServiceImpl.save() - 增强单一方法save,必须为无参、public类型、返回值为void ② * com.sikiedu.service.*.*(..) - 增强service包下所有类的所有方法 2、id:唯一标识 --> <aop:pointcut expression="execution(* com.sikiedu.service.*.*(..))" id="servicePc" /> <!-- 切面:通知+切入点--> <aop:aspect ref="myAdvice"> <!-- 通知类型 --> <!-- 前置通知 --> <aop:before method="befor" pointcut-ref="servicePc"/> <!-- 成功通知 --> <aop:after-returning method="afterReturning" pointcut-ref="servicePc"/> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="servicePc"/> <!-- 异常通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="servicePc"/> <!-- 最终通知 --> <aop:after method="after" pointcut-ref="servicePc"/> </aop:aspect> </aop:config> </beans>
④ 测试
package com.sikiedu.test; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.sikiedu.service.UserService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class AopTest { @Resource(name = "userService") UserService us; @Test public void test1() { us.delete(); } }
运行结果:
- 非异常:,异常:
① 配置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-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 配置目标对象 --> <bean name="userService" class="com.sikiedu.service.UserServiceImpl"> </bean> <!-- 配置通知对象 --> <bean name="myAdvice" class="com.sikiedu.aop.MyAdvice"></bean> <!-- 开启使用注解完成织入 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
② 书写通知类
@Aspect:指定切面类
@JoinPoint:作为函数的参数传入切面方法,可以得到目标方法的相关信息
声明一个切入点:@Pointcut("execution(返回值 全类名.方法名(参数))")
package com.sikiedu.aop; 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; /** * 自定义通知类 * * @author ASUS * */ @Aspect // 通知类 - 使用Aspect表示是一个通知类 public class MyAdvice { // 声明一个切入点 - servicePc为切入点名称 @Pointcut("execution(* com.sikiedu.service.*.*(..))") public void servicePc() { }; @Before("MyAdvice.servicePc()") // before 前置通知 - 在目标方法前调用 public void befor() { System.out.println("前置通知"); } @AfterReturning("MyAdvice.servicePc()") // afterReturning 成功通知(后置通知)- 在目标方法执行后,并且执行成功,如果方法出现异常则不调用 public void afterReturning() { System.out.println("成功通知"); } @Around("MyAdvice.servicePc()") // around 环绕通知 - 需要我们手动调用目标方法,并且可以设置通知。 public void around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕通知 - 前"); pjp.proceed(); System.out.println("环绕通知 - 后"); } @AfterThrowing("MyAdvice.servicePc()") // afterThrowing 异常通知(后置通知)- 在目标方法执行出现异常的时候才会调用。 public void afterThrowing() { System.out.println("异常通知"); } @After("MyAdvice.servicePc()") // after 最终通知(后置通知)- 在目标方法后调用,无论是否出现异常都会执行。 public void after() { System.out.println("最终通知"); } }
● 注意环绕通知:需要我们手动调用目标方法
proceed()表示对拦截的方法进行放行,若注释proceed()则不会执行被AOP匹配的方法。
public void around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕通知 - 前") pjp.proceed(); System.out.println("环绕通知 - 后") }
③ 测试:
package com.sikiedu.test; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.sikiedu.service.UserService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext2.xml") public class AopTest { @Resource(name = "userService") UserService us; @Test public void test1() { us.delete(); } }
运行结果:
- 非异常:,异常:
① 前置通知(前置通知):目标方法运行之前调用
@Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可
② 成功通知(后置通知):目标方法运行之后调用(如果出现异常不调用) @AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值
④ 异常拦截通知(后置通知):如果出现异常,就会调用 @AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象
④ 最终通知(后置通知):目标方法运行之后调用(无论是否出现异常都会调用) @After:在目标方法完成之后做增强,无论目标方法是否成功完成。@After可以指定一个切入点表达式
③ 环绕通知:目标方法之前和之后都调用 - 需要我们手动调用目标方法,并且可以设置通知 @Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
原文:https://www.cnblogs.com/Dm920/p/12076090.html