# 关于Spring框架的总结(三、Spring AOP)
3.Spring AOP
Spring AOP
3.1.Spring AOP的基本概念
3.2.动态代理
3.3.AOP的常见术语
3.4.基于XML配置开发
3.5.基于注解开发
3.1.Spring AOP的基本概念
AOP (Aspect- Oriented Programming) 即面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。它与OOP (Object-Otiented
Programming,面向对象编程)相辅相成, 提供了与OOP不同的抽象软件结构的视角。在OOP中,以类作为程序的基本单元,而AOP中的基本单元是Aspect (切面)。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
3.2.动态代理
动态代理常用的有两种方式
(1)、JDK动态代理(基于接口的动态代理)
1.创建接口以及实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public interface IActor {
public void basicAct(float money);
public void dangerAct(float money); }
public class Actor implements IActor{ public void basicAct(float money){ System.out.println("拿到钱,开始基本的表演:"+money); } public void dangerAct(float money){ System.out.println("拿到钱,开始危险的表演:"+money); } }
|
2.创建代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| public class Client { public static void main(String[] args) {
final Actor actor = new Actor();
IActor proxyActor = (IActor) Proxy.newProxyInstance( actor.getClass().getClassLoader(), actor.getClass().getInterfaces(), new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); Float money = (Float) args[0]; Object rtValue = null;
if("basicAct".equals(name)){
if(money > 2000){
rtValue = method.invoke(actor, money/2); } } if("dangerAct".equals(name)){
if(money > 5000){
rtValue = method.invoke(actor, money/2); } } return rtValue; } });
proxyActor.basicAct(8000f); proxyActor.dangerAct(50000f); } }
|
(2)、CGLIB动态代理(基于子类的动态代理)
1.创建目标类
1 2 3 4 5 6 7 8
| public class Actor{ public void basicAct(float money){ System.out.println("拿到钱,开始基本的表演:"+money); } public void dangerAct(float money){ System.out.println("拿到钱,开始危险的表演:"+money); } }
|
2.创建代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public class Client {
public static void main(String[] args) { final Actor actor = new Actor(); Actor cglibActor = (Actor) Enhancer.create(actor.getClass(), new MethodInterceptor() {
@Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { String name = method.getName(); Float money = (Float) args[0]; Object rtValue = null; if("basicAct".equals(name)){
if(money > 2000){ rtValue = method.invoke(actor, money/2); } } if("dangerAct".equals(name)){
if(money > 5000){ rtValue = method.invoke(actor, money/2); } } return rtValue; } }); cglibActor.basicAct(10000); cglibActor.dangerAct(100000); } }
|
3.3.AOP的常见术语
➊切面
切面(Aspect) 是指封装横切到系统功能(例如事务处理)的类。
❷连接点
连接点(Joinpoint)是指程序运行中的一些时间点, 例如方法的调用或异常的抛出。
❸切入点
切入点(Poineu)是指需要处理的连接点。在Spring AOP中,所有的方法执行都是连接点,而切入点是一个描述信息, 它修饰的是连接点。
❹通知
通知(Advice) 是由切面添加到特定的连接点(满足切入点规则)的一段代码,即在定义好的切入点处所要执行的程序代码,可以将其理解为切面开启后切面的方法,因此通知是切面的具体实现。
❺引入
引入( Introduction)允许在现有的实现类中添加自定义的方法和属性。
❻目标对象
目标对象(Target Object)是指所有被通知的对象。如果AOP框架使用运行时代理的方式(动态的AOP)来实现切面,那么通知对象总是一个代理对象。
❼代理
代理(Proxy) 是通知应用到目标对象之后被动态创建的对象。
❽织人
织入( Weaving)是将切面代码插入到目标对象上,从而生成代理对象的过程。根据不同的实现技术,AOP 织入有3种方式:编译期织入,需要有特殊的Java 编译器;类装载期织入,需要有特殊的类装载器:动态代理织入,在运行期为目标类添加通知生成子类的方式。Spring AOP框架默认采用动态代理织入,而AspectJ (基于Java语言的AOP框架)
Spring通知类型介绍
根据Spring中通知在目标类方法的链接点位置,可以分为6种类型:
(1)环绕通知:
实现接口 |
功能描述 |
org.aopalliance.itercept.MethodInterceptor |
在目标方法执行前和执行后实施增强。可以用于日志记录、事务处理等功能。 |
(2)前置通知:
实现接口 |
功能描述 |
org.springframework.aop.MethodBeforeAdvice |
在目标方法执行前实施增强。可以用于权限管理等功能。 |
(3)后置返回通知:
实现接口 |
功能描述 |
org.springframework.aop.AfterReturningAdvice |
在目标方法成功执行后实施增强。可以用于关闭流、删除临时文件等功能。 |
(4)后置(最终)通知:
实现接口 |
功能描述 |
org.springframework.aop.AfterAdvice |
在目标方法执行后实施增强。与后置返回通知不同的是,不管是否发生异常都要执行该通知,可应用于释放资源。 |
(5)异常通知:
实现接口 |
功能描述 |
org.springframework.aop.ThrowsAdvice |
在方法抛出异常后实施增强。可以用于异常处理、记录日志等功能。 |
(6)引入通知:
实现接口 |
功能描述 |
org.springframework.aop.IntroductInterceptor |
在目标类中添加一些新的方法和属性。可以用于修改目标类(增强类)。 |
3.4.基于XML配置开发
1.把通知类用 bean 标签配置起来
1 2 3 4
| <!-- 配置通知 --> <bean id="txManager" class="com.itheima.utils.TransactionManager"> <property name="dbAssit" ref="dbAssit"></property> </bean>
|
2.使用 aop:config 声明 aop 配置
1 2 3 4
| 作用:用于声明开始 aop 的配置 <aop:config> <!-- 配置的代码都写在此处 --> </aop:config>
|
3.使用 aop:aspect 配置切面
1 2 3 4 5 6 7 8 9
| aop:aspect: 作用: 用于配置切面。 属性: id:给切面提供一个唯一标识。 ref:引用配置好的通知类 bean 的 id。 <aop:aspect id="txAdvice" ref="txManager"> <!--配置通知的类型要写在此处--> </aop:aspect>
|
4.使用 aop:pointcut 配置切入点表达式
1 2 3 4 5 6 7 8 9 10
| aop:pointcut: 作用: 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。 属性: expression:用于定义切入点表达式。 id:用于给切入点表达式提供一个唯一标识 <aop:pointcut id="pt1" expression="execution( execution:匹配方法的执行(常用) execution(表达式) 表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))" />
|
5.使用 aop:xxx 配置对应的通知类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| aop:before 作用: 用于配置前置通知。指定增强的方法在切入点方法之前执行 属性: method:用于指定通知类中的增强方法名称 ponitcut-ref:用于指定切入点的表达式的引用 poinitcut:用于指定切入点表达式 执行时间点: 切入点方法执行之前执行 <aop:before method="beginTransaction" pointcut-ref="pt1"/> aop:after-returning 作用: 用于配置后置通知 属性: method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 切入点方法正常执行之后。它和异常通知只能有一个执行 <aop:after-returning method="commit" pointcut-ref="pt1"/> aop:after-throwing 作用: 用于配置异常通知 属性: method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 切入点方法执行产生异常后执行。它和后置通知只能执行一个 <aop:after-throwing method="rollback" pointcut-ref="pt1"/> aop:after 作用: 用于配置最终通知 属性: method:指定通知中方法的名称。 pointct:定义切入点表达式 pointcut-ref:指定切入点表达式的引用 执行时间点: 无论切入点方法执行时是否有异常,它都会在其后面执行。 <aop:after method="release" pointcut-ref="pt1"/>
|
3.5.基于注解开发
1.配置类的编写
1 2 3 4 5
| @Configuration @ComponentScan(basePackages="包名") @EnableAspectJAutoProxy public class SpringConfiguration { }
|
2.通知类的编写
1 2 3 4 5 6 7
| @Component("txManager") @Aspect public class TransactionManager {
@Autowired private DBAssit dbAssit ; }
|
3.在增强的方法上使用注解配置通知
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| @Before 作用: 把当前方法看成是前置通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Before("execution(* com.itheima.service.impl.*.*(..))") public void beginTransaction() { try { dbAssit.getCurrentConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } @AfterReturning 作用: 把当前方法看成是后置通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用
@AfterReturning("execution(* com.itheima.service.impl.*.*(..))") public void commit() { try { dbAssit.getCurrentConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } } @AfterThrowing 作用: 把当前方法看成是异常通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用
@AfterThrowing("execution(* com.itheima.service.impl.*.*(..))") public void rollback() { try { dbAssit.getCurrentConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } } @After 作用: 把当前方法看成是最终通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用
@After("execution(* com.itheima.service.impl.*.*(..))") public void release() { try { dbAssit.releaseConnection(); } catch (Exception e) { e.printStackTrace(); } } @Around 作用: 把当前方法看成是环绕通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Pointcut 作用: 指定切入点表达式 属性: value:指定表达式的内容 @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1() {} @Around("pt1()") public Object transactionAround(ProceedingJoinPoint pjp) {
Object rtValue = null; try {
Object[] args = pjp.getArgs();
beginTransaction();
rtValue = pjp.proceed(args);
commit(); }catch(Throwable e) {
rollback(); e.printStackTrace(); }finally {
release(); } return rtValue; }
|
参考文献:[1] 陈恒,楼偶俊,张立杰.Java EE框架整和开发入门到实践[M].清华大学出版社,2018-:.39-45
案例来自于黑马程序员Spring教学