Spring学习九-AOP

14 篇文章 0 订阅
14 篇文章 0 订阅

AOP概念

1.什么是AOP?

AOP:全称是 Aspect Oriented Programming 即:面向切面编程。简单来说AOP就是在程序中将重复的代码提取出来,在需要的时候,通过预编译方式运行时动态代理实现在不修改源代码的情况下,对已有的方法进行增强。

2.Spring AOP代理机制

1.若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。

优点:因为有接口,所以使系统更加松耦合
缺点:为每一个目标类创建接口

2、若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。

优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。

3.相关术语

Joinpoint( 连接点):
所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的
连接点。
Pointcut( 切入点):
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
Advice( 通知/ 增强):
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction( 引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或 Field。
Target( 目标对象):
代理的目标对象。
Weaving( 织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy (代理):
一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect( 切面):
是切入点和通知(引介)的结合。

4.AOP基于xml配置

使用前要明确先编写完成核心业务代码,然后把公用的代码抽取出来,制作成通知,然后在配置文件中声明切入点与通知的关系,及切面。
具体步骤如下:

  1. 把通知Bean也交给spring来管理
  2. 使用aop:config标签表明开始AOP的配置
  3. 使用aop:aspect标签表明配置切面
    • id属性:是给切面提供一个唯一标识
    • ref属性:是指定通知类bean的Id。
  4. 在aop:aspect标签的内部使用对应标签来配置通知的类型
    • aop:before:表示配置前置通知
      method属性:用于指定Logger类中哪个方法是前置通知
      pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

    • aop:after-returning :后置通知,切入点方法执行后执行

    • aop:after-throwing :异常通知,切入点方法产生异常时通知

    • aop:after :最终通知,无论是否切入点方法异常都会通知类似 finally
      切入点表达式的写法:
      关键字:execution(表达式)
      表达式:
      访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
      标准的表达式写法:
      public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
      访问修饰符可以省略
      void com.itheima.service.impl.AccountServiceImpl.saveAccount()
      返回值可以使用通配符,表示任意返回值
      * com.itheima.service.impl.AccountServiceImpl.saveAccount()
      包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
      * ....AccountServiceImpl.saveAccount())
      包名可以使用…表示当前包及其子包
      * …AccountServiceImpl.saveAccount()
      类名和方法名都可以使用
      来实现通配
      * .()
      参数列表:
      可以直接写数据类型:
      基本类型直接写名称 int
      引用类型写包名.类名的方式 java.lang.String
      可以使用通配符表示任意类型,但是必须有参数
      可以使用…表示有无参数均可,有参数可以是任意类型
      全通配写法:
      * .
      (…)

          实际开发中切入点表达式的通常写法:
              切到业务层实现类下的所有方法
                  * com.service.impl.*.*(..)
      

Maven导入aspectj包 切入表达式

<dependencies>
    <!-- 切入点表达式配置 -->
    <dependency>
        <groupId>aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.5.3</version>
    </dependency>
</dependencies>

xml配置

    <context:annotation-config/>
    <!-- 配置srping的Ioc,把service对象配置进来-->
    <bean id="accountService" class="com.springAll.service.impl.AccountServiceImpl"/>
    <!-- 配置logger类 -->
    <bean id="logger" class="com.springAll.Logger"/>
    <!-- 配置AOP -->
    <aop:config>
   <!-- 配置切面 -->
   <aop:aspect id="logAdvice" ref="logger">
 <!-- 配置AOP -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置通知类型,简历通知方法和切入方法的关联 -->
            <aop:before method="beforePrintLog" pointcut="execution(* com.springAll.service.impl.*.*(..))"></aop:before>
            <!-- 后置通知 -->
            <aop:after-returning method="afterPrintLog" pointcut="execution(* com.springAll.service.impl.*.*(..))"></aop:after-returning>
            <!--异常通知-->
            <aop:after-throwing method="errorPrintLog" pointcut="execution(* com.springAll.service.impl.*.*(..))"></aop:after-throwing>
            <!-- 最终通知 -->
            <aop:after method="finallyPrintLog" pointcut="execution(* com.springAll.service.impl.*.*(..))"></aop:after>
        </aop:aspect>
    </aop:config>

测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringAopTest {
     @Resource
     private IAccountService accountService;
     @Test
     public void xmlAopTest(){
          accountService.saveAccount();
          accountService.updateAccount();
     }
}

但是每个切面中都需要有一个pointcut标签 配置切入点表达式,有些繁琐,所以这里可以将表达式提出来单独用标签来表示。

<!-- 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
    <!-- 配置通知类型,简历通知方法和切入方法的关联 -->
    <aop:before method="beforePrintLog" pointcut-ref="log"></aop:before>
 pointcut-ref="log"></aop:after>
    <aop:pointcut id="log" expression="execution(* com.springAll.service.impl.*.*(..))"/>
</aop:aspect>

将aop:pointcut标签写在aop:aspect内部时,只有当前的切面可以使用,此时可以将其放到aop:config标签下,让其他的切入点也可以使用。(注:一定要将配置放到引用文件之前,不然没法使用。

<aop:config>
    <aop:pointcut id="log" expression="execution(* com.springAll.service.impl.*.*(..))"/>
    <!-- 配置切面 -->
    <aop:aspect id="logAdvice" ref="logger">
        <!-- 配置通知类型,简历通知方法和切入方法的关联 -->
        <aop:before method="beforePrintLog" pointcut-ref="log"></aop:before>
        <!-- 最终通知 -->
        <aop:after method="finallyPrintLog" pointcut-ref="log"></aop:after>
    </aop:aspect>
</aop:config>

在上面讲了后置通知,前置通知,异常通知,和最终通知,最后再来看环绕通知

  <!-- 环绕通知 -->
 <aop:around method="aroundPrintLog" pointcut-ref="log"/>

Spring框架提供了一个接口ProceedingJoinPoint,此接口有一个方法proceed()。相当于明确调用切入点方法。该接口可以作为环绕通知的方法参数,在程序执行时,Spring框架会提供该接口实现供我们使用。

public Object aroundPrintLog(ProceedingJoinPoint pjp){
    Object returnVal = null;
    try {
        Object obj[] = pjp.getArgs(); //得到方法执行所需的参数
        System.out.println("===============Logger类记录开始日志================ 前置");
        returnVal = pjp.proceed(); //切入点方法
        System.out.println("===============Logger类记录开始日志================ 后置");
    } catch (Throwable throwable) {
        System.out.println("===============Logger类记录开始日志================ 异常");
        throwable.printStackTrace();
    }finally {
        System.out.println("===============Logger类记录开始日志================ 最终");
    }
    return returnVal;
}
  • 以上流程就叫做环绕通知,既可以定制化的决定使用何种通知,且可以在其中决定是否调用目标方法!!!在前置等其他通知中是不可以的,只是在方法调用前后执行通知而已。
  • 环绕通知也可以控制返回对象,既可以返回一个与目标对象方法完全不同的对象!

5.AOP基于注解

xml开启注解AOP的支持

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

添加 @Aspect 表示当前类为切面类
使用 @Pointcut 来指定切入点表达式。(此注解需要引入jar包aspectjweaver 最好是1.8以后的,之前使用的1.5的使用注解时报错了,需注意)

@Component("logger")
@Aspect
//表示当前类是一个切面类
public class Logger {
    @Pointcut("execution(* com.springAll.service.impl.*.*(..))")
    private void pt1(){}
    /**
     *   用于打印日志切入点方法之前执行
     */
   // @Before("pt1()")
    public void beforePrintLog(){
        System.out.println("===============Logger类记录开始日志================");
    }
    @AfterReturning("pt1()")
    public void afterPrintLog(){
        System.out.println("===============Logger类记录结束日志================");
    }
    @AfterThrowing("pt1()")
    public void errorPrintLog(){
        System.out.println("===============Logger类记录异常日志================");
    }
    @After("pt1()")
    public void finallyPrintLog(){
        System.out.println("===============Logger类记录最终日志================");
    }
//@Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object returnVal = null;
        try {
            Object obj[] = pjp.getArgs(); //得到方法执行所需的参数
            System.out.println("===============Logger类记录开始日志================ 前置");
            returnVal = pjp.proceed(); //切入点方法
            System.out.println("===============Logger类记录开始日志================ 后置");
        } catch (Throwable throwable) {
            System.out.println("===============Logger类记录开始日志================ 异常");
            throwable.printStackTrace();
        }finally {
            System.out.println("===============Logger类记录开始日志================ 最终");
        }
        return returnVal;
    }
}

执行结果:

===============Logger类记录开始日志================
执行保存
===============Logger类记录最终日志================
===============Logger类记录结束日志================

此时发现执行结果的顺序有些问题,最终日志竟然输出在了结束日志的前面,经查spring注解aop中存在调用顺序异常的问题,所以在实际开发中可以考虑使用xml配置的方式或者使用注解的环绕通知,因为具体调用顺序由程序决定,所以不会有此问题。

练习资源

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值