Spring AOP原理和用法
AOP也就是面向切面编程,首先AOP不等于Spring aop,前者是编程所要实现的目标,后者仅仅是AOP的实现方式之一,作为一种动态注入的实现方式,还有一些别的例如AspectJ(静态注入)等
一. Spring aop使用场景分析
如图是常规思维下一个登陆流程的实现,在登陆的流程中,每一步都不能发生任何错误(这里指的当然不是空指针这种RuntimeException),而是指的数据传输出现了错误,比如http传了一个name=a,但是controller却接收成了name=b,这样就会导致整个程序的出错
这一条自上而下的逻辑,可以理解为主逻辑,而每个部分可能都会有他们自己的切向的一些方法的调用
例如在进入controller的过程中,调用了一个f方法,而f方法作为一个日志,比方说记录了一个日志log,就是说我在什么时候调用了这个方法,作为一个记录,
然后进入service,又调用了f1方法,f1方法就是验证从上面传下来的用户名是否有权限对service进行调用,如果都验证成功了,则进入dao,dao自然记录的场景就是事务处理。
那么,为什么这些是作为切面?
因为这些内容和主逻辑没有关系,就是说无论日志记录的如何,权限验证如何,dao事务如何处理,和主逻辑一概没有关系,可能会有很多的切面,但都不影响主逻辑自己的运行,除非你主逻辑写错了tx
切面一般对如下内容进行封装,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性
- 事务
- 权限处理
- 日志
- 性能检测
- 异常处理
二.SpringAOP的常用术语
在spring framework的官方文档,地址添加链接描述,首先第一段话大致就是aop和oop的区别,阐述了面向切面编程和面向对象编程的联系
下面的术语来逐条看
Aspect中文翻译是切面,对于切面的理解,得结合后面的几点,总体来说就是join point, cut point和advice所在的类称之为切面。
join point
连接点(join point),中文翻译大致意思是join point在spring aop中作为一种方法或者是对报错的处理。比如说刚举的例子,切面log是基于f方法的,而这个f方法就是连接点,我个人理解就是切面和主逻辑的连接处。
Advice
也就是增强,为了实现log日志,得编写一些逻辑代码Logic,但此时,我们需要在不同的连接点编写代码,可以说advice决定了在原有方法功能中出现的位置和时机。可分为around before after
Pointcut
可以与任何join point点进行匹配,匹配后的位置就是切面的位置。
target object
顾名思义,目标对象
如上图,f方法里实现的m功能再进行一些修改后(或者说增强)实现了n功能,而target object,也就是基础对象,增强后的方法f,则是被存在代理对象proxy object中。
AOP Proxy
aop代理,也就是为了实现方法增强而编写的对象,编写方式有jdk动态代理等,在我的另一篇文章也有写到。
Weaving
编织,也就是把切面应用到目标对象来创建新的代理对象的过程。也就是把上图的target object通过切面穿件proxy object的过程。
三.Spring aop实现方式
第一步得输入依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency>
这方面我稍微了解了一下,aspectj其实是和spring aop一样,都是aop的一种实现方式,按理来说是竞争对手的关系,那这里为什么出现了Aspectj呢,而且在spring的官网上也能看到对aspectj的描述
我个人的了解是在spring aop诞生之初,想实现aop的操作非常复杂(具体怎么实现不知道 我那时候可能还在打dota) 后来是借助了Aspectj的样式,并取其精华,使用AspectJ为切入点解析和匹配提供的库解释与AspectJ 5相同的注释。具体可以详见sping.io官网
下面列举几个spring aop的实现方式
实现方式一(通过 Spring API 实现)
首先编写我们的业务接口和实现类,这里用一个简单的增删改查,配合之前一篇动态代理的笔记来理解。
public interface UserService { public void add(); public void delete(); public void update(); public void search(); }
public class UserServiceImpl implements UserService{ public void add() { System.out.println("增加用户"); } public void delete() { System.out.println("删除用户"); } public void update() { System.out.println("更新用户"); } public void search() { System.out.println("查询用户"); } }
然后去写增强类 , 一个前置增强 一个后置增强,也就是advice里的语句,before和after
public class Log implements MethodBeforeAdvice { public void before(Method method,Object[] args,Object target)throws Throwable{ System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了"); } }
public class AfterLog implements AfterReturningAdvice { public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println("执行了" + method.getName() +"的"+method.getName()+"方法," +"返回值:"+o); } }
然后去applicationContext.xml文件下注入,并实现aop切入
<bean id="userService" class="com.vincewang.service.UserServiceImpl"/> <bean id="Log" class="com.vincewang.log.Log"/> <bean id="afterLog" class="com.vincewang.log.AfterLog"/> <aop:config> <aop:pointcut id="pointcut" expression="execution(* com.vincewang.service.UserServiceImpl.*(..))"/> <aop:advisor advice-ref="Log" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config>
前面三行的bean注入就不解释了
而pointcut,就是切入点,expression就是表达式,execute执行,
execution(* com.kuang.pojo.UserServiceImpl.*(…))
解释:一开始的的意思就是所有文件,com.vincewang.service,UserServiceImpl就是切入在这个类里,
.就是当前类下的所有方法,(…)括号里的两个点代表可以是任意参数
execution的书写规范可以详见https://www.cnblogs.com/xbzg/p/4807092.html
(注意!一定要增加aop的约束
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
否侧会出现org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException这样的报错
现在就可以进测试类进行测试了
public class MyTest { @Test public void test(){ ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService=(UserService)context.getBean("userService"); userService.add(); } }
输出
这里可以很好的理解advice增强了,用before和after编写的语句都在输出里有很好的体现,分别出现在了原来的输出的前面和后面,但并没有改变UserServiceImpl类下的一兵一卒,完全是由两个增强类继承spring aop的接口,通过applicationContext.xml注入,就实现了aop的一种操作流程
实现方式二(自定义类来实现)
上面这种方法,可能程序复杂以后就会出现记不住接口的情况
记录下第二种方法
首先创建一个diy的类 里面有两种方法
public class DiyPointCut { public void before(){ System.out.println("-----方法执行前-----"); } public void after(){ System.out.println("-----方法执行后-----"); } }
然后进入applicationContext.xml进行配置
<bean id="userService" class="com.vincewang.service.UserServiceImpl"/> <bean id="diy" class="com.vincewang.diy.DiyPointCut"/> <aop:config> <aop:aspect ref="diy"> <aop:pointcut id="point" expression="execution(* com.vincewang.service.UserServiceImpl.*(..))"/> <aop:before method="before" pointcut-ref="point"/> <aop:after method="after" pointcut-ref="point"/> </aop:aspect> </aop:config>
解释:
<aop:aspect ref=“diy”>
由上文可知 aspect是一个类 切面类 既然是类就有他所依赖的位置 位置在bean中已经注入为class="com.vincewang.diy.DiyPointCut,所以称为自定义切面,既然是切面就需要切入点Pointcut,和通过spring api方式实现的一样。然后就是advise增强,切入刚自定义的before和after方法,和他们的切入依赖point,也就是在point这个切入点之后定义了两个diy的方法。
输出
实现方式三(注解实现)
新建类
@Aspect public class AnnotationPointCut { @Before("execution(* com.vincewang.service.UserServiceImpl.*(..))") public void before(){ System.out.println("------方法执行前------"); } }
与之前不同,一开始得加上@Aspect注解,后面只要在方法名上面使用注解定义切入点即可
<bean id="annocationpointcut" class="com.vincewang.diy.AnnotationPointCut"/> <aop:aspectj-autoproxy/>
输出
- 点赞
- 收藏
- 分享
- 文章举报
- Spring三种注入方式
- JavaSE多态
- java和.net的对象类型转换浅谈
- SpringMVC工作流程及代码分析
- No qualifying bean of type 'org.springframework.mail.javamail.JavaMailSender' available: expected at...
- 宠物领养网站(一):简 4000 单搭建SpringBoot+JPA+Gradle+Mysql项目
- 解决 https://start.spring.io 访问报错问题
- 宠物领养网站(二):SpringBoot支持JSP开发配置
- JDK,JRE,JVM的区别与联系
- java中 equals 和 hashcode 的关系
- java知识点总结
- java实现九九乘法表-超简单的一目了然
- Java验证码读取
- java界面实现骰子比赛改进
- BES-多模块Springboot项目MyBatis通用Mapper配置(Controller Service Dao在不同子模块中)
- BES-SpringCloud Gateway网关整合多模块项目-Predicates与Filter
- java实现一个删除固定后缀文件的程序
- java编写贪吃蛇小游戏源代码分享给你们
- @蓝桥杯javaB组习题集入门(4)之第二题:序列求和
- @蓝桥杯javaB组习题集入门(4)第四题:Fibonacci数列