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

最近写一个项目要实现保存操作记录,就想到了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 1
Before.
BeforeWithAnnotation.人员管理
BeforeWithParame.马良
add a User name is 马良
add a new one type : 1 	 perentid : 1
Around 2
Around :add success!
After
AfterReturning:add success!

五、实际使用

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值