本文实现了,自定义个注解,用来标注切入点,就是说,你想让哪些个方法执行切面的方法,只需要在这些方法上面,添加自定义注解,然后,就可以执行切面的advice啦。

我们在切面可以拿到:

1,当前执行方法的参数。

2,自定义注解上定义的参数。

3,顺便获得当前session里面的用户吧。

要在spring mvc里面集成aop,那么就得先看如何完善配置文件。

这有个前提。

就是你的项目已经是spring mvc啦,我这就不逼逼spring mvc需要的配置啦,下面只说aop相关的配置

首先是:applicationContext.xml

引入命名空间,aop相关的就是下面的2行,为了方便观众,我就把整个的命名空间给贴在下面。

xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.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" 
       xmlns:context="http://www.springframework.org/schema/context" 
       xmlns:mvc="http://www.springframework.org/schema/mvc" 
       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-3.0.xsd 
            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd 
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
命名空间完了之后,就是aop的注解,我这顺便把这个spring 的扫描也给写这,是因为,声明切面的时候,这个切面也是要声明成一个组件,交给spring 容器帮忙处理一下的。

要是有些老铁,不了解原理的话,直接就只扫描controller或者service文件夹,那不就扫描不到这个切面类了吗,那就运行不起来啦。那就尴尬啦。后面,我会上我的整个测试项目的文件目录概览图,仅供参考吧。

    <!-- 开启spring的扫描注入,使用如下注解 --> 
    <!-- @Component,@Repository,@Service,@Controller--> 
    <context:component-scan base-package="com.lxk"/> 
 
    <!-- aop 注解实现 --> 
    <aop:aspectj-autoproxy/>

然后是:pom.xml引入AOP的jar依赖

        <!-- AspectJ --> 
        <dependency> 
            <groupId>org.aspectj</groupId> 
            <artifactId>aspectjrt</artifactId> 
            <version>1.6.10</version> 
        </dependency> 
        <dependency> 
            <groupId>org.aspectj</groupId> 
            <artifactId>aspectjweaver</artifactId> 
            <version>1.7.2</version> 
        </dependency>
这地方,可能有老铁,会只是单独引入上面那个,不过,你跟着我的教程来,那肯定不会出下面这个错啦。

aspectj 使用spring AOP切面编程的时候报错:ReflectionWorld$ReflectionWorldException NoClassDefFoundError 的处理

看看在项目里面,这个jar包的依赖关系图


可以看到,引入的2个jar包是直接被这个项目依赖的,后面没有出现复杂的嵌套依赖,这依赖关系,一目了然。清晰。。。

想知道,这个图怎么来吗?看下面链接。

Intellij IDEA 中如何查看maven项目中所有jar包的依赖关系图


自定义注解:MethodLog

package com.lxk.annotation; 
 
import java.lang.annotation.*; 
 
/** 
 * 记录数据库相关的操作如:新建,更新,删除。 
 * 
 * @author lxk on 2017/12/7 
 */ 
@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.METHOD}) 
@Documented 
public @interface MethodLog { 
    /** 
     * 记录操作描述 
     */ 
    String description() default ""; 
 
    /** 
     * 增删改的数据的类型 
     */ 
    Class<?> clazz(); 
}

这个注解,是使用在方法上的。对于注解的使用,还是白板的小伙伴,麻烦移步到下面这个链接,简单瞅瞅,这个注解是怎么声明,怎么使用的

注解之概念的理解

好,注解定义OK之后,就是该使用了,咱先看切面的代码吧。

切面类:CalendarAspect.java

package com.lxk.aop; 
 
import com.lxk.annotation.MethodLog; 
import com.lxk.httpModel.SessionInfo; 
import com.lxk.model.User; 
import org.aspectj.lang.JoinPoint; 
import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.*; 
import org.springframework.stereotype.Component; 
import org.springframework.web.context.request.RequestContextHolder; 
import org.springframework.web.context.request.ServletRequestAttributes; 
 
import javax.servlet.http.HttpServletRequest; 
import java.lang.reflect.Method; 
 
/** 
 * 日历的日志切面操作 
 * 
 * @author lxk on 2017/12/7 
 */ 
@Component 
@Aspect 
public class CalendarAspect { 
 
    //声明个切面,切哪呢?切到 com.lxk.service.StudentService 这个目录下,以save开头的方法,方法参数(..)和返回类型(*)不限 
    @Pointcut("execution(* com.lxk.service.StudentService.save*(..))") 
    private void aa() { 
    }//切入点签名 
 
    /** 
     * 前置通知 
     */ 
    @Before("aa()") 
    public void beforeMethod(JoinPoint joinPoint) { 
        System.out.println("before method   start ..."); 
 
        System.out.println("before method   end ..."); 
    } 
 
    /** 
     * 环绕通知 around advice 
     * 这个切的是 com.lxk.service 包下面以及子包下所有,后面又 && 同时满足带有注解 MethodLog 
     */ 
    @Around(value = "(execution(* com.lxk.service..*(..))) && @annotation(methodLog)", argNames = "joinPoint, methodLog") 
    public Object methodAround(ProceedingJoinPoint joinPoint, MethodLog methodLog) throws Throwable { 
        System.out.println("Around method    start......................."); 
        User user = getUserFromSession(); 
        if (user != null) { 
            System.out.println("Around method                " + user.toString()); 
        } 
        //获得自定义注解的参数 
        System.out.println("Around method  methodLog 的参数,remark:" + methodLog.description() + " clazz:" + methodLog.clazz()); 
        //执行目标方法,并获得对应方法的返回值 
        Object result = joinPoint.proceed(); 
        System.out.println("Around method     返回结果:" + result); 
        System.out.println("Around method          end......................."); 
        return result; 
    } 
 
    /** 
     * 最终通知 after advice 
     * 使用的是在上面声明的切面,并且带上个注解,意思是除了满足上面aa()方法的条件还得带上注解才OK 
     */ 
    @After(value = "aa() && @annotation(methodLog)", argNames = "joinPoint, methodLog") 
    public void methodAfter(JoinPoint joinPoint, MethodLog methodLog) throws Throwable { 
        System.out.println("After method     start......................."); 
        //获得自定义注解的参数 
        System.out.println("After method   methodLog 的参数,remark:" + methodLog.description() + " clazz:" + methodLog.clazz()); 
        MethodLog remark = getMethodRemark(joinPoint); 
        System.out.println("After method        end......................."); 
    } 
 
    /** 
     * 后置通知 
     * 
     */ 
    @AfterReturning(value = "(execution(* com.lxk.service..*(..))) && @annotation(methodLog)", argNames = "joinPoint, methodLog, result", 
            returning = "result") 
    public void methodAfterReturning(JoinPoint joinPoint, MethodLog methodLog, Object result) throws Throwable { 
        System.out.println("AfterReturning method    start......................."); 
        System.out.println("AfterReturning method   返回的结果:" + result); 
        User user = getUserFromSession(); 
        if (user != null) { 
            System.out.println("AfterReturning  " + user.toString()); 
        } 
        System.out.println("AfterReturning method       end......................."); 
    } 
 
    /** 
     * 从session里面获得user对象 
     */ 
    private User getUserFromSession() { 
        //获取到当前线程绑定的请求对象 
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 
        //已经拿到session,就可以拿到session中保存的用户信息了。 
        User user = null; 
        try { 
            SessionInfo sessionInfo = (SessionInfo) request.getSession().getAttribute("sessionInfo"); 
            user = sessionInfo.getUser(); 
        } catch (Exception ignored) { 
 
        } 
        return user; 
    } 
 
    /** 
     * 获取方法的中文备注____用于记录用户的操作日志描述 
     */ 
    private MethodLog getMethodRemark(JoinPoint joinPoint) throws Exception { 
        //返回目标对象 
        Object target = joinPoint.getTarget(); 
        String targetName = target.getClass().getName(); 
        //返回当前连接点签名 
        String methodName = joinPoint.getSignature().getName(); 
        //获得参数列表 
        Object[] arguments = joinPoint.getArgs(); 
 
        Class targetClass = Class.forName(targetName); 
        Method[] method = targetClass.getMethods(); 
        //这个怎么这么low呢。 
        for (Method m : method) { 
            if (m.getName().equals(methodName)) { 
                Class[] tmpCs = m.getParameterTypes(); 
                if (tmpCs.length == arguments.length) { 
                    MethodLog methodCache = m.getAnnotation(MethodLog.class); 
                    if (methodCache != null && !("").equals(methodCache.description())) { 
                        return methodCache; 
                    } 
                    break; 
                } 
            } 
        } 
        return null; 
    } 
} 
这个是切面的代码,在aop里面切面的代码,英文对应advice,通知。大概有有如上,前置,后置,最终,环绕,还有个异常,我这个切面没体现,

上面这个代码很多,没使用过的,肯定都不知道啥是啥,当然我也是这么个状态。

分享点东西。酌情看看,你是否需要看一下。

1,execution(* com.lxk.service.StudentService.save*(..))

这个叫切入点表达式,具体表达什么意思,什么语法,可参考下面的链接文章中,xml配置部分,顺便看看最原始的aop的样子。

spring AOP 之 xml 配置实现(附 Java 代码实例)

2,关于JoinPoint的方法和简单注释。

package org.aspectj.lang; 
 
import org.aspectj.lang.reflect.SourceLocation; 
 
public interface JoinPoint { 
    String toString();         //连接点所在位置的相关信息 
    String toShortString();     //连接点所在位置的简短相关信息 
    String toLongString();     //连接点所在位置的全部相关信息 
    Object getThis();         //返回AOP代理对象 
    Object getTarget();       //返回目标对象 
    Object[] getArgs();       //返回被通知方法参数列表 
    Signature getSignature();  //返回当前连接点签名 
    SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置 
    String getKind();        //连接点类型 
    StaticPart getStaticPart(); //返回连接点静态部分 
}

3,自定义注解的时候,自己的理解。

spring aop 中@annotation()的使用,绝壁原创的文章


然后是调用的地方,如何调用。

先是controller层的代码

    @ResponseBody 
    @RequestMapping(value = "createNewStudent", method = RequestMethod.POST) 
    public JsonResult create(@RequestBody Student student) { 
        if (student == null) { 
            return new JsonResult(false, "student is null"); 
        } 
        //Student result = studentService.save(student); 
        Student result = studentService.saveEmptyData(student); 
        return result == null 
                ? new JsonResult(false, "查无结果") 
                : new JsonResult(true, "查找成功", result); 
    }
这地方,没啥,就是让观众知道,我这地方轮换着调用了2个save开头的方法。
正儿八经调用的是在service层里面。

    @MethodLog(description = "保存-方法名称save", clazz = Student.class) 
    public Student save(Student student) { 
        if (student != null) { 
            student.setCreateTime(new Date()); 
        } 
        return dao.save(student); 
    } 
 
    @MethodLog(description = "保存-方法名称saveEmptyData", clazz = Student.class) 
    public Student saveEmptyData(Student student) { 
        return null; 
    }
这个时候,启动spring 项目,然后切着保存一下,看看切面代码的执行效果如何
//这个是单独执行:studentService.save(student)方法的时候的运行结果。 
Around method                start....................... 
Around method                methodLog 的参数,remark:保存-方法名称save clazz:class com.lxk.model.Student 
before method                start ... 
before method                end ... 
Around method                返回结果:Student{id='5a3207d8684fc4586f07f0a4', name='李学凯新建', age=18, sex=true, money=null, floor=null} 
Around method                end....................... 
After method                start....................... 
After method                methodLog 的参数,remark:保存-方法名称save clazz:class com.lxk.model.Student 
After method                end....................... 
AfterReturning method               start....................... 
AfterReturning method               返回的结果:Student{id='5a3207d8684fc4586f07f0a4', name='李学凯新建', age=18, sex=true, money=null, floor=null} 
AfterReturning method               end....................... 
 
//这个是单独执行:studentService.save(student)方法的时候的运行结果。 
Around method                start....................... 
Around method                methodLog 的参数,remark:保存-方法名称saveEmptyData clazz:class com.lxk.model.Student 
before method                start ... 
before method                end ... 
Around method                返回结果:null 
Around method                end....................... 
After method                start....................... 
After method                methodLog 的参数,remark:保存-方法名称saveEmptyData clazz:class com.lxk.model.Student 
After method                end....................... 
AfterReturning method               start....................... 
AfterReturning method               返回的结果:null 
AfterReturning method               end.......................

从这个运行结果上,我们在切面,

确实可以拿到,咱自定义注解的各个参数的值。这是一个。

还可以拿到咱切入点,也就是目标方法的参数。joinPoint.getArgs()

方法名称啥的,也是可以拿到的,当前所切的类,所切的方法,所切方法的参数,以及所切方法的返回参数。

我这也拿了session里面的 sessionInfo,这个是我自定义的一个对象,然后把这个东西放到session里面。因为我这没放,所以,没有取到user对象。

所以,你得把你的user先放到session里面才能在这取。这个就先不说啦。

还有,就是可以看到这些个不同的切面通知的执行先后的情况


哦,对啦,我的这个测试项目的文件目录结构概览图,还没奉上呢。

差不多都这个样子吧


AOP,重点就是要知道怎么切,切哪,切完之后,能拿到什么参数,这个包括所切方法的参数,所切方法的返回值,等等。





评论关闭
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!