spring AOP切面使用

spring的两大特性,AOP和DI。DI应该使用比较频繁,而AOP一般局限于拦截器的使用上。

但是今天遇到一个场景,考虑起来还是用AOP更合适一些。

场景介绍:

需要在现有服务基础上添加redis支持,before service验证是否已经在redis中有了缓存。 afterrunning  将servie返回的结果写入redis。

由于此拦截并不是针对url进行的,并且拦截器接口HandlerInterceptor限制了输入输出的数据类型,使用起来不是很方便。所以最终决定采用注解AOP的方式完成。

1、需要添加AOP依赖

我是用的springboot所以添加maven配置如下

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2、添加一个aop配置类

package com.netease.lede.aop;

import org.apache.commons.collections.set.SynchronizedSortedSet;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;

import com.netease.lede.entity.request.BaseBusinessRequest;
import com.netease.lede.entity.request.GetOnlinePageRequest;
import com.netease.lede.entity.response.BaseResponse;

/**
* @author 张啸雷 E-mail:bjzhangxiaolei1@corp.netease.com
* @version 创建时间:2016年9月27日 下午3:30:52
* 类说明   由于redis的set和get与业务逻辑相关度很高,不能够直接采用interceptor方式完成,所以考虑在service的实现上添加aop
*/
@Aspect
@Configuration
 public class RedisAop {
	//任意定义在com.netease.lede.service.api包里的任意方法的执行
	private BaseBusinessRequest query;
	private BaseResponse result;
	@Pointcut(
			//定义切面范式,针对com.netease.lede.service.api包下面的所有接口的所有方法的实现。
			//由于是多个接口,这就要求所有接口的参数和返回值必须一样,接口名称可以不同。
			//com.netease.lede.entity.request.BaseBusinessRequest是接口参数类型
			//args(query)是定义了参数名称
			"execution(* com.netease.lede.service.api.*.*(com.netease.lede.entity.request.BaseBusinessRequest)) && args(query)")
			//"execution(* com.netease.lede.service.api.*.*(T)) && args(query)")
			//"execution(* com.netease.lede.service.api.*.*(..))")
			public void bussinessService(BaseBusinessRequest query){	}
	
	@Before("bussinessService(query)")
	public void getResultFromRedis(BaseBusinessRequest query){
		
		System.out.println(query.getRedisKey());
		System.out.println("getResultFromRedis");
	}
	
	//value="bussinessService(query)"定义了切面名称
	//returning="result"定义了返回值名称
	@AfterReturning(value="bussinessService(query)",returning="result")
	public void setResultToRedis(BaseBusinessRequest query,BaseResponse result){
		System.out.println(result.getClass().getName());
		System.out.println("setResultToRedis");
	}

}
    private BaseBusinessRequest query;  //所有接口的入参类型
    private BaseResponse result;  //所有接口的返回值类型


如上配置后:如下接口类中的各个方法则都会被拦截。

package com.netease.lede.service.api;

import org.springframework.web.bind.annotation.RequestBody;

import com.netease.lede.entity.request.BaseBusinessRequest;
import com.netease.lede.entity.request.ChangeCompareDateRequest;
import com.netease.lede.entity.request.GetOnlineFigureRequest;
import com.netease.lede.entity.request.GetOnlinePageRequest;
import com.netease.lede.entity.response.BaseResponse;
import com.netease.lede.entity.response.ChangeCompareDateResponse;
import com.netease.lede.entity.response.GetFigureResponse;
import com.netease.lede.entity.response.GetOnlinePageResponse;

/**
 * @author 张啸雷 E-mail:bjzhangxiaolei1@corp.netease.com
 * @version 创建时间:2016年9月21日 上午9:35:44 类说明 实时数据相关的service
 */
public interface IOnlineService {
	BaseResponse getFrame(BaseBusinessRequest requestEntity);

	BaseResponse changeCompareDate(BaseBusinessRequest requestEntity);

	BaseResponse getFigures( BaseBusinessRequest requestEntity);
}
遇到的几个坑:

1、Can not set com.netease.lede.service.impl.ProductServiceImpl field com.netease.lede.controller.ProductController.service to com.sun.proxy.$Proxy108

报这个错的原因是controller中DI时使用的是实现类,而不是接口类。在AOP之前没有什么问题,一旦配置了AOP,则由于AOP底层是基于动态代理,所以必须指明接口类。

@Controller
@RequestMapping("/product")
public class ProductController {
    
    @Autowired
    public IProductService service;
    //ProductServiceImpl service;

修改成如上形式就可以了。


2、Caused by: java.lang.IllegalArgumentException: warning no match for this type name: BaseBusinessRequest [Xlint:invalidAbsoluteTypeName]

在AOP时,pointcut处配置参数的类型时,需要写带报名的全限定名,如果直接写类名则会报上述错误。

    @Pointcut(
            //定义切面范式,针对com.netease.lede.service.api包下面的所有接口的所有方法的实现。
            //由于是多个接口,这就要求所有接口的参数和返回值必须一样,接口名称可以不同。
            //com.netease.lede.entity.request.BaseBusinessRequest是接口参数类型
            //args(query)是定义了参数名称
            "execution(* com.netease.lede.service.api.*.*(com.netease.lede.entity.request.BaseBusinessRequest)) && args(query)")
            //"execution(* com.netease.lede.service.api.*.*(BaseBusinessRequest)) && args(query)")
            public void bussinessService(BaseBusinessRequest query){    }
   

参考文章

http://blog.csdn.net/luckyzhoustar/article/details/47420789

http://www.cnblogs.com/rainy-shurun/p/5195439.html



关于增强通知方法

在AOP的过程中,我们往往需要目标方法的参数,怎么才能做到呢?就需要使用增强通知了。

增强通知非常简单,只需要在通知方法里面使用JoinPoint  joinPoint就可以了。

JoinPoint类提供了若干方法获取目标方法的各种属性。

在使用@Around通知时,由于要调用目标方法,所以使用的是JoinPoint方法的子类ProceedingJoinPoint

在上面的例子中,想要达到的效果是,在通知方法运行之前判断redis中是否已经有相关记录,如果有则取redis中的值,如果没有则执行目标方法,并将目标方法的返回值写入redis。

最初使用@Before和@AfterReturning通知来进行上述功能的实现遇到两个问题。

1、@Before不能中断对目标方法的调用,也就是当判断redis中有记录时,取回reids中的记录,但目标方法依然会被执行。

2、@AfterReturning通知在方法返回后执行,这就导致,无论结果是不是新计算的,都会向redis中写入一次。这显然不是我们想看到的。

所以综合上面的两点,最终还是需要用@Around方法来实现通知。

package com.netease.lede.aop;

import org.apache.commons.collections.set.SynchronizedSortedSet;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.netease.lede.entity.request.BaseBusinessRequest;
import com.netease.lede.entity.request.GetOnlinePageRequest;
import com.netease.lede.entity.response.BaseResponse;

/**
* @author 张啸雷 E-mail:bjzhangxiaolei1@corp.netease.com
* @version 创建时间:2016年9月27日 下午3:30:52
* 类说明   由于redis的set和get与业务逻辑相关度很高,不能够直接采用interceptor方式完成,所以考虑在service的实现上添加aop
*/
@Aspect
@Configuration
 public class RedisAop {
	
	String redisKeyName="magiccubedata";
	
	@Autowired
	RedisTemplate<String,BaseResponse>  redisTemplate;
	
	
	
	//任意定义在com.netease.lede.service.api包里的任意方法的执行
	//private BaseBusinessRequest query;
	//private BaseResponse result;
	@Pointcut(
			"execution(* com.netease.lede.service.api.*.*(..))")
	public void bussinessService(){	}
	
	
	@Around(value = "bussinessService()")
	public BaseResponse getResultFromRedis(ProceedingJoinPoint pjp) throws Throwable{
		BaseBusinessRequest query=(BaseBusinessRequest)pjp.getArgs()[0];
		System.out.println(query.getRedisKey());
		HashOperations<String,String, BaseResponse>  hop=redisTemplate.opsForHash();
		BaseResponse reponse=hop.get(redisKeyName, query.getRedisKey());
		if(null!=reponse){
			System.out.println("getResultFromRedis");
			return reponse;
		}else{
			System.out.println("getResultFromMysql");
			reponse=(BaseResponse) pjp.proceed(new Object[]{query});
			hop.put(redisKeyName, query.getRedisKey(), reponse);
			return reponse;
		}

	}

}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值