Spring框架学习笔记2:什么是AOP,以及AOP的配置使用

什么是AOP

定义:​ AOP就是一种面向切面编程的方式:基于OOP基础之上新的编程思想,OOP面向的主要对象是类,而AOP面向的主要对象是切面,在处理日志、安全管理、事务管理等方面有非常重要的作用。AOP是Spring中重要的核心点,虽然IOC容器没有依赖AOP,但是AOP提供了非常强大的功能,用来对IOC做补充。
底层原理:java代理设计模式,动态代理

为什么要用AOP

优点

在AOP思想中,通过aspect(切面)可以分别在不同的类的方法中加入,例如事务日志权限和异常处理等功能。
使用切面这种横向方式。能够使开发人员在编写业务逻辑时专注于核心业务逻辑,而不用过度的关注与其他业务逻辑的实现。这样可以提高开发效率,同时还增强了代码的可维护性。实现了附加操作和核心操作解耦。

AOP的核心概念及术语

切点(Pointcut): 匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。【指定开发好的通知应用于项目中的哪些组件中的哪些方法,也就是创建代理对象的代码,例如:UserService proxy= (UserService)Proxy.newProxyInstance(Classloader,Class[],InvocationHandler) 】

通知(Advice): 在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before” and “after”等等。通知的类型将在后面的章节进行讨论。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。【除了核心功能以外的操作都叫通知,也就是附加功能,如事务通知,日志通知,性能通知;也就是InvocationHandler的具体实现,例如:】

new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object invoke=null;
                try{
                    System.out.println("开启事务");
                    invoke = method.invoke(userService, args);
                    System.out.println("提交事务");
                }catch (Exception e){
                    System.out.println("回滚事务");
                }
                return invoke;
            }
        })

在这里插入图片描述

切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以@Aspect注解(@AspectJ 注解方式)来实现。【也就是,通知+切点】

连接点(Join point): 在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。

引入(Introduction): 声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现 IsModified接口, 以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。

目标对象(Target object): 被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。

AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。

织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。

AOP的应用场景

日志管理
权限认证
安全检查
事务控制

如何使用

总体流程:1.开发通知类(附加功能) 2.配置切(入)点 3.组装切面

1.添加pom依赖

cglib、aspectj、aopalliance、aspects

2.项目开发额外功能

环绕通知:MethodIntercept
前置通知:MethodBeforeAdvice
后置通知:AfterReturningAdvice
异常通知:ThrowsAdvice

3.配置切面

  1. 注册通知类
    <bean class="aopp.MyBeforeAdvice" id="myBeforeAdvice"/>
    
  2. 组装切面 aspect = advice + pointcut
    <aop:config>
    	<aop:pointcut>
    	<aop:advisort>
    </aop:config>
    
    实例:
    <bean class="aopp.EmpServiceImpl" id="empService"/>
    <!--注册通知-->
        <bean class="aopp.MyBeforeAdvice" id="myBeforeAdvice"/>
    
    <!--组装切面-->
        <aop:config>
            <!--
                配置切入点pointcut
                id:切入点在工厂中的唯一标识
                expression:用来指定切入项目中哪些组件的哪些方法
             -->
            <aop:pointcut id="pc" expression="execution(* aopp.EmpServiceImpl.*(..))"/>
            <!--配置切面 advice-ref:工厂中通知的id pointcut-ref:工厂中切入点的id-->
            <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="pc"/>
        </aop:config>
    
    在test中输出empService.getClass()时,如果注释掉aop的配置,就是empServiceImpl类,否则就是一个代理Proxy类。

切入点表达式

作用:主要用来决定项目中哪些组件需要加入通知
expression=“切入点表达式”

  1. execution 切入点表达式: ---->方法级别的切入点表达式
    完整语法:
    - execution(访问权限修饰符 返回值 报名.类名.方法名(参数类型))
    - execution(返回值 报名.类名.方法名(参数类型)) //默认访问权限修饰符是public

  2. within 切入点表达式: ---->类级别的切入点表达式
    完整语法:
    -----------within(包.类名)

编写配置

  • 将切面类和目标类加入到IOC容器中,在对应的类上添加组件注解
    1. 给LogUtil添加@Component注解
    2. 给MyCalculator添加@Service注解
  • 设置切面类中的方法是什么时候在哪里执行、切入点表达式、切面类方法执行顺序、使用切面类方法时如何获取对应执行方法的名称和参数、环绕方法执行顺序、多个切面类执行顺序排序方式:
 * 通知注解有以下几种类型:
     *
     * @Before:前置通知,在方法执行之前完成
     * @After:后置通知,在方法执行完成之后执行
     * @AfterReturing:返回通知:在返回结果之后运行
     * @AfterThrowing:异常通知:出现异常的时候使用
     * @Around:环绕通知
     *
     * 在方法的参数列表中不要随便添加参数值,会有异常信息
     *
     * 切入点表达式:
     *  最精确的匹配方式:
     *      public Integer com.mashibing.service.MyCalculator.add(Integer,Integer)
     *  在实际的生产环境中,更多的时候使用通配符的方式
     *      *:
     *          1、可以用来匹配一个或者多个字符
     *          execution( public Integer com.mashibing.service.MyCalculator.*(Integer,Integer)
     *          2、匹配任意类型的参数,只能匹配一个
     *          execution( public Integer com.mashibing.service.M*Calculator.*(Integer,*))
     *          3、*在进行匹配的时候只能匹配一层路径,不能匹配多层
     *          4、*不能够匹配访问修饰符,如果不确定访问修饰符是什么,可以直接省略不写
     *          execution( Integer com.mashibing.service.MyCalculator.*(Integer,*))
     *          5、返回值可以使用*来代替
     *      ..:
     *          1、可以匹配多个参数,任意类型
     *          execution(* com.mashibing.service.MyCalculator.*(..))
     *          2、可以匹配多层路径
     *          execution(* com.mashibing..MyCalculator*.*(..))
     *     最偷懒的方式:
     *          execution(* *(..))
     *          execution(* com..*(..))
     *      如果表达式是以*开头,那么可以代替所有
     *
     *      在使用表达式的时候还支持逻辑运算
     *      &&:多个条件必须同时满足
     *          execution(public Integer com.mashibing.service.MyCalculator.*(..)) && execution(* *(..))
     *      ||:多个条件只要满足其中一个即可
     *          execution(public Integer com.mashibing.service.MyCalculator.*(..)) || execution(* *(..))
     *      !:取反,除了这种情况的其他都满足
     *          !execution(public Integer com.mashibing.service.MyCalculator.add(Integer,Integer))
     *
     *      使用通配符的时候不是越简洁越好,更多的是要选择符合要求或者符合规则的匹配方式,
     *      此时就要求在定义标识符的时候必须要遵循项目规范
     *
     *      通知的正常执行顺序:
     *      如果正常执行:@Before--》@After----》@AfterReturning
     *      如果异常结束:@Before--》@After----》@AfterThrowing
     *
     *
     *      如果想要在方法中获取对应的参数或者方法名称等信息的时候,必须要使用JoinPoint对象,并且此参数必须是第一个
     *      getSignature()
     *      getArgs()
     *      如果方法中有返回值,那么必须要在注解中添加 Returing="result" ,这个result必须要和参数列表中的参数名称保持一致
     *      如果需要添加异常信息,那么在注解中要添加Throwing="e" 这个e的名称必须跟参数列表中的名称保持一致
     *      如果想要添加其他参数,必须要添加args(参数列表),ArgNames(参数列表)
     *          @Before(value = "execution(public Integer com.mashibing.service.MyCalculator.*(Integer,Integer)) && args(joinPoint,k)",argNames = "joinPoint,k")
     *
     *
     *      通知方法在定义的时候有没有什么特殊的要求?
     *          通知方法在定义的时候对于访问修饰符、返回值类型都没有明确的要求,
     *          但是要注意,参数不能随便添加
     *
     *      如果有多个匹配的表达式相同,能否做抽象?
     *          定义一个没有返回值的空方法,给该方法添加@PointCut注解,后续在使用的时候可以直接调用方法名称
     *          此处的方法只是起一个声明的作用,能够供内部的其他通知方法进行调用
     *
     *
     *      环绕通知:
     *          环绕通知在执行的时候是优先于普通通知的
     *          如果是正常结束,那么执行顺序是:
     *              环绕前置通知--》@Before--》环绕后置通知--》环绕返回通知--》@After--》@AfterReturning
     *          如果是异常结束,那么执行顺序是:
     *              环绕前置通知--》@Before--》环绕异常通知--》环绕返回通知--》@After--》@AfterReturing
     *              如果出现异常的时候,在环绕通知中解决了,那么普通通知是接受不到的,如果想让普通通知接收到需要进行抛出 throw throwable
     *              执行顺序改为:
     *              环绕前置通知--》@Before--》环绕异常通知--》环绕返回通知--》@After--》@AfterThrowing
     *
     *      当应用程序中包含多个切面类的时候,具体的执行顺序是什么样?
     *          按照切面类的名称的首字母进行排序操作,按照字典序
     *          如果需要认为的规定顺序,可以在切面类上添加@Order注解同时可以添加具体的值
     *          值越小,越优先
     */
  • 计划任务
  • 完成任务
@Component
@Aspect
public class LogUtil {

    /*
    设置下面方法在什么时候运行
        @Before:在目标方法之前运行:前置通知
        @After:在目标方法之后运行:后置通知
        @AfterReturning:在目标方法正常返回之后:返回通知
        @AfterThrowing:在目标方法抛出异常后开始运行:异常通知
        @Around:环绕:环绕通知

        当编写完注解之后还需要设置在哪些方法上执行,使用表达式
        execution(访问修饰符  返回值类型 方法全称)
     */
    @Before("execution( public int com.mashibing.inter.MyCalculator.*(int,int))")
    public static void start(){
//        System.out.println("XXX方法开始执行,使用的参数是:"+ Arrays.asList(objects));
//        System.out.println(method.getName()+"方法开始执行,参数是:"+ Arrays.asList(objects));
        System.out.println("方法开始执行,参数是:");
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值