Spring AOP原理和用法

AOP也就是面向切面编程,首先AOP不等于Spring aop,前者是编程所要实现的目标,后者仅仅是AOP的实现方式之一,作为一种动态注入的实现方式,还有一些别的例如AspectJ(静态注入)等

一. Spring aop使用场景分析

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/>

输出
在这里插入图片描述

发布了26 篇原创文章 · 获赞 1 · 访问量 479

猜你喜欢

转载自blog.csdn.net/Vince_Wang1/article/details/104080298