浅谈AOP

AOP,面向切面编程,作为OOP的一种补充,在处理一些系统共有的业务,比如日志,事务等,提供了一种比OOP更佳的解决方案。

在OOP中,控制的粒度为对象,因此,对象中也就参杂这不属于本身业务主体的一下系统共有的业务:

登陆提供了如下接口:

package me.hockey.spring.aoptest;

public interface ILogin {
    String loginByname(String name);
}

 

例如以一个简单的硬编码的登陆为例子,在未使用AOP前纪录日志的工作都是嵌套在业务中间的,如下所示:

package me.hockey.spring.aoptest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

public class LoginWithOutAOP implements ILogin{
    private Logger logger = LoggerFactory.getLogger(Login.class);
    public String loginByname(String name) {
        Assert.notNull(name);        
        logger.info("Login by name invoke! args:" + name);
        if("he".equals(name)){
            logger.info("Login ok:" + name);
            return "login ok";    
        } else {
            logger.info("Login failed:" + name);
            return "login failed";
        }        
    }
}

 

可以看到,在没有使用AOP前,日志记录在以上的代码中占据了四行位置。其实日志记录在很多地方都会使用到,这样,无疑就将与具体业务无关的日志代码引入到了具体的业务中了,而且日志代码也影响了代码的简洁性。

AOP提供了更细粒度的控制,同样使用以上例子,使用AOP时,无须在业务中添加关于日志的代码,如下:

package me.hockey.spring.aoptest;

import org.springframework.util.Assert;

public class Login implements ILogin{
    public String loginByname(String name) {
        Assert.notNull(name);
        if("he".equals(name)){
            return "login ok";    
        } else {
            return "login failed";
        }
    }    
}

 

这样所有的代码都与本业务相关了,没有多余的代码,代码的简洁性也得到了保证。

当然,要使用AOP,还要对AOP进行相关的配置,关于AOP的一些术语,join point(连接点)、point cut(切入点)、advice(通知)、aspect(方面)、introduce(引入),就不再过多论述了。本文使用SpringAspectj来对AOP进行配置。

Spring对AOP有很好的支持,在maven中添加对Spring-aop、Spring-context和aspectj的依赖。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>3.0.0.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>3.0.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.5.0_M5</version>
</dependency>

日志使用slf4jlog4j,同样添加两者的依赖

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.5</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.5</version>            
</dependency>
<dependency>
    <groupId>aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.5.0_M5</version>
</dependency>

一个很简单的日志记录,

import org.aspectj.lang.JoinPoint;

/**
 * AOP 日志接口
 * @author Hockey
 *
 */
public interface IAopLog {
    void logBefore(JoinPoint jointPoint);
    void logAfter(JoinPoint jointPoint);
    void logAfterReturn(JoinPoint jointPoint,Object o);
    void logAfterThrow(JoinPoint jointPoint,Throwable tr);
}

 

日志类的实现如下:

package me.hockey.spring.apotest.utils;

import java.util.Date;

import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AopLogImpl implements IAopLog{
    
    Logger logger = LoggerFactory.getLogger(AopLogImpl.class);

    public void logBefore(JoinPoint jointPoint) {
        
        logger.info("before "+new Date().toString()+
                jointPoint.getTarget().toString()+
                jointPoint.getSignature().getName()+
                jointPoint.getArgs().toString());
        }

    public void logAfter(JoinPoint jointPoint) {
        logger.info("After "+new Date().toString()+
                jointPoint.getTarget().toString()+
                jointPoint.getSignature().getName()+
                jointPoint.getArgs().toString());
    }

    public void logAfterReturn(JoinPoint jointPoint,Object o) {
        logger.info("AfterReturn "+new Date().toString()+
                jointPoint.getTarget().toString()+
                jointPoint.getSignature().getName()+
                jointPoint.getArgs().toString()
                +o.toString());
    }

    public void logAfterThrow(JoinPoint jointPoint,Throwable tr) {
        logger.info("AfterThrow "+new Date().toString()+
                jointPoint.getTarget().toString()+
                jointPoint.getSignature().getName()+
                jointPoint.getArgs().toString()+
                tr.getMessage());
    }

}

 

applicationcontext.xml的配置:

添加AOP的xsd

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx">
    <bean id="login" class="me.hockey.spring.aoptest.Login"/>
    <bean id="aopLog" class="me.hockey.spring.apotest.utils.AopLogImpl"/>
    <aop:config>
        <aop:pointcut id="loginPointCut" expression="execution(* me.hockey.spring.aoptest.Login.*(..))"/>
        <aop:aspect id="loginAspect" ref="aopLog">
            <aop:before method="logBefore" pointcut-ref="loginPointCut"/>
            <aop:after method="logAfter"  pointcut-ref="loginPointCut" />
            <aop:after-returning method="logAfterReturn" returning="o" pointcut-ref="loginPointCut"/>
            <aop:after-throwing method="logAfterThrow"  throwing="tr" pointcut-ref="loginPointCut"/>
        </aop:aspect>
    </aop:config>
</beans>

测试类代码:

package me.hockey.spring.aoptest.test;

import me.hockey.spring.aoptest.ILogin;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationcontext/ApplicationContext.xml");
        ILogin login = (ILogin) ac.getBean("login");
        login.loginByname("he");
        login.loginByname(null);
    }
}

日志记录结果:

[INFO ] 2013-12-30 04:41:25,545(0) --> [main] org.springframework.context.support.AbstractApplicationContext.prepareRefresh(AbstractApplicationContext.java:447): Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1950198: startup date [Mon Dec 30 04:41:25 CST 2013]; root of context hierarchy
[INFO ] 2013-12-30 04:41:25,598(53) --> [main] org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:315): Loading XML bean definitions from class path resource [applicationcontext/ApplicationContext.xml]
[INFO ] 2013-12-30 04:41:25,775(230) --> [main] org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:532): Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1f98d58: defining beans [login,aopLog,org.springframework.aop.config.internalAutoProxyCreator,loginPointCut,org.springframework.aop.aspectj.AspectJPointcutAdvisor#0,org.springframework.aop.aspectj.AspectJPointcutAdvisor#1,org.springframework.aop.aspectj.AspectJPointcutAdvisor#2,org.springframework.aop.aspectj.AspectJPointcutAdvisor#3]; root of factory hierarchy
[INFO ] 2013-12-30 04:41:26,044(499) --> [main] me.hockey.spring.apotest.utils.AopLogImpl.logBefore(AopLogImpl.java:15): before Mon Dec 30 04:41:26 CST 2013me.hockey.spring.aoptest.Login@1586cbdloginByname[Ljava.lang.Object;@1e2c9bf
[INFO ] 2013-12-30 04:41:26,044(499) --> [main] me.hockey.spring.apotest.utils.AopLogImpl.logAfter(AopLogImpl.java:22): After Mon Dec 30 04:41:26 CST 2013me.hockey.spring.aoptest.Login@1586cbdloginByname[Ljava.lang.Object;@1e2c9bf
[INFO ] 2013-12-30 04:41:26,045(500) --> [main] me.hockey.spring.apotest.utils.AopLogImpl.logAfterReturn(AopLogImpl.java:29): AfterReturn Mon Dec 30 04:41:26 CST 2013me.hockey.spring.aoptest.Login@1586cbdloginByname[Ljava.lang.Object;@1e2c9bflogin ok
[INFO ] 2013-12-30 04:41:26,045(500) --> [main] me.hockey.spring.apotest.utils.AopLogImpl.logBefore(AopLogImpl.java:15): before Mon Dec 30 04:41:26 CST 2013me.hockey.spring.aoptest.Login@1586cbdloginByname[Ljava.lang.Object;@10e9df
[INFO ] 2013-12-30 04:41:26,045(500) --> [main] me.hockey.spring.apotest.utils.AopLogImpl.logAfter(AopLogImpl.java:22): After Mon Dec 30 04:41:26 CST 2013me.hockey.spring.aoptest.Login@1586cbdloginByname[Ljava.lang.Object;@10e9df
[INFO ] 2013-12-30 04:41:26,045(500) --> [main] me.hockey.spring.apotest.utils.AopLogImpl.logAfterThrow(AopLogImpl.java:37): AfterThrow Mon Dec 30 04:41:26 CST 2013me.hockey.spring.aoptest.Login@1586cbdloginByname[Ljava.lang.Object;@10e9df[Assertion failed] - this argument is required; it must not be null

 

至此,一个简单的AOP记录日志的功能已经实现了,当然,只要在Spring配置文件里面配置好,日志这一个功能可以被很多地方复用。

 

 

posted @ 2013-12-30 04:53  Be a programer  阅读(315)  评论(0编辑  收藏  举报