Spring编程:通过Aspect实现AOP编程完成日志记录

来源:互联网 发布:黑魂3故事剧情知乎 编辑:程序博客网 时间:2024/05/19 17:05

最近写一个项目要实现保存操作记录,就想到了AOP,先附上参考的地址

网上小实例:http://my.oschina.net/yangzg/blog/343945

慕课网视频:http://www.imooc.com/video/4888

建议先把小实例在电脑上跑通,然后看慕课的视频教学,效果不错。下面附上我学习的思路和代码吧


一、Spring jar包的导入

实现Spring编程,jar包少不了,主要用到了五个jar,其中有两个是测试用的

pom.xml依赖如下

  <dependencies>  <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --><dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-context</artifactId>    <version>5.0.0.RELEASE</version></dependency><!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --><dependency>    <groupId>org.aspectj</groupId>    <artifactId>aspectjweaver</artifactId>    <version>1.8.11</version></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects --><dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-aspects</artifactId>    <version>5.0.1.RELEASE</version></dependency><!-- https://mvnrepository.com/artifact/junit/junit --><dependency>    <groupId>junit</groupId>    <artifactId>junit</artifactId>    <version>4.12</version>    <scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.springframework/spring-test --><dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-test</artifactId>    <version>5.0.0.RELEASE</version>    <scope>test</scope></dependency>  </dependencies>
注意依赖顺序,最好放最前,不用maven的项目可以去下载这些jar导入工程就好了

二、Spring 配置文件

<?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:aop="http://www.springframework.org/schema/aop"         xsi:schemaLocation="http://www.springframework.org/schema/beans             http://www.springframework.org/schema/beans/spring-beans-2.5.xsd             http://www.springframework.org/schema/context             http://www.springframework.org/schema/context/spring-context-2.5.xsd             http://www.springframework.org/schema/aop             http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">      <context:annotation-config />      <!-- 自动扫描的包路径 -->    <context:component-scan base-package="com.aspect"/>      <!-- 对aspectJ的支持配置 -->    <aop:aspectj-autoproxy />  </beans>
注意自动扫描路径正确性,maven项目放到resource下就好,其他项目放到src下面

三、项目文件目录结构

放代码前先看看项目包目录结构

四、代码实现

写日志用到了注解实现,就是在要方法前加入该方法说明的注解就能自动读取注解内容,且不改变方法本身,没有侵入性
1、逻辑业务接口

package com.aspect.applogic;/* * /  用户管理业务逻辑接口 */public interface UserManagerApplogic {      public String addUser(String name);      public void addOne(int type,int parentid);  }  

2、用户管理业务逻辑实现类

package com.aspect.impl;import org.springframework.stereotype.Component;import com.aspect.applogic.UserManagerApplogic;import com.aspect.annotation.BussAnnotation;/* * 用户管理业务逻辑实现类 */@Component("userManager")public class UserManagerApplogicImpl implements UserManagerApplogic {        @BussAnnotation(moduleName="人员管理",option="添加用户")      @Override      public String addUser(String name) {         System.out.println("add a User name is "+name);   this.addOne(1, 1);          // 测试异常放回抛出        // throw new RuntimeException("add failed!");       return "add success!";    }        @BussAnnotation(moduleName="人员管理",option="添加新人")      @Override      public void addOne(int type, int parentid) {          System.out.println("add a new one type : "+type+" \t perentid : "+parentid);      }      }  
我们看到方法前的注解,不用写入代码中就能实现该方法的说明,为此我们要自己声明这个注解

3、业务注释类

package com.aspect.annotation;import java.lang.annotation.Retention; import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy;  import java.lang.annotation.Target;  /* * 业务注释类 */// RUNTIME代表的是表示在源码、编译好的.class文件中保留信息,在执行的时候会把这一些信息加载到JVM中去的。@Retention(RetentionPolicy.RUNTIME)  // @Target里面的ElementType是用来指定Annotation类型可以用在哪一些元素上的.METHOD(方法)@Target ({ElementType.METHOD})  public @interface  BussAnnotation {  //模块名      String moduleName() default "";      //操作内容      String option() default "";  }
上面的注解还有其他的类型,可参考开头给的链接

4、切面类

重点就是这个切面类

package com.aspect.aop;import java.lang.reflect.Method;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Around;  import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;  import org.springframework.stereotype.Component;import com.aspect.annotation.BussAnnotation;//切面类  http://my.oschina.net/yangzg/blog/343945  /* * 特别注意: Spring的配置文件中添加:   *  * <aop:aspectj-autoproxy /> * spring-mvc-dispatcher.xml中天机 * <!--通知spring使用cglib而不是jdk的来生成代理方法 AOP可以拦截到Controller-->   * <aop:aspectj-autoproxy proxy-target-class="true"/> * * <aop:config>节点中proxy-target-class="true"不为true时。 * 当登录的时候会报这个异常java.lang.NoSuchMethodException: $Proxy54.login(), */  // aspect注解不能被自动扫描。配合component使用// 这个类为一个切面类且自己不会被代理/* * aspectJ是编译期的AOP,检查代码并匹配连接点和切入点代价高,要选择好的切入点 * execution get、set、call、handler等等选择 * within withincode确定范围 * this、target、@annotation 匹配上下文信息 */@Aspect  @Component  public class LogInterceptor {  // 定义切入点  @Pointcut("execution(public * com.aspect..*.*(..))")  -- 表示对com.aspect 包下的所有方法都可添加切入点  // 该注解下方法返回值为void@Pointcut("execution(public * addUser(..))")   public void aApplogic() {}  //定义切入点  -- 拦截指定的方法  这里拦截 com.aspect.demo3.aop1.impl.UserManagerApplogicImpl 的addOne()方法   @Pointcut("execution(public * addOne(..))")   public void joinPointForAddOne(){}  ///** //* 环绕通知 用于拦截指定内容,记录用户的操作 //* 切入点可以通过 && 和 || 进行一个拼接的作用//*/  //@Around(value = "aApplogic() && @annotation(annotation) &&args(object,..) ", argNames = "annotation,object")  //public Object interceptorApplogic(ProceedingJoinPoint pj,  //BussAnnotation annotation, Object object) throws Throwable {  //System.out.println("moduleName:" + annotation.moduleName());  //System.out.println("option:" + annotation.option());  //pj.proceed();//// 打印方法名//System.out.println(pj.getSignature().getName());  //// 打印参数//for(Object obj : pj.getArgs()){  //System.out.println(obj.toString());  //}  //return object;  //}  //    //  //  /** //   * 环绕通知   拦截指定的切点,这里拦截joinPointForAddOne切入点所指定的addOne()方法 //   *  //   */  //@Around("joinPointForAddOne()")  //public Object interceptorAddOne(ProceedingJoinPoint joinPoint) throws Throwable {  //System.out.println("Aop start");  //String methodRemark = getMthodRemark(joinPoint);  //Object result = null;  // try {    // // 记录操作日志...谁..在什么时间..做了什么事情..    // result = joinPoint.proceed();    // } catch (Exception e) {    // // 异常处理记录日志..log.error(e);    // throw e;    // }    //  System.out.println(methodRemark);  // System.out.println("Aop end");  //return result;  //  }  //////// 获取方法的中文备注____用于记录用户的操作日志描述    //public static String getMthodRemark(ProceedingJoinPoint joinPoint)    //throws Exception {    //  String targetName = joinPoint.getTarget().getClass().getName();    //   String methodName = joinPoint.getSignature().getName();    //   System. out.println("====调用" +methodName+"方法-开始!");  //   Object[] arguments = joinPoint.getArgs();   //获得参数列表  //  System.out.println("打印出方法调用时传入的参数,可以在这里通过添加参数的类型,进行一些简易逻辑处理和判断");  //  if(arguments.length<=0){  //  System.out.println("=== "+methodName+" 方法没有参数");  //  }else{  //  for(int i=0;i<arguments.length;i++){  //  System.out.println("==== 参数   "+(i+1)+" : "+arguments[i]);  //  }  //  }  //  //  @SuppressWarnings("rawtypes")//  Class targetClass = Class.forName(targetName);    // Method[] method = targetClass.getMethods();    //  String methode = "";    //  for (Method m : method) {    //  if (m.getName().equals(methodName)) {    //  @SuppressWarnings("rawtypes")//Class[] tmpCs = m.getParameterTypes();    //           if (tmpCs.length == arguments.length) {    //              BussAnnotation methodCache = m.getAnnotation(BussAnnotation.class);    //              methode = methodCache.moduleName();    //              break;    //         }    //  }    //  }    //return methode;    //}      /*  * imooc 例子  */  // 执行前 // 下面写法类型这个@Before("execution(public * add*(..))") @Before("aApplogic()")public void Before(){System.out.println("Before.");}  // Advice可以获取参数 @Before("aApplogic()&&args(arg)")public void BeforeWithParame(String arg){System.out.println("BeforeWithParame." + arg);}  // Advice获取注解,先定义一个注解例如:bussAnnotation那个,然后再方法那加上注解 // 注意注解获取在参数获取之前 //@Before("aApplogic()&&@annotation(annotation)")// 简单写法// 上述方法的简单写法@Pointcut("execution(public * addUser(..))&&@annotation(annotation)")  public void aApplogicAnno(BussAnnotation annotation) {}   @Before("aApplogicAnno(annotation)") public void BeforeWithAnnotation(BussAnnotation annotation){System.out.println("BeforeWithAnnotation." + annotation.moduleName());}   // 正常返回后获取返回值 // returning 获取方法的返回值,放回类型不确定定义该类型为object @AfterReturning(pointcut="aApplogic()",returning="returnValue") public void afterReturning(Object returnValue){ System.out.println("AfterReturning:" + returnValue); }  // 异常返回后获取异常值e 接收抛出的异常 @AfterThrowing(pointcut="aApplogic()",throwing="e") public void afterThrowing(RuntimeException e){ // 调试打出堆栈信息 // e.printStackTrace(); System.out.println("afterThrowing :" + e.getMessage()); }  // 返回(无论返回正常还是异常)后释放资源:注意,after先执行,AfterReturning和AfterThrowing后执行 @After("aApplogic()") public void after(){ System.out.println("After"); }  // 环绕通知 /*  * 环绕通知通知方法的第一个参数必须是ProceedingJoinPoint类型  * 在通知内部调用ProceedingJoinPoint的proceed()方法会导致执行真正的方法  * 传入一个object[]对象,数组中的值将会作为参数传递个方法  * proceed()方法执行时才执行真正的方法,把真正的返回值个object类型  */ @Around("aApplogic()") public Object around(ProceedingJoinPoint pjp) throws Throwable{ /* 执行顺序  * 1、around 1  * 2、before  * 3、method  * 4、around 2  * 5、around : obj  * 6、after  * 7、afterReturning  */ System.out.println("Around 1"); Object object = pjp.proceed(); System.out.println("Around 2"); System.out.println("Around :" + object); return object;}}  
我只保留了慕课网上的Advic,可以先跑一下看看效果


5、测试方法

package com.aspect.test;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import org.springframework.beans.factory.annotation.Autowired;import com.aspect.applogic.UserManagerApplogic;// 使用所有注释前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="classpath:applicationContextmay.xml")public class test{  @Autowired    private UserManagerApplogic userManager;       @Test      public void testAopAddUser(){          userManager.addUser("马良");     }        @Test      public void testAopAddOne(){          userManager.addOne(1, 1);      }  }  
先测试testAopAddUser()方法可以看到控制台打印效果如下

Around 1Before.BeforeWithAnnotation.人员管理BeforeWithParame.马良add a User name is 马良add a new one type : 1  perentid : 1Around 2Around :add success!AfterAfterReturning:add success!

五、实际使用

ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContextmay.xml");LoginMainFrm loginMainFrm = (LoginMainFrm) context.getBean("loginMainFrm");
ApplicationContext 
读取Spring配置文件,且想用aop的类全要Spring自动装配的bena,就是要spring管理起来。

多个项目还不知道怎么实现日志记录管理~先写这么多吧

原创粉丝点击