day03_SSM_AOP

aop前奏

AOP前奏(动态代理)

实现计算器业务功能

  • 需求,在实现加减乘除核心业务同时,添加日志及数据校验功能。
  • 问题
    1核心业务代码,与非核心业务代码(日志代码)耦合度太高。
    2代码相对比较分散,也比较混乱。
  • 解决
    1提取非核心业务代码(日志代码)
    2将非核心业务,动态作用(织入)核心业务中。(动态代理实现)
  • 使用动态代理,(实现将非核心业务,动态织入核心业务中)
  • 动态代理的实现方式
    1基于接口实现动态代理(JDK)
    2基于(继承)类实现动态代理(Cglib\Javassist)
  • 基于接口实现动态代理的步骤(JDK)
    1定义目标对象
    2定义获取代理对象方法
    3有参构造器()
  • 补充:兄弟关系,不能相互转换。(Dog不能转换Cat)
  • 总结,问:代理对象是否可以强制转换为实现类对象?
    答:不可以(com.sun.proxy.$Proxy0 cannot be cast to com.atguigu.spring.aop.CalcImpl)
    因为:代理对象与实现类对象,是兄弟关系。

AOP&AspectJ

applicationContext.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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--    组件扫描-->
    <context:component-scan base-package="com.atguigu.spring.aopimpl"/>


    <!--    开启AspectJ
        就会为指定的切面(@Aspect)动态生成代理对象。
    -->
    <aop:aspectj-autoproxy/>


</beans>

AOP

概述

  • Aspect-Oriented Programming,面向切面编程,
  • 面向切面的编程设计,可以理解为是面向对象编程设计的一种补充。
    OOP:面向对象编程,纵向(实现接口|继承类)关系
    AOP:面向切面编程,横向(提取-非核心业务)关系

术语

  • 横切关注点:非核心业务(日志功能)
  • 切面:非核心业务提取类中,当前类称之为:切面。(MyLogging)
  • 通知:将非核心业务,提取到切面后,横切关注点更名为:通知
  • 目标:需要被代理的类,称之为目标类(CalcImpl)
  • 代理:自定义的CalcImplProxy类称之为代理类,
    通过该类中的方法【getProxyObject()】获取的对象,称之为代理对象。
  • 连接点:需要被通知的位置(需要将非核心业务作用回核心业务中的位置)
  • 切入点:被通知后的连接点,更名为:切入点。

应用(AspectJ)

  • AspectJ简介
    • 是java社区目前最主流的AOP框架
    • 不是spring体系内的框架,但spring2.0后对AspectJ做了支持
  • Spring开启AspectJ步骤
    1. 导入jar包
      com.springsource.net.sf.cglib-2.2.0.jar
      com.springsource.org.aopalliance-1.0.0.jar
      com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
      spring-aop-4.0.0.RELEASE.jar
      spring-aspects-4.0.0.RELEASE.jar
    2. 在核心配置文件中,添加:
      <aop:aspectj-autoproxy>
      
      • 一旦spring容器识别到该标签,就会为指定的切面(@Aspect)动态生成代理对象。
      • 为java类添加注解:@Aspect,此时该类称之为切面。
    3. 实现通知(5种)
      • 前置通知
        • 在切入点表达式指定的方法之前,执行。(如果有异常,依然会执行)
        /**
             * 前置通知
             */
            @Before(value="execution(public double com.atguigu.spring.aopimpl.CalcImpl.add(double,double))")    //切入点表达式
            public void methodBefore(JoinPoint jp) {   //通知
                //获取方法名
                String methodName = jp.getSignature().getName();
                //获取参数
                Object[] args = jp.getArgs();
        
                System.out.println("==>日志:" + methodName + "()之前,参数args:" + Arrays.toString(args));
            }
        
      • 后置通知
        • 在切入点表达式指定的方法之后,执行。(如果有异常,依然会执行)
        @After(value="execution(* com.atguigu.spring.aopimpl.*.*(..))")
            public void methodAfter(JoinPoint jp){
                //获取方法名
                String methodName = jp.getSignature().getName();
                //获取参数
                Object[] args = jp.getArgs();
        
                System.out.println("==>日志:" + methodName + "()之后,参数args:" + Arrays.toString(args));
            }
        
      • 返回通知
        • 在切入点表达式指定的方法返回结果,执行。(如果有异常,则不执行)
        @AfterReturning(value="puintCut()", returning = "data")
            public void methodAfterReturning(JoinPoint jp, Object data){
                //获取方法名
                String methodName = jp.getSignature().getName();
                //获取参数
                Object[] args = jp.getArgs();
        
                System.out.println("==>日志:" + methodName + "()之后,结果:" + data);
            }
        
          - 注意:两个data必须一致。
        
      • 异常通知
        • 在切入点表达式指定的方法出现异常,执行。(如果有异常,则执行,无异常,不执行)
        @AfterThrowing(value="puintCut()", throwing = "ex")
            public void methodAfterThrowing(JoinPoint jp, Exception ex){
                //获取方法名
                String methodName = jp.getSignature().getName();
                //获取参数
                Object[] args = jp.getArgs();
        
                System.out.println("==>日志:" + methodName + "()之后,结果:" + ex);
            }
        
          - 注意:两个ex必须一致。
        
      • 环绕通知
        • 当前通知相当于,之前四个通知的汇总。
        /*
            环绕通知
             */
            @Around(value="puintCut()")
            public Object methodAround(ProceedingJoinPoint pjp){
                //获取方法名
                String methodName = pjp.getSignature().getName();
                //获取参数
                Object[] args = pjp.getArgs();
                
                Object proceed = null;
                try {
                    System.out.println("==>日志:" + methodName + "()之前,参数args:" + Arrays.toString(args));
                    proceed = pjp.proceed();
                    System.out.println("==>日志:" + methodName + "()之后,结果:" + proceed);
                } catch (Throwable throwable) {
                    System.out.println("==>日志:" + methodName + "()之后,结果:" + throwable);
                    throwable.printStackTrace();
                }finally {
                    System.out.println("==>日志:" + methodName + "()之后,参数args:" + Arrays.toString(args));
                }
                return proceed;
            }
        
          - 环绕通知必须使用JoinPoint子接口:ProceedingJoinPoint,因为需要我们
              使用pjp.proceed(),手动调用代理对象的目标方法,
          - 环绕通知,必须将目标方法的结果,返回(必须有返回值类型)
        

切入点表达式

  • 语法: execution(public double com.atguigu.spring.aopimpl.CalcImpl.add(double,double))
  • " * "的用法
    " * "可以代表,任意的访问修饰符及返回值类型
    " * "可以代表,任意类的全类名
    " * "可以代表,任意方法名称
  • "…"的用法
    "…"在方法的参数中使用,表示任意数据类型,任意数量的参数列表。
  • 重(重复)用切入点表达式
    1. 提取相同的切入点表达式,如下:
      	/**
           * 重用切入点表达式
           */
          @Pointcut(value="execution(* com.atguigu.spring.aopimpl.*.*(..))")
          public void puintCut(){}
      
    2. 在指定的通知内,引入提取后的切入点表达式
         @Before(value="myPointCut()")
      

切面的优先级

  • 语法:@Order(int num)
    num 数值越小,优先级越高。
    num默认值为int类型的最大值。

基于XML配置AspectJ的applicationContext.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">

<!--    装配CalcImpl(目标对象)-->
    <bean id="calcImpl" class="com.atguigu.spring.aopxmlimpl.CalcImpl"></bean>

<!--    装配切面类MyLogging-->
    <bean id="myLogging" class="com.atguigu.spring.aopxmlimpl.MyLogging"></bean>

<!--    装配切面类MyValidata-->
    <bean id="myValidata" class="com.atguigu.spring.aopxmlimpl.MyValidata"></bean>

<!--    基于xml配置AOP(通知)-->
    <aop:config>

        <!--配置切入点表达式-->
        <aop:pointcut id="myPointCut" expression="execution(* com.atguigu.spring.aopxmlimpl.*.*(..))"/>

        <!-- 配置日志切面-->
        <aop:aspect id="myLoggingAspect" ref="myLogging" order="2">
            <!-- 前置通知-->
            <aop:before method="methodBefore" pointcut-ref="myPointCut"></aop:before>
            <!-- 后置通知-->
            <aop:after method="methodAfter" pointcut-ref="myPointCut"></aop:after>
            <!--返回通知-->
            <aop:after-returning method="methodAfterReturning" pointcut-ref="myPointCut" returning="data"></aop:after-returning>
            <!-- 异常通知-->
            <aop:after-throwing method="methodAfterThrowing" pointcut-ref="myPointCut" throwing="ex"></aop:after-throwing>
        </aop:aspect>

        <!-- 配置验证切面-->
        <aop:aspect id="myValidataAspect" ref="myValidata" order="1">
            <!--前置通知-->
            <aop:before method="methodBeforeValidata" pointcut-ref="myPointCut"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值