zoukankan      html  css  js  c++  java
  • Spring的第三天AOP之xml版

    Spring的第三天AOP之xml版

    AOP介绍

    AOP(Aspect Oriented Programming),面向切面编程。它出来的目的并不是去取代oop,而是对它的完善和补充。在oop中,人们的是去定义纵向的关系,但是会出现一个问题:在程序中,日志代码往往是横向的地散布在各种对象层次中,而在oop的模式设计中,导致了大量重复工作的代码。

    可以这样说:oop是面向名词领域,AOP是面向动词领域。AOP适合通用的工作,不适合个性化的工作。

    图来自网络,侵删

     


     

     

    在AOP中,我们将那些与多个类相关的行为放在一起变成一个模块,命名为Aspect【切面】。讲个故事:

    村里来了一个通告,以前是到每家每户去通知,假如通告进行了改变,又要重新进行通知,然后村里面的人觉得太麻烦了,就做了一个声音传输管道,每当有通告来的时候,村长就选择要通知的,告诉他们某个时间去做通告里面的东西,然后打开管道进行通知。

    • 通告:通知(Advice)

      就是你想要的东西,比如说日志,事物。

    • 人:PointCut【切入点】

      切入点里面定义了Advice发生的地点,例如某个类或方法的名称,为被切的地方。

    • 时间:Joinpoint【连接点】

      连接点就是告诉程序什么时候去使用通知,例如当方法被调用时,或者是异常抛出时。

    • 村长:Proxy【代理】
      Proxy不能算是AOP的家庭成员,它是一个管理部门,管理AOP如何融入OOP,是AOP的实践者。同时AOP的代理离不开spring的IOC容器。在Spring Framework中,AOP代理是JDK动态代理或CGLIB代理。

    那么什么是切面呢?
    切面其实就是Advice+PointCut,代表了切面的所有元素,而将切面织入代码中就是依靠Proxy

    maven依赖

    导入Spring依赖和aop依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.0.8.RELEASE</version>
        <type>pom</type>
    </dependency>
    
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.6.11</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.1.4.RELEASE</version>
    </dependency>
    

    头部文件xmlns文件配置

    <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"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
    </beans>
    

    类的java代码

    package com.weno.pojo;
    
    public class User {
          public void print(){
            System.out.println("我这是在执行一个方法");
            throw new RuntimeException("我故意的报错");
        }
    
        public String msg(){
            return "竟然有返回值";
        }
    }
    

    建议类:

    package com.weno.aop;
    
        public void beforeMethod(){
            System.out.println("一千年以前");
        }
    
        public void afterMethod(){
            System.out.println("一千年以后");
        }
        
        public void returnMethod(Object rvt){
            System.out.println("返回值>>>>>>"+rvt);
            System.out.println("方法返回");
        }
    
        public void errorMethod(){
            System.out.println("程序竟然报错了");
        }
    }
    

    xml版本的使用

    首先先说一下切入点,切入点分为:

    • 前置通知:在连接点之前运行但无法阻止执行流程进入连接点的建议(除非它引发异常,该异常将中断当前方法链的执行而返回)。

    • 后置通知:在连接点正常完成后运行的建议(例如,如果方法返回而不抛出异常)。

    • 异常通知:如果方法通过抛出异常退出,则执行建议。

    • 后置最终通知:无论连接点退出的方式(正常或异常返回),都要执行建议。

    • 环绕通知:环绕在连接点处的方法所执行的通知,环绕通知可以在方法调用之前和之后自定义任何行为,并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等等。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。

    前置通知,后置通知,异常通知,返回通知

    <bean class="com.weno.pojo.User" id="user"/>
    <bean class="com.weno.aop.Method" id="method"/>
    
    <aop:config>
        <!--定义一个切面-->
        <aop:aspect ref="method">
    
            <!--切点,定义切点的id exexution后面写要切入的地点:在print这个方法进行切-->
            <aop:pointcut id="positon" expression="execution(* com.weno.pojo.User.print())"/>
            <!-- 切的时间 method表示切的方法 -->
            <!-- beforeMethod 在执行方法之前切入 -->
            <aop:before method="beforeMethod" pointcut-ref="positon"/>
            <!-- 在执行方法之后切入 -->
            <aop:after method="afterMethod" pointcut-ref="positon"/>
            <!-- 在方法报错的时候切入 -->
            <aop:after-throwing method="errorMethod" pointcut-ref="positon"/>
            <!-- 在方法有返回值的时候切 
                 同时可以加上returning,将值传给returnMethod()方法
            -->
            <aop:after-returning method="returnMethod" returning="rvt" pointcut="execution(* com.weno.pojo.User.msg())"/>
        </aop:aspect>
    </aop:config>
    

    测试代码一:

    @Test
    public void m01(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/spring-test1.xml");
        // 在这里,如果直接通过new实例化User,那么aop功能将失效,因为AOP是要在spring IOC容器里面实现的
        User user = ctx.getBean("user",User.class);
        user.print();
    }
    

    输出结果

    一千年以前
    我这是在执行一个方法
    一千年以后
    程序竟然报错了
    

    测试代码二:

    @Test
    public void m03(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/spring-test1.xml");
        User user = ctx.getBean("user",User.class);
        user.msg();
    }
    

    结果:

    返回值>>>>>>竟然有返回值
    方法返回
    

    环绕通知

    环绕通知是所有通知类型中功能中最为强大的,能够全面地控制连接点,甚至能够控制方法是否执行,同时,他还可以实现before和after的功能。

    切面程序的代码

    public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    
        //允许程序进行执行
        proceedingJoinPoint.proceed();
    }
    
    public void aroundMethod2(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //允许程序进行执行,并将其参数进行改变。
        proceedingJoinPoint.proceed(new String[]{"你最帅"});
    }
    
    

    被切的程序

    public void msg1(){
        System.out.println("这是米有参数的msg");
    }
    
    public void msg2(String msg){
        System.out.println("进行执行方法输出"+msg);
    }
    

    xml文件配置

    <aop:around method="aroundMethod" pointcut="execution(* com.weno.pojo.User.msg1())"/>
    <aop:around method="aroundMethod2" pointcut="execution(* com.weno.pojo.User.msg2(..))"/>
    

    测试文件

    
    @Test
    public void m04() {
    
        ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/spring-test1.xml");
        User user = ctx.getBean("user", User.class);
    
        user.msg1();
        user.msg2("我好帅");
    }
    

    结果

    这是米有参数的msg
    //在这里面,参数进行了改变,由我好帅变成了你最帅
    进行执行方法输出你最帅
    

    JoinPoint的神奇之处

    官方文档

    这个是官方文档截取过来的

    使用上面的那个例子来获得参数:

    public void aroundMethod2(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object[] args = proceedingJoinPoint.getArgs();
            System.out.println(Arrays.toString(args));
        //允许程序进行执行,并将其参数进行改变。
        proceedingJoinPoint.proceed(new String[]{"你最帅"});
    }
    
    

    输出结果当然是:

    这是米有参数的msg
    [我好帅]
    进行执行方法输出你最帅
    

    当然,如果你将Object[] args = proceedingJoinPoint.getArgs(); System.out.println(Arrays.toString(args));放在后面,那输出参数当然就变成了[你最帅]。

    基本数据类型和包装类对于execution严格区分

    首先先简单的介绍一下execution,先定义一个表达式:

    execution(* com.weno...(..))

    在这里面

    标识符含义
    execution 表达式的主体
    第一‘*’号 表示返回值的类型,*号代表任意类型
    com.weno 代表包,被切的地方
    包后面的‘..’ 代表当前包及其子包
    第二个‘*’号 代表类,*号代表所有类
    第三个‘*’号 代表方法,‘*’代表任意方法
    (..) 括号里面表示参数,两个点表示任意参数,也可以不加

    在execution表达式中,参数严格区分基本数据类型和包装类。例如:

    在com.weno.pojo.User.hasAge()中

    public void hasAge(Integer age){
    }
    
    <!-- 这样是可以切到的 -->
    <aop:before method="beforeAge" pointcut="execution(* com.weno.pojo.User.hasAge(Integer))"/>
    
    <!-- 加入将Integer换成int,那么,无法执行切面 -->
    <aop:before method="beforeAge" pointcut="execution(* com.weno.pojo.User.hasAge(int))"/>
    

    好了,Spring的第三天就到这里了。明天就星期六了,IG加油(ง •_•)ง

     


     

     

    在下一篇博客中,我将介绍一下aop注解版的使用

     


     

     

  • 相关阅读:
    多线程
    Java I/O简述
    Java集合
    Java常用类
    Java面向对象基础知识汇总
    Java标识符和关键字
    认识Java
    mvn打包源码和生成javadoc
    Http协议概述
    exe可执行程序及堆栈分配(转载)
  • 原文地址:https://www.cnblogs.com/xiaohuiduan/p/9899442.html
Copyright © 2011-2022 走看看