6.spring AOP

Posted yuanGrowing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了6.spring AOP相关的知识,希望对你有一定的参考价值。

6.1 aop的概念

       6.1.1
    aop里面切面、切点的定义什么的我就不说,网上多如牛毛,我就记录一下自己对aop概念和流程的理解吧。
    spring里的切面编程,浅显的讲就是你在调用某个方法的时候,程序会自动先执行某个方法,执行完你调用的方法之后再又自动的执行某个方法。
这样就完成了一次切面编程,其实过程很简单。
    假设我们调用了A类里面的方法a,这时候程序会在执行a之前去调用B类里面的方法b,执行完a之后会去调用B类里面的d,我们用这个例子来看看aop里面的概念。这里的方法a就是一个pointcut(切入点),而b和d就是连个advice(通知),a是前置通知,b是后置通知。还有一个很重要的aspect(切面概念)是什么呢,我觉得是切入点和通知的整体,如下图,这整个是一个aspect 。

    6.1.2 前后置的advice
    现在的网站都能够统计在线的人数,这个是怎么做的呢?真实的项目中是怎么做的我不知道,但是我觉得用spring-aop可以很好的做到这个功能。
    首先需要一个登陆的类(里面有登陆和登出的方法,先只用登陆的):
public interface ILogin 
    void login(User user);
    void logout(User user);
    实现类:
public class Login implements ILogin
    public void login(User user)
        System.out.println(user.getName()+"登陆");
    
    public void logout(User user)
        System.out.println(user.getName()+"登出");
    
    还需要一个用来做切面的类,number用来统计登陆人数。
public class Count 
    static int number = 0;
    public void beforeuserlogin()
        number++;
        System.out.println("当前在线人数:"+number);
    
    public void afteruserlogin()
        System.out.println("login end");
    
    配置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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="login" class="com.example.aop.Login"/>
        <bean id="count" class="com.example.aop.Count"/>

        <aop:config>
            <aop:aspect id="logaspect" ref="count"> <!-- 我把这个ref理解为切面的制造者,就是拥有advice的类 -->
                <aop:pointcut id="loginpointcut" expression="execution(* com.example.aop.ILogin.login(..))"/>
                <aop:before method="beforeuserlogin" pointcut-ref="loginpointcut"/>
                <aop:after method="afteruserlogin" pointcut-ref="loginpointcut"/>
            </aop:aspect>
        </aop:config>

</beans>
    这个配置文件的意思如下图:


一旦调用login方法,就会执行前后置的advice,就能完成人数统计了。
测试代码:

ApplicationContext app = new ClassPathXmlApplicationContext("spring-main.xml");
ILogin login = (ILogin) app.getBean("login");
User user = new User();
user.setName("mj");
login.login(user);
User user1 = new User();
user1.setName("lili");
login.login(user1);
    结果:
当前在线人数:1
mj登陆
login end
当前在线人数:2
lili登陆
login end
    6.1.3 around advice
    能统计登陆的人数,当然还得能统计登出的人数了,不然人数不是一直在增长了。现在使用一种新的advice-----around advice
    使用这个advice可以同时实现前后置advice。
    我们在Count里面增加方法aroundlogout作为around advice。(这里的ProceedingJoinPoint指的就是定义的切入点,
joinPoint.proceed()这个就是执行切入点函数)
public Object aroundlogout(ProceedingJoinPoint joinPoint)
    System.out.println("logout start");
    Object object = null;
    try 
        object = joinPoint.proceed();
     catch (Throwable throwable) 
        throwable.printStackTrace();
    
    number--;
    System.out.println("logout end");
    System.out.println("当前在线人数:"+number);
    return object;
    修改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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="login" class="com.example.aop.Login"/>
        <bean id="count" class="com.example.aop.Count"/>

        <aop:config>
            <aop:aspect id="logaspect" ref="count"> <!-- 我把这个ref理解为切面的制造者,就是拥有advice的类 -->
                <aop:pointcut id="loginpointcut" expression="execution(* com.example.aop.ILogin.login(..))"/>
                <aop:before method="beforeuserlogin" pointcut-ref="loginpointcut"/>
                <aop:after method="afteruserlogin" pointcut-ref="loginpointcut"/>
                <aop:around method="aroundlogout" pointcut="execution(* com.example.aop.ILogin.logout(..))"/>
                <!-- 上面这一句等于下面这两句的综合 -->
                <!--<aop:pointcut id="logoutpointcut" expression="execution(* com.example.aop.ILogin.logout(..))"/>-->
                <!--<aop:around method="aroundlogout" pointcut-ref="logoutpointcut"/>-->
            </aop:aspect>
        </aop:config>
</beans>
    测试代码:
ApplicationContext app = new ClassPathXmlApplicationContext("spring-main.xml");
ILogin login = (ILogin) app.getBean("login");
User user = new User();
user.setName("mj");
login.login(user);
User user1 = new User();
user1.setName("lili");
login.login(user1);
login.logout(user);
    结果:
当前在线人数:1
mj登陆
login end
当前在线人数:2
lili登陆
login end
logout start
mj登出
logout end
当前在线人数:1
    6.1.4   afterreturn advice
    这个advice是在切入点函数执行完之后执行,它可以获取到切入点函数的返回值,用来对返回值进行一定的格式化等,afterreturn advice在after advice之后执行。为了看看它的效果,修改logout接口,让它返回一个StringBuffer(本来想用String来演示的,但是发现String演示不了,如果logout函数返回一个String的话,不能被afterreturn advice改变,我想这个和String变量在java中的特殊性有关):        
public class Login implements ILogin
    public void login(User user)
        System.out.println(user.getName()+"登陆");
    
    public StringBuffer logout(User user)
        System.out.println(user.getName()+"登出");
        return new StringBuffer("logout");
    
    修改Count,增加一个方法,它会接收切入点返回的值,然后修改:
public void afterReturn(StringBuffer s)
    s.append(" after return");
    xml文件,就是加入了一个after-returning的配置(returning后面跟afterReturn函数的入参名称):
<aop:config>
    <aop:aspect id="logaspect" ref="count"> <!-- 我把这个ref理解为切面的制造者,就是拥有advice的类 -->
        <aop:pointcut id="loginpointcut" expression="execution(* com.example.aop.ILogin.login(..))"/>
        <aop:before method="beforeuserlogin" pointcut-ref="loginpointcut"/>
        <aop:after method="afteruserlogin" pointcut-ref="loginpointcut"/>
        <aop:around method="aroundlogout" pointcut="execution(* com.example.aop.ILogin.logout(..))"/>
        <!-- 上面这一句等于下面这两句的综合 -->
        <!--<aop:pointcut id="logoutpointcut" expression="execution(* com.example.aop.ILogin.logout(..))"/>-->
        <!--<aop:around method="aroundlogout" pointcut-ref="logoutpointcut"/>-->
        <aop:after-returning method="afterReturn" returning="s" pointcut="execution(* com.example.aop.ILogin.logout(..))"/>
    </aop:aspect>
</aop:config>
    测试代码:
ApplicationContext app = new ClassPathXmlApplicationContext("spring-main.xml");
ILogin login = (ILogin) app.getBean("login");
User user = new User();
user.setName("mj");
login.login(user);
User user1 = new User();
user1.setName("lili");
login.login(user1);
System.out.println(login.logout(user));
    从代码上看最后一行是不是应该输出 logout ?
结果:
当前在线人数:1
mj登陆
login end
当前在线人数:2
lili登陆
login end
logout start
mj登出
logout end
当前在线人数:1
logout after return
    6.2 在通知函数里面获取参数
        注意,这里的参数似乎只能是切入点函数的参数或者切入点所在的那个类
        6.2.1  利用JoinPoint类进行传参
            在上面的例子中我们用到了ProceedingJoinPoint这个类,这是个JoinPoint的子类,利用它我们就可以获取切入点相关的信息,我们修改aroundlogout这个函数,在里面获取切入点的信息(其它都不变)
public Object aroundlogout(ProceedingJoinPoint joinPoint)
    System.out.println("logout start");
    System.out.println("获取切入点的信息,切入点所在类:"+joinPoint.getThis());
    Object object = null;
    try 
        object = joinPoint.proceed();
     catch (Throwable throwable) 
        throwable.printStackTrace();
    
    number--;
    System.out.println("logout end");
    System.out.println("当前在线人数:"+number);
    return object;
    结果:
当前在线人数:1
mj登陆
login end
当前在线人数:2
lili登陆
login end
logout start
获取切入点的信息,切入点所在类:com.example.aop.Login@470f1802
mj登出
logout end
当前在线人数:1
logout after return
    我们如果想要用JoinPoint进行传参的话,只要把advice函数的第一个参数设置为JoinPoint就可以了,必须是第一个参数!
比如我们修改beforelogin:
public void beforeuserlogin(JoinPoint joinPoint)
    number++;
    System.out.println("获取切入点的信息,切入点所在类:"+joinPoint.getThis());
    System.out.println("当前在线人数:"+number);

修改测试代码:

ApplicationContext app = new ClassPathXmlApplicationContext("spring-main.xml");
ILogin login = (ILogin) app.getBean("login");
User user = new User();
user.setName("mj");
login.login(user);

结果:

获取切入点的信息,切入点所在类:com.example.aop.Login@470f1802
当前在线人数:1
mj登陆
login end
JoinPoint还提供了很多方法,可以自己试验一下。
    6.2.2  利用配置文件传参
        假设我们在afteruserlogin这个函数里面要用到切入点login的参数user,我们可以用joinpoint提供的getArgs方法,也可以利用配置文件单独的将user传入到afteruserlogin。
   修改afteruserlogin:
public void afteruserlogin(User user)
    System.out.println(user.getName()+"  login end");
    修改xml:
<aop:after method="afteruserlogin" pointcut="execution(* com.example.aop.ILogin.login(..)) and args(user)"/>
    运行6.2.1中的测试代码:
获取切入点的信息,切入点所在类:com.example.aop.Login@3e92efc3
当前在线人数:1
mj登陆
mj  login end
    (如果参数很多也没问题,在args里面用“,”隔开就行了。)
end:
    spring还提供了AspectJ 形式的切面编程,用的都是注解的形式,还提供了一种名为Introductions的切面编程(可以把一个类做一个切面,向里面添加接口),这些以后慢慢来吧。

以上是关于6.spring AOP的主要内容,如果未能解决你的问题,请参考以下文章

spring框架学习6:spring-aop的五种通知类型

6.Spring中AOP术语与XML方式简单实现

Spring-AOP切面编程

Spring框架参考手册翻译——第三部分 核心技术 6.1 Spring IoC容器和bean的介绍

Spring面试,IoC和AOP的理解

Spring面试,IoC和AOP的理解