栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring AOP项目实战

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring AOP项目实战

目录

一、需求说明

二、Spring AOP

 2.1 AOP切点指示器

2.2 AOP表达式 

2.3、Spring AOP通知类型 

2.4、示例代码

三、需求实现

3.1、定义注解

3.2、定义切面

3.2.1获取方法参数名称

3.2.2  构建缓存Key

3.2.3环绕切面处理

四、源码

五、其他问题

5.1为什么无法获取到参数名称


一、需求说明

        为了简化代码,提示可读性需要针对优先读缓存,如果缓存没有则插入数据库然后再将数据写入缓存并返回。

        根据这个需求我们可以基于Spring AOP 去实现。但是有一个问题,缓存的key是一个动态值,对于不同入参它的key是不同的。我们可以使用SpringEL 表达式去解决这个问题。

预期的效果是:

@Cache(prefix = "test", value = "#bizId", expire = CacheTime.CACHE_EXP_DAY)
public Object queryFromCache(Long bizId)

二、Spring AOP

  首先我们先了解一下Spring AOP的基础知识

 2.1 AOP切点指示器
AspectJ 指示器描述
args()限制连接点匹配参数为执行类型的执行方法
@args()限制连接点匹配参数由执行注解标注的执行方法
execution()匹配连接点的执行方法
this()限制连接点匹配AOP代理的Bean引用类型为指定类型的Bean
target()限制连接点匹配目标对象为指定类型的类
@target()限制连接点匹配目标对象被指定的注解标注的类
within()限制连接点匹配匹配指定的类型
@within()限制连接点匹配指定注解标注的类型
@annotation限制匹配带有指定注解的连接点

Spring AOP 中常用的是:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
          throws-pattern?)

2.2 AOP表达式 
表达式说明
 execution("* *.*(..)")匹配所有
execution("* *.set*(..))匹配所有以set开头的方法
execution("* com.david.biz.service.impl.*(..))匹配指定包下所有的方法
 execution("* com.david..*(..)")匹配指定包以及其子包下的所有方法
execution("* com.david..*(java.lang.String))匹配指定包以及其子包下 参数类型为String 的方法

2.3、Spring AOP通知类型 
通知类型说明
前置通知(Before)在目标方法被调用之前调用通知功能。
后置通知(After)在目标方法完成之后调用通知,此时不会关心方法的输出是什么
返回通知(After-returning)在目标方法成功执行之后调用通知。
异常通知(After-throwing)目标方法抛出异常后调用通知。
环绕通知(Around)通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

2.4、示例代码
@Component
@Aspect
public class LogAspect {
    private static final Logger logger = LogManager.getLogger(LogAspect.class);

    
    @Pointcut("args(..)&&within(com.david.biz..*)")
    public void arg() {

    }

    @Pointcut("@args(com.david.aop.LoggingRequired)")
    public void annotationArgs() {

    }

    @Pointcut("@annotation(com.david.aop.LoggingRequired)")
    public void logRequiredPointcut() {

    }

    @Pointcut("args(java.lang.String,*)")
    public void argsWithString() {

    }

    @Pointcut("target(com.david.biz.service.impl.BookServiceImpl)")
    public void targetPointcut() {

    }

    @Pointcut("@target(org.springframework.stereotype.Service)")
    public void targetAnnotation() {

    }

    //    @Around("execution(* org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(..))")
    //    public Object aa(ProceedingJoinPoint pjp) throws Throwable {
    //        try {
    //            Object retVal = pjp.proceed();
    //            System.out.println(retVal);
    //            return retVal;
    //        } catch (Exception e) {
    //            System.out.println("异常");
    //            return null;
    //        }
    //    }
    @Before(value = "logRequiredPointcut()")
    public void before(JoinPoint joinPoint) {
        LogUtils.info(logger,
            " 连接点表达式@annotation(com.david.aop.LoggingRequired) - method={0} has been visited",
            joinPoint.getSignature().getName());
    }

    @Before(value = "arg()")
    public void beforeArg(JoinPoint joinPoint) {
        LogUtils.info(logger,
            "连接点表达式:args(..)&&within(com.david.biz..*)  method ={0}, args ={1},target={2}",
            joinPoint.getSignature().getName(), ToStringBuilder.reflectionToString(
                joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE), joinPoint.getTarget()
                .getClass().getName());
    }

    @Before(value = "argsWithString()")
    public void beforeArgWithString(JoinPoint joinPoint) {
        LogUtils.info(logger, "连接点表达式:args(java.lang.String,*)  method={0} ,args ={1},target={2}",
            joinPoint.getSignature().getName(), ToStringBuilder.reflectionToString(
                joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE), joinPoint.getTarget()
                .getClass().getName());
    }

    @Before(value = "annotationArgs()")
    public void beforeAnnotationArgs(JoinPoint joinPoint) {
        LogUtils
            .info(
                logger,
                "连接点表达式:@args(com.david.annotation.validate.Length,*)  method={0} ,args ={1},target={2}",
                joinPoint.getSignature().getName(), ToStringBuilder.reflectionToString(
                    joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE), joinPoint.getTarget()
                    .getClass().getName());
    }

    @Before(value = "targetPointcut()")
    public void beforeTarget(JoinPoint joinPoint) {
        LogUtils
        .info(
            logger,
            "连接点表达式:target(com.david.biz.service.impl.BookServiceImpl)  method={0} ,args ={1},target={2}",
            joinPoint.getSignature().getName(), ToStringBuilder.reflectionToString(
                joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE), joinPoint.getTarget()
                .getClass().getName());
    }

    @Before(value = " targetAnnotation()")
    public void beforeTargetAnnotation(JoinPoint joinPoint) {
        LogUtils
            .info(
                logger,
                "连接点表达式:@target(org.springframework.stereotype.Service)  method={0} ,args ={1},target={2}",
                joinPoint.getSignature().getName(), ToStringBuilder.reflectionToString(
                    joinPoint.getArgs(), ToStringStyle.SHORT_PREFIX_STYLE), joinPoint.getTarget()
                    .getClass().getName());
    }
}

三、需求实现

       根据需求我们需要使用注解指示器,也就是基于注解实现。

3.1、定义注解

      作为一个缓存注解首先就是缓存key的构成,其次就是缓存的有效期。下面我们就看看源码

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Cache {

    
    String prefix() default "";

    
    String[] value() default {};

    
    String separator() default "_";

    
    int expire() default CacheTime.CACHE_EXP_DAY;

}

3.2、定义切面

3.2.1获取方法参数名称

可以通过ASM提供的通过字节码获取方法的参数名称,spring已经集成了相关功能,我们可以方便的使用它。

第一步、声明一个成员并初始化变量LocalVariableTableParameterNameDiscoverer

private LocalVariableTableParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer();

第二步、通过反射方式获取方法参数类型

Class[] argTypes = ((MethodSignature) pjp.getSignature()).getMethod().getParameterTypes();

 第三步、根据方法名称和参数类型获取方法

 Method method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);

第四步、获取方法的参数名称

String[] paraNameArr = nameDiscoverer.getParameterNames(method);

3.2.2  构建缓存Key

第一步、声明并初始化成员变量ExpressionParser

    private ExpressionParser parser = new SpelExpressionParser();

第二步、构建SpringEL上下文

    从需求可以知道上下文中就是参数名称和参数值的映射关系

String[] paraNameArr = nameDiscoverer.getParameterNames(method);
        Object[] args = pjp.getArgs();
        //SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();            //把方法参数放入SPEL上下文中
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }

第三步、解析构建缓存key

    StringBuilder sb = new StringBuilder();
        for (String expressionString : soloCache.value()) {
            sb.append(cache.separator());
            Object temp = parser.parseExpression(expressionString).getValue(context);
            String str = temp == null ? "null" : temp.toString();
            sb.append(str);
        }
        return sb.toString(); 

3.2.3环绕切面处理

        环绕切面处理核心流程就是首先从缓存中获取数据,如果有就直接返回,如果没有就执行原始方法并有效数据写入到缓存中。

        Object object = cacheClient.get(cache.prefix(), key);
        
        if (object != null) {
            return object;
        } else {

            Object value = pjp.proceed();
            if (value != null) {
               
             cacheClient.set(soloCache.prefix(), key, value, soloCache.expire());
                
            }
            return value;
        }

四、源码
@Aspect
@Component
public class SoloCacheAdvice {


    private ExpressionParser parser = new SpelExpressionParser();

    @Resource
    private CacheClient cacheClient;

    private LocalVariableTableParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer();


    @Around(value = "@annotation(cache)")
    public Object invoke(ProceedingJoinPoint pjp, Cache cache) throws Throwable {

        Method method = getMethod(pjp);
        //获取被拦截方法参数名列表(使用Spring支持类库)
        String[] paraNameArr = nameDiscoverer.getParameterNames(method);
        Object[] args = pjp.getArgs();
        //SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();            //把方法参数放入SPEL上下文中
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        String key = createKey(cache, context);
        Object object = cacheClient.get(soloCache.prefix(), key);
        if (object != null) {
            return object;
        } else {

            Object value = pjp.proceed();
            if (value != null) {
                try {
                    cacheClient.set(cache.prefix(), key, value, cache.expire());
                } catch (Exception e) {
                    //error Log
                }
            }
            return value;
        }

    }
    
    private String createKey(Cache cache, StandardEvaluationContext context) {
        StringBuilder sb = new StringBuilder();
        for (String expressionString : cache.value()) {
            sb.append(cache.separator());
            Object temp = parser.parseExpression(expressionString).getValue(context);
            String str = temp == null ? "null" : temp.toString();
            sb.append(str);
        }
        return sb.toString();
    }


   

    public Method getMethod(ProceedingJoinPoint pjp) {
        //获取参数的类型
        Class[] argTypes = ((MethodSignature) pjp.getSignature()).getMethod().getParameterTypes();

        Method method = null;
        try {
            method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);
        } catch (NoSuchMethodException | SecurityException e) {
           //error Log
        }
        return method;

    }
}

五、其他问题

5.1为什么无法获取到参数名称

        首先检查一下maven配置是否配置了true

        
                org.apache.maven.plugins
                maven-compiler-plugin
                3.8.1
                
                    ${java.version}
                    ${java.version}
                    ${project.build.sourceEncoding}
                    false
                    false
                    true
                
            

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1013768.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号