大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
Spring AOP原理详解及实例
观前提示:
本文所使用的Eclipse版本为Photon Release (4.8.0),IDEA版本为ultimate 2019.1,JDK版本为1.8.0_141,Tomcat版本为9.0.12。
1.Spring AOP简介
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2.AOP与OOP对比
AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
上面的陈述可能过于理论化,举个简单的例子,对于“雇员”这样一个业务实体进行封装,自然是OOP/OOD的任务,我们可以为其建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用AOP设计思想对“雇员”进行封装将无从谈起。
同样,对于“权限检查”这一动作片断进行划分,则是AOP的目标领域。而通过OOD/OOP对一个动作进行封装,则有点不伦不类。
换而言之,OOD/OOP面向名词领域,AOP面向动词领域。
3.AOP使用场景
AOP用来封装横切关注点,具体可以在下面的场景中使用:
-
Authentication 权限
-
Caching 缓存
-
Context passing 内容传递
-
Error handling 错误处理
-
Lazy loading 懒加载
-
Debugging 调试
-
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
-
Performance optimization 性能优化
-
Persistence 持久化
-
Resource pooling 资源池
-
Synchronization 同步
-
Transactions 事务
4.AOP相关概念
-
切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式来实现。
-
连接点(Joinpoint): 在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。
-
通知/增强(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。spring aop advice的类型:
1、前置通知(before advice),在目标方法执行之前执行。
2、返回后通知(after returning advice),在方法正常执行结束之后的通知,可以访问到方法的返回值。
3、抛出异常后通知(after throwing advice),在目标方法出现异常时执行的代码,可以访问到异常对象,且可以指定出现特定异常执行此方法。
4、后置通知:(after[finally] advice),在目标方法执行之后执行(无论是否发生异常)。
5、环绕通知:(around advice),可以实现上述所有功能。 -
切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上
-
引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口
-
目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO
-
AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
-
织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
5.AOP实例
需要的jar包:Jar包链接详情点我
需要的jar包
aspectjrt-1.9.2.jar、aspectjweaver-1.9.2.jar
5.1 基于xml配置方式
这里我使用的是Eclipse
目录结构
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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<bean id="userService" class="cn.com.demo.service.UserService"/>
<bean id="logAopDemo" class="cn.com.demo.aop.LogAop"/>
<!-- 面向切面编程 -->
<aop:config>
<aop:aspect ref="logAopDemo">
<!-- 定义切点 -->
<aop:pointcut expression="execution(* *..*(..))" id="pointCut"/>
<!-- 声明前置通知 (在切点方法被执行前调用)-->
<aop:before method="beforeAdvice" pointcut-ref="pointCut"/>
<!-- 声明后置通知 (在切点方法被执行后调用)-->
<aop:after method="afterAdvice" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
</beans>
注:aop:aspect 子节点还可配置
<aop:config>
<aop:aspect ref="logAopDemo">
<!-- 定义切点 -->
<aop:pointcut expression="execution(* *..*(..))" id="pointCut"/>
<!-- 声明前置通知 (在切点方法被执行前调用)-->
<aop:before method="beforeAdvice" pointcut-ref="pointCut"/>
<!-- 声明后置通知 (在切点方法被执行后调用)-->
<aop:after method="afterAdvice" pointcut-ref="pointCut"/>
<aop:after-returning method="aferReturning" pointcut-ref="pointCut"/>
<aop:after-throwing method="aferThrowing" pointcut-ref="pointCut"/>
<aop:around method="around" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
日志类LogAop.java
package cn.com.demo.aop;
public class LogAop {
public void beforeAdvice() {
System.out.println("beforeAdvice");
}
public void afterAdvice() {
System.out.println("afterAdvice");
}
}
UserService.java
package cn.com.demo.service;
public class UserService {
public void insertUser() {
System.out.println("插入用户成功");
}
public boolean updateUser() {
System.out.println("更新用户成功");
return true;
}
}
测试类TestAop
package cn.com.demo.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.com.demo.service.UserService;
public class TestAop {
private ClassPathXmlApplicationContext ac;
@Before
public void before() {
ac = new ClassPathXmlApplicationContext("*/applicationContext.xml");
}
@Test
public void test() {
try {
UserService userService = (UserService) ac.getBean("userService");
userService.insertUser();
userService.updateUser();
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果
5.2 基于注解配置方式
这里我使用的是Eclipse
目录结构
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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<bean id="userService" class="cn.com.demo.service.UserService"/>
<!-- 启用注释驱动自动注入 -->
<!-- <context:annotation-config/> -->
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="cn.com.demo"></context:component-scan>
<!-- 开启aop注解方式,此步骤不能少,这样java类中的aop注解才会生效 -->
<aop:aspectj-autoproxy/>
</beans>
注<aop:aspectj-autoproxy/>为开启aop注解方式,此步骤必不能少,这样java类中的aop注解才会生效。
日志类LogAop.java
package cn.com.demo.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogAop {
@Pointcut("execution(* cn.com.demo.service.UserService.insertUser(..))")
public void ponitCut() {
}
@Before("ponitCut()")
public void beforeAdvice() {
System.out.println("beforeAdvice");
}
@After("ponitCut()")
public void afterAdvice() {
System.out.println("afterAdvice");
}
//环绕通知。注意要有ProceedingJoinPoint参数传入
@Around("ponitCut()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("注解类型环绕通知..环绕前");
pjp.proceed();//执行方法
System.out.println("注解类型环绕通知..环绕后");
}
}
UserService.java
package cn.com.demo.service;
public class UserService {
public void insertUser() {
System.out.println("插入用户成功");
}
public boolean updateUser() {
System.out.println("更新用户成功");
return true;
}
}
测试类TestAop
package cn.com.demo.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.com.demo.service.UserService;
public class TestAop {
private ClassPathXmlApplicationContext ac;
@Before
public void before() {
ac = new ClassPathXmlApplicationContext("*/applicationContext.xml");
}
@Test
public void test() {
try {
UserService userService = (UserService) ac.getBean("userService");
userService.insertUser();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果
5.3 @AspectJ切点函数
参考文章:@AspectJ切点函数详解
6.可能出现的问题及解决方法
6.1 java.lang.IllegalArgumentException: error at :: 0 can’t find referenced pointcut……
如果出现如下错误:
可能是你的aspectjweaver和aspectjrt与你的jdk版本不匹配。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/169610.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...