Spring AOP的原理及使用实例

一、Spring AOP中的一些概念

  • 切面(Aspect):切入业务流程的一个独立模块。“切面”在ApplicationContext中<aop:aspect>来配置。
  • 连接点(Joinpoint) :业务流程在运行过程中需要插入切面的具体位置。
  • 通知(Advice) :切面的具体实现方法。可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)和环绕通知(Around)五种。实现方法具体属于哪类通知,是在配置文件和注解中指定的。
  • 切入点(Pointcut) :定义通知应该切入到哪些连接点上,不同的通知通常需要切入到不同的连接点上。
  • 目标对象(Target Object) :被一个或者多个切面所通知的对象。
  • AOP代理(AOP Proxy) :在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将 <aop:config>的 proxy-target-class属性设为true。
  • 织入:将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期。

    通知类型:

  1. 前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明。例如,TestAspect中的doBefore方法。
  2. 后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。例如,ServiceAspect中的returnAfter方法,所以Teser中调用UserService.delete抛出异常时,returnAfter方法仍然执行。
  3. 返回后通知(After return advice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect>里面使用<after-returning>元素进行声明。
  4. 环绕通知(Around advice):包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明。例如,ServiceAspect中的around方法。
  5. 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。例如,ServiceAspect中的returnThrow方法。

注:可以将多个通知应用到一个目标对象上,即可以将多个切面织入到同一目标对象。

二、案例--Spring AOP实现后台管理日志

1)自定义注解,拦截Controller

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemControllerLog {

	/**
	 * 描述业务操作 例:Xxx管理-执行Xxx操作
	 * @return
	 */
	String description() default "";
}

2)系统日志切点类

@Aspect
@Component
public class SystemLogAspect {
	private  static  final Logger logger = LoggerFactory.getLogger(SystemLogAspect. class);
	
	private static final ThreadLocal<Date> beginTimeThreadLocal =
			new NamedThreadLocal<Date>("ThreadLocal beginTime");
	private static final ThreadLocal<Log> logThreadLocal = 
			new NamedThreadLocal<Log>("ThreadLocal log");
	
	private static final ThreadLocal<User> currentUser=new NamedThreadLocal<>("ThreadLocal user");
	
	@Autowired(required=false)
	private HttpServletRequest request;
	
	@Autowired
	private ThreadPoolTaskExecutor threadPoolTaskExecutor;

	@Autowired
	private LogService logService;
		
	/**
	 * Controller层切点 注解拦截
	 */
	@Pointcut("@annotation(com.myron.ims.annotation.SystemControllerLog)")
	public void controllerAspect(){}
	
	/**
	 * 方法规则拦截
	 */
	@Pointcut("execution(* com.myron.ims.controller.*.*(..))")
	public void controllerPointerCut(){}
	/**
	 * 前置通知 用于拦截Controller层记录用户的操作的开始时间
	 * @param joinPoint 切点
	 * @throws InterruptedException 
	 */
	@Before("controllerAspect()")
	public void doBefore(JoinPoint joinPoint) throws InterruptedException{
		Date beginTime=new Date();
		beginTimeThreadLocal.set(beginTime);
		//debug模式下 显式打印开始时间用于调试
		if (logger.isDebugEnabled()){
	        logger.debug("开始计时: {}  URI: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
	        	.format(beginTime), request.getRequestURI());
		}
		
		//读取session中的用户 
		HttpSession session = request.getSession();       
	    User user = (User) session.getAttribute("ims_user");    
		currentUser.set(user);

	}
	
	/**
	 * 后置通知 用于拦截Controller层记录用户的操作
	 * @param joinPoint 切点
	 */
	@SuppressWarnings("unchecked")
	@After("controllerAspect()")
	public void doAfter(JoinPoint joinPoint) {
        User user = currentUser.get();
        //登入login操作 前置通知时用户未校验 所以session中不存在用户信息
        if(user == null){
    		HttpSession session = request.getSession();       
    	    user = (User) session.getAttribute("ims_user");    
    	    if(user==null){
    	    	return;
    	    }
        }
        Object [] args = joinPoint.getArgs();
        System.out.println(args);
        
    	String title="";
    	String type="info";						  //日志类型(info:入库,error:错误)
    	String remoteAddr=request.getRemoteAddr();//请求的IP
    	String requestUri=request.getRequestURI();//请求的Uri
    	String method=request.getMethod();		  //请求的方法类型(post/get)
    	Map<String,String[]> params=request.getParameterMap(); //请求提交的参数
    	try {
    		title=getControllerMethodDescription2(joinPoint);
    	} catch (Exception e) {
    		e.printStackTrace();
    	}    
    	// debu模式下打印JVM信息。
    	long beginTime = beginTimeThreadLocal.get().getTime();//得到线程绑定的局部变量(开始时间)  
    	long endTime = System.currentTimeMillis(); 	//2、结束时间  
		if (logger.isDebugEnabled()){
	        logger.debug("计时结束:{}  URI: {}  耗时: {}   最大内存: {}m  已分配内存: {}m  已分配内存中的剩余空间: {}m  最大可用内存: {}m",
	        		new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(endTime), 
	        		request.getRequestURI(), 
	        		DateUtils.formatDateTime(endTime - beginTime),
					Runtime.getRuntime().maxMemory()/1024/1024, 
					Runtime.getRuntime().totalMemory()/1024/1024, 
					Runtime.getRuntime().freeMemory()/1024/1024, 
					(Runtime.getRuntime().maxMemory()-Runtime.getRuntime().totalMemory()+Runtime.getRuntime().freeMemory())/1024/1024); 
		}
    	
    	Log log=new Log();
    	log.setLogId(UuidUtils.creatUUID());
    	log.setTitle(title);
    	log.setType(type);
    	log.setRemoteAddr(remoteAddr);
    	log.setRequestUri(requestUri);
    	log.setMethod(method);
    	log.setMapToParams(params);
    	log.setUserId(user.getId());
    	Date operateDate=beginTimeThreadLocal.get();
    	log.setOperateDate(operateDate);
    	log.setTimeout(DateUtils.formatDateTime(endTime - beginTime));
    	
        threadPoolTaskExecutor.execute(new SaveLogThread(log, logService));
        logThreadLocal.set(log);
       
 
	}
	
	/**
	 *  异常通知 
	 * @param joinPoint
	 * @param e
	 */
	@AfterThrowing(pointcut = "controllerAspect()", throwing = "e")  
	public  void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
		Log log = logThreadLocal.get();
		if(log != null){
			log.setType("error");
			log.setException(e.toString());
			new UpdateLogThread(log, logService).start();			
		}
	}

}

3)Controller层接口代码:

@Api(value = "/", tags = "系统登入接口")
@Controller
@RequestMapping("/")
public class LoginController {
	private static final Logger logger = LoggerFactory.getLogger(LoginController.class);

	/** 登入页 */
	public static final String LOGIN_PAGE = "/index.jsp";
	/** 首页 */
	public static final String MAIN_PAGE = "/main.jsp";
	/** 用户session key */
	public static final String KEY_USER = "ims_user";
	
	@ApiOperation(value = "登入系统", notes = "登入系统", httpMethod = "POST")
	@SystemControllerLog(description="登入系统")
	@RequestMapping("/login")
	public String login(HttpServletRequest request, ModelMap model,User user, Boolean rememberMe, String verifycode) throws Exception{		
		//TODO 用户密码校验逻辑省略...
		user.setId("0001");
		//TODO 验证码...
		
		//登入成功
		HttpSession session = request.getSession();       
		session.setAttribute(KEY_USER, user);
		logger.info("{} 登入系统成功!", user.getUsername());
		model.addAttribute("user", user);
		return MAIN_PAGE;
	}
	
	/**
	 * 安全退出登入
	 * @return
	 */
	@SystemControllerLog(description="安全退出系统")
	@RequestMapping("logout")
	public String logout(HttpServletRequest request){
		HttpSession session = request.getSession(); 
		User user = (User) session.getAttribute(KEY_USER);
		if (user != null) {
			//TODO 模拟退出登入,直接清空sessioni
			logger.info("{} 退出系统成功!", user.getUsername());
			session.removeAttribute(KEY_USER);			
		}
		return LOGIN_PAGE;
	}
}

4)请求登录接口,效果如下:

 

参考文章:https://blog.csdn.net/myron_007/article/details/54927529#comments

案例源码:https://github.com/MusicXi/demo-aop-log 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP 是一个用于实现面向切面编程的框架。它可以通过配置来实现横切关注点的模块化,并将其应用到程序的不同部分。Spring AOP 使用 AspectJ 切入点指示符来定义切入点表达式,用于匹配方法执行连接点。Spring AOP 支持以下 AspectJ 切入点指示符: 1. execution:用于匹配方法执行连接点。这是使用 Spring AOP 时要使用的主要切入点指示符。 2. within:限制匹配以连接某些类型中的点(使用 Spring AOP 时在匹配类型中声明的方法的执行)。 3. this:限制匹配到连接点(使用 Spring AOP 时方法的执行),其中 Bean 引用(Spring AOP 代理)是给定类型的实例。 4. target:限制匹配到连接点(使用 Spring AOP 时方法的执行),其中目标对象(正在代理的应用程序对象)是给定类型的实例。 5. args:限制匹配到连接点(使用 Spring AOP 时方法的执行),其中参数是给定类型的实例。 6. @target:限制匹配到连接点(使用 Spring AOP 时方法的执行),其中执行对象的类具有给定类型的注释。 7. @args:限制匹配到连接点(使用 Spring AOP 时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释。 8. @within:限制匹配以连接具有给定注释的类型中的点(使用 Spring AOP 时在具有给定注释的类型中声明的方法的执行)。 9. @annotation:限制匹配到连接点的主题(在 Spring AOP 中运行的方法)具有给定注释的连接点。 在使用 Spring AOP 时,需要引入 Spring AOPSpring Context 相关的包,并在配置文件中进行相应的配置。可以通过 Maven 或其他构建工具来引入相关依赖。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值