spring AOP


spring AOP

什么是AOP

AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于
将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模
块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时
提高了系统的可维护性。可用于权限认证、日志、事务处理等

Spring AOP and AspectJ AOP–AOP 有两种实现方式

AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表
(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理
类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的
时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理

Spring怎么在运行时通知对象

通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的是ApplicationContext的
话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对
象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入SpringAOP的切面。

Spring只支持方法级别的连接点

因为Spring基于动态代理,所以Spring只支持方法连接点。Spring缺少对字段连接点的支持,而且
它不支持构造器连接点。方法之外的连接点拦截功能,我们可以利用AspectJ来补充

解释一下Spring AOP里面的几个名词

(1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
(2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
(3)通知(Advice):在AOP术语中,切面的工作被称为通知。
(4)切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
(5)引入(Introduction):引入允许我们向现有类添加新方法或属性。
(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
(7)织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:
编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目
标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面

结合步骤解读
在这里插入图片描述
步骤解读:
①找到Target Object(目标对象),需要被代理的类,UserService
②找到Joinpoint(连接点),那些可能被拦截的方法 可以是UserService中的所有方法
③指定Pointcut(切入点),需要我们增强的方法 例如:addUser(),就是对应代理类中的method
④写我们需要增强的内容,引入过程(Introduction),写我们的通知Advice(通知/增强处理):增强代码 例如: befor(),after(),代理类中invoke方法里面
Spring增强(advisor)=通知(advice)+切入点(PointCut)
⑤Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程
⑥Proxy(代理):动态创建的代理类
⑦形成Aspect(切面):advice通知和Pointcut切入点组成的面

Spring通知有哪些类型

在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过SpringAOP框架触发的代码段。

Spring切面可以应用5种类型的通知

前置通知(Before):在目标方法被调用之前调用通知功能;
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
返回通知(After-returning ):在目标方法成功执行之后调用通知;
异常通知(After-throwing):在目标方法抛出异常后调用通知;
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

什么是切面 Aspect

aspect 由 pointcount (切入点)和 advice(通知) 组成,切面是通知和切点的结合。 它既包含了横切逻辑的定义,也包括了连接点的定义. Spring AOP 就是负责实施切面的框架, 它将切面所定义的横切逻辑编织到切面所指定的连接点中. AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作:

如何通过 pointcut 和 advice 定位到特定的 joinpoint(连接点)上

业务层需要实现的接口

public interface IAccountService {
   void saveAccount();
   void updateAccount(int i);  
   int  deleteAccount();
}

业务层方法

public class AccountServiceImpl implements IAccountService{
    @Override
    public void saveAccount() {
        System.out.println("执行了保存");
    }
   @Override
    public void updateAccount(int i) {
        System.out.println("执行了更新"+i);
    }
    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

要对业务层方法增强的代码,也就是通知(暂未考虑通知类型)

public class Logger {
    /**
     * 用于打印日志
     */
    public  void PrintLog() {
        System.out.println("Logger类中的PrintLog方法开始记录日志了。。。");
    }
}

代码写完了,接下来就是去xml中对Spring进行配置了

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 配置srping的Ioc,把service对象配置进来-->
    <bean id="accountService" class="AOP.service.impl.AccountServiceImpl"></bean>
    <!-- 配置Logger类 -->
    <bean id="logger" class="AOP.utils.Logger"></bean>
    <!--配置AOP-->
    <aop:config>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
        <!-- 配置前置通知的类型(在切入点方法之前执行),并且建立通知方法和切入点方法的关联-->
            <aop:before method="beforePrintLog" pointcut="execution(* *..*.updateAccount(int))"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

测试方法

public class test1 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("AOPBean.xml");
        IAccountService accountService = (IAccountService)ac.getBean("accountService");
        accountService.updateAccount(1);
    }
}

运行截图在这里插入图片描述
图中的内容我们可以看出,在不更改原代码的基础上,我们对源代码的方法进行了增强
以上内容就是Spring框架中AOP的基本实现了,接下来的内容是对xml配置的详细解析和对配置的五个类型的代码演示

AOP的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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 配置srping的Ioc,把service对象配置进来-->
    <bean id="accountService" class="AOP.service.impl.AccountServiceImpl"></bean>
    <!-- 配置Logger类 -->
    <bean id="logger" class="AOP.utils.Logger"></bean>
    <!--spring中基于XML的AOP配置步骤
        1、把通知Bean也交给spring来管理
        2、使用aop:config标签表明开始AOP的配置
        3、使用aop:aspect标签表明配置切面
                id属性:是给切面提供一个唯一标识
                ref属性:是指定通知类bean的Id。
        4、在aop:aspect标签的内部使用对应标签来配置通知的类型
               现在的代码示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
               aop:before:表示配置前置通知
                    method属性:用于指定Logger类中哪个方法是前置通知
                    pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
            切入点表达式的写法:
                关键字:execution(表达式)
                表达式写法:
                    访问修饰符  返回值  包名.包名.包名(把所有包名都写完).类名.方法名(参数列表)
                标准的表达式写法:
                    public void AOP.service.impl.AccountServiceImpl.updateAccount(int)
                访问修饰符可以省略
                    void AOP.service.impl.AccountServiceImpl.updateAccount(int)
                返回值可以使用通配符,表示任意返回值
                    * AOP.service.impl.AccountServiceImpl.updateAccount(int)
                包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
                    * *.*.*.*.AccountServiceImpl.updateAccount(int))
                包名可以使用..表示当前包及其子包
                    * *..AccountServiceImpl.updateAccount()
                类名和方法名都可以使用*来实现通配
                    * *..*.*()
                参数列表:
                    可以直接写数据类型:
                        基本类型直接写名称           int
                        引用类型写包名.类名的方式   java.lang.String
                    可以使用通配符表示任意类型,但是必须有参数
                    可以使用..表示有无参数均可,有参数可以是任意类型
                全通配写法:
                    * *..*.*(..)
    -->
    <!--配置AOP-->
    <aop:config>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
        <!-- 配置前置通知的类型(在切入点方法之前执行),并且建立通知方法和切入点方法的关联-->
            <aop:before method="beforePrintLog" pointcut="execution(* *..*.updateAccount(int))"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

如何在 advice 中编写切面代码.

AOP中五个通知类型的解析:前置通知,后置通知,异常通知,最终通知,环绕通知

前置通知,后置通知,异常通知,最终通知写法展示

业务层需要实现的接口

public interface IAccountService {
   void saveAccount();
   void updateAccount(int i);   
   int  deleteAccount();
}

业务层方法

public class AccountServiceImpl implements IAccountService{
    @Override
    public void saveAccount() {
        System.out.println("执行了保存");
    }
    @Override
    public void updateAccount(int i) {
        System.out.println("执行了更新"+i);
    }
    @Override
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

要对业务层方法增强的代码,也就是通知(考虑通知类型)

public class Logger {
    /**
     * 前置通知
     * 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
     */
    public  void beforePrintLog() {
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
    }
    /**
     * 后置通知
     * 用于打印日志:计划让其在切入点方法成功执行之后执行(切入点方法就是业务层方法)
     */
    public  void afterReturningPrintLog() {
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
    }
    /**
     * 最终通知
     * 用于打印日志:计划让其在切入点方法无论是否成功执行之后执行(切入点方法就是业务层方法)
     */
    public  void afterPrintLog() {
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
    }
    /**
     * 异常通知
     * 用于打印日志:计划让其在切入点方法抛出异常之后执行(切入点方法就是业务层方法)
     */
    public  void afterThrowingPrintLog() {
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
    }
}

代码写完了,接下来就是去xml中对Spring进行配置了

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 配置srping的Ioc,把service对象配置进来-->
    <bean id="accountService" class="AOP.service.impl.AccountServiceImpl"></bean>
    <!-- 配置Logger类 -->
    <bean id="logger" class="AOP.utils.Logger"></bean>
    <!--配置AOP-->
    <aop:config>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置切入点表达式,id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
            此标签可以写在aop:aspect标签的内部和外部,写在内部时只能当前切面使用,写在外部时所有切面可用-->
            <aop:pointcut id="point" expression="execution(* *..*.deleteAccount())"/>
            <!-- 配置前置通知的类型(在切入点方法之前执行),并且建立通知方法和切入点方法的关联-->
            <aop:before method="beforePrintLog" pointcut-ref="point"></aop:before>
            <!-- 配置后置通知的类型(在切入点方法正常执行之后执行,它和后置通知永远只能执行一个),并且建立通知方法和切入点方法的关联-->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="point"></aop:after-returning>
            <!-- 配置异常通知的类型(在切入点方法执行产生异常之后执行),并且建立通知方法和切入点方法的关联-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="point"></aop:after-throwing>
            <!-- 配置最终通知的类型(无论切入点方法是否正常执行它都会在其后面执行),并且建立通知方法和切入点方法的关联-->
            <aop:after method="afterPrintLog" pointcut-ref="point"></aop:after>
</beans>

测试方法

public class test1 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("AOPBean.xml");
        IAccountService accountService = (IAccountService)ac.getBean("accountService");
        accountService.deleteAccount();
    }
}

运行截图(业务层方法没有异常)
在这里插入图片描述
接下来在deletedeleteAccount方法中添加 int i=1/0.是他出现异常
运行截图(业务层方法有异常)
在这里插入图片描述
从以上两张截图中可以看出,异常通知和后置通知永远只能执行一个

环绕通知写法展示

环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点. 对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.

在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.

注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
写法上

package com.fy.utils;

import org.aspectj.lang.ProceedingJoinPoint;

public class logger {
    public void printLog(){
        System.out.println("Logger中的printLog开始记录日志了。。。");
    }
    //对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint** . 它是 JoinPoint 的子接口, **允许控制何时执行, 是否执行连接点
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
         Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数
            System.out.println("前置通知");
//在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行
            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
            System.out.println("后置通知");
 //环绕通知的方法需要返回目标方法执行之后的结果,即调用joinPoint.proceed()的返回值,否则会出现空指针异常
            return rtValue;
        }catch (Throwable t){
            System.out.println("异常通知");
            throw new RuntimeException(t);
        }finally {
            System.out.println("最终通知");
        }
    }
}

配置xml文件

<bean id="logger" class="com.fy.utils.logger"></bean>
    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* com.fy.service.impl.*.*(..))"/>
        <aop:aspect id="logAdvice" ref="logger">
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>

测试类

package com.fy.test;
import com.fy.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AOPTest {
    public static void main(String[] args) {
         ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        AccountService as = (AccountService)ac.getBean("accountService");
        as.saveAccount();
    }
}

结果截图
在这里插入图片描述
总结:结合动态代理的环绕通知来理解,spring只是将其管理分配,你只需要告诉Spring是怎么回事就行

在Spring AOP 中,关注点和横切关注的区别是什么?在 springaop 中 concern 和 cross-cutting concern 的不同之处

关注点(concern)是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个
功能。横切关注点(cross-cutting concern)是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值