`
hiskyrisa
  • 浏览: 36205 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类

基于Annotation和AOP实现Service缓存方案

 
阅读更多

简单的使用缓存的方法,就是在Service中嵌入调用缓存的代码。

1.先查询缓存,

2.若缓存命中,则返回

3.从数据库查询,并且往缓存中插入一份

 

这种做法导致许多许多重复代码,所以想到了用注解来调用缓存。并且在注解中设置key和expire

先定义注解

package com.cache;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodCache {
    String prefix();

    int expire();
}

 定义一个简单的Service类,并对需要走缓存的方法加上注解。注意此处注解中的prefix写成这个样子,是为了告诉Aspect要获取的属性,以便能够动态生成key。

package com.cache;

import com.user.vo.User;
import org.springframework.stereotype.Component;

@Component("userService2")
public class UserService {

    @MethodCache(prefix = "'user_'+#userId", expire = 100)
    public User get(User user) {
        System.out.println("Calling Service...");
        user.setName("hisky");
        return user;
    }
}

 定义一个简单的CacheManager类

package com.cache;

import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;

@Component
public class CacheManager {
    private ConcurrentHashMap cache = new ConcurrentHashMap();

    public Object get(String key) {
        Object value = cache.get(key);
        return value;
    }

    public void put(String key, Object value) {
        cache.put(key, value);
    }
}

 重点来了!定义一个Aspect,截获有注解的方法。

package com.cache;

import com.sun.javafx.binding.StringFormatter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

@org.aspectj.lang.annotation.Aspect
@Component
public class CacheAspect {

    @Autowired
    private CacheManager cacheManager;

    @Pointcut("@annotation(com.cache.MethodCache)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        String key = getKey(pjp);
        Object cachedObj = cacheManager.get(key.toString());
        System.out.println(String.format("Find the object from cache, key=%s,value=%s", key, cachedObj));
        if (cachedObj != null) {
            return cachedObj;
        }

        System.out.println("Find nothing from cache, keep call service.");
        cachedObj = pjp.proceed(pjp.getArgs());
        System.out.println(String.format("Put the object to cache, key=%s,value=%s", key, cachedObj));
        cacheManager.put(key.toString(), cachedObj);
        return cachedObj;
    }

    public String getKey(ProceedingJoinPoint pjp) {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        Annotation annotation = method.getAnnotation(MethodCache.class);
        String prefix = ((MethodCache) annotation).prefix();

        Object args[] = pjp.getArgs();
        String key = SpringELParser.parseKey(prefix, args[0]);
        return key;

    }
}

 为了支持动态的生成Key,此处引入Spring EL解析注解的Key

package com.cache;

import com.user.vo.User;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class SpringELParser {

    private static ExpressionParser parser = new SpelExpressionParser();

    //  private static Logger log = Logger.getLogger(SpelParser.class);
    // String prefix = "'Book.'+#bookId";
    // int bookId = 100;
    public static String parseKey(String key, String condition, String[] paramNames, Object[] arguments) {
        try {
            if (!checkCondition(condition, paramNames, arguments)) {
                return null;
            }
            Expression expression = parser.parseExpression(key);
            EvaluationContext context = new StandardEvaluationContext();
            int length = paramNames.length;
            if (length > 0) {
                for (int i = 0; i < length; i++) {
                    context.setVariable(paramNames[i], arguments[i]);
                }
            }
            return expression.getValue(context, String.class);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static boolean checkCondition(String condition, String[] paramNames, Object[] arguments) {
        if (condition.length() < 1) {
            return true;
        }
        Expression expression = parser.parseExpression(condition);
        EvaluationContext context = new StandardEvaluationContext();
        int length = paramNames.length;
        if (length > 0) {
            for (int i = 0; i < length; i++) {
                context.setVariable(paramNames[i], arguments[i]);
            }
        }
        return expression.getValue(context, boolean.class);
    }

    public static String parseKey(String prefix, Object arg) {
        String key = "";
        try {
            String param = getParam(prefix);
            Object value = getValue(arg, param);
            key = parseKey(prefix, "", new String[]{param}, new Object[]{value});
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return key;
    }

    private static Object getValue(Object arg, String param) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        String methodName = getMethodName(param);
        Class clzz = arg.getClass();
        Method method = clzz.getMethod(methodName);
        return method.invoke(arg);
    }

    private static String getParam(String prefix) {
        return prefix.substring(prefix.lastIndexOf("#") + 1);
    }

    private static String getMethodName(String param) {
        return "get" + param.substring(0, 1).toUpperCase() + param.substring(1);
    }

    public static void main(String[] args) {
        try {
            String prefix = "'user_'+#userId";

            User user = new User();
            user.setUserId(123);

            String key = parseKey(prefix, user);

            System.out.println(key);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 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:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd"
       default-lazy-init="true">

    <context:component-scan base-package="com.*">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    </context:component-scan>

    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

 测试类

package com.cache;

import com.user.vo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {
    public static void main(String args[]) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/cache/spring-test.xml");
        UserService service = (UserService) ctx.getBean("userService2");
        User user = new User();
        user.setUserId(1);
        service.get(user);

        service.get(user);
    }
}

 从控制台可以看出,此处生成的key为user_1。第一次缓存查不到,则调用service获取。第二次则从缓存直接返回了。

Find the object from cache, key=user_1,value=null
Find nothing from cache, keep call service.
Calling Service...
Put the object to cache, key=user_1,value=com.user.vo.User@45667d98[userId=1,name=hisky,password=<null>,type=<null>]
Find the object from cache, key=user_1,value=com.user.vo.User@45667d98[userId=1,name=hisky,password=<null>,type=<null>]

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics