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注解版的使用


猜你喜欢

转载自www.cnblogs.com/xiaohuiduan/p/9899442.html
今日推荐