springboot确实简化了Java项目开发的不少繁琐的步骤,并且使代码更加看着更加友好了。另外spring的全注解开发摒弃了繁琐蛋疼的xml配置,因此相对单单这件事情来说这是一件皆大欢喜的事情。用惯了注解,想在任何语言中都想写个注解。好了不跑题了,说说springboot的AOP,全注解的那种。

AOP可以说是层的一种实现,更是HOOK大法在Java里面的一种表现形式。springboot中使用AOP还是挺简单的,直接引入spring-boot-starter-aop即可,不需要额外的注解去启动AOP。默认就是启动的。

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

相关的注解

//正常秩序:around -> before->method ->around -> after -> afterReturning
//连接点,被AOP拦截的类或者方法
@Joinpoint
//表明是AOP
@Aspect
//添加切入点
@Pointcut 
//前置通知
@Before 
//后置通知
@After 
//环绕通知
@Around 
//返回后通知
@AfterReturning 
//异常通知
@AfterReturning 
//引入新的方法
@DeclareParents

下面是一个简单的aspect类

package test;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
 
@Component
@Aspect
public class Aspect2 {
 
    @Before(value = "test.PointCuts.aopDemo()")
    public void before(JoinPoint joinPoint) {
        System.out.println("[Aspect2] before advise");
    }
 
    @Around(value = "test.PointCuts.aopDemo()")
    public void around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("[Aspect2] around advise 1");
        pjp.proceed();
        System.out.println("[Aspect2] around advise2");
    }
 
    @AfterReturning(value = "test.PointCuts.aopDemo()")
    public void afterReturning(JoinPoint joinPoint) {
        System.out.println("[Aspect2] afterReturning advise");
    }
 
    @AfterThrowing(value = "test.PointCuts.aopDemo()")
    public void afterThrowing(JoinPoint joinPoint) {
        System.out.println("[Aspect2] afterThrowing advise");
    }
 
    @After(value = "test.PointCuts.aopDemo()")
    public void after(JoinPoint joinPoint) {
        System.out.println("[Aspect2] after advise");
    }
}

Pointcut

spring 的AOP支持9种切入点表达式的写法

  • execute表达式

    早起使用最多的一种方式

    execution(public * *(..)) //拦截所有的公共方法

    execution(* set*(..)) //拦截所有以set开头的方法

    execution(* com.xyz.service.AccountService.*(..)) //拦截指定接口的所有方法

    execution(* com.xyz.service..(..)) //拦截包中定义的方法,不包含子包中的方法

    execution(* com.xyz.service...(..)) //拦截包或者子包中定义的方法

  • within

    within(com.xyz.service.*) //拦截包中任意方法,不包含子包中的方法

    within(com.xyz.service..*)//拦截包或者子包中定义的方法

  • this

    代理对象为指定的类型会被拦截

  • target

    目标对象为指定的类型被拦截

  • args

    args(com.ms.aop.args.demo1.UserModel)匹配方法中的参数

  • @target

    @target(com.ms.aop.jtarget.Annotation1)目标对象中包含com.ms.aop.jtarget.Annotation1注解,调用该目标对象的任意方法都会被拦截

  • @within

    @within(com.ms.aop.jwithin.Annotation1)声明有com.ms.aop.jwithin.Annotation1注解的类中的所有方法都会被拦截

  • @annotation

    @annotation(com.ms.aop.jannotation.demo2.Annotation1)//被调用的方法包含指定的注解

  • @args

    @args(com.ms.aop.jargs.demo1.Anno1)匹配1个参数,且第1个参数所属的类中有Anno1注解

实例

利用AOP是写rediscache注解。Spring-data-redis本身就支持类似的注解,但是其不能设置失效时间,如要支持需要修改源码,可以看之前的文章。

package com.imonsoon.system.annotation;

import java.lang.annotation.*;

@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BusRedisCache {
    /**
     * 缓存值主键前缀
     */
    String prefix ();

    /**
     * 缓存时间默认-1 单位未秒
     */
    long expire () default -1L;
}
package com.imonsoon.system.aspect;

import com.imonsoon.system.annotation.BusMobile;
import com.imonsoon.system.annotation.BusRedisCache;
import com.imonsoon.system.component.RedisComponent;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

@Aspect
@Component
@Order(2)
public class RedisCacheAspect {

    private static Logger logger = LoggerFactory.getLogger(RedisCacheAspect.class);

    @Autowired
    private RedisComponent redisComponent;

    @Around("@annotation(com.imonsoon.system.annotation.BusRedisCache)")
    public Object logWrited(ProceedingJoinPoint point) throws Throwable {
        String prefix = "";
        Long expire = 0L;
        String mobile = "";
        Class returnType = null;
        try {
            Object[] args = point.getArgs();
            Class<?>[] argTypes = new Class[args.length];
            for (int i = 0; i < args.length; i++) {
                argTypes[i] = args[i].getClass();
            }
            Method method = point.getTarget().getClass()
                    .getMethod(point.getSignature().getName(), argTypes);
            returnType = method.getReturnType();
            BusRedisCache ma = method.getAnnotation(BusRedisCache.class);
            prefix = ma.prefix();
            expire = ma.expire();

            Parameter[] parameters = method.getParameters();
            for (int i = 0; i < parameters.length; i++) {
                Parameter parameter = parameters[i];
                if (parameter.isAnnotationPresent(BusMobile.class)) {
                    mobile = args[i].toString();
                }
            }
            if (mobile == null || mobile.length() != 11) {
                return point.proceed();
            }
            String redisKey = (prefix + mobile).toUpperCase();

            Object object = redisComponent.get(redisKey);
            if (object != null && equalsClass(object.getClass().getName(), returnType.getName())) {
                logger.debug("RedisCacheAspect %s  key hit");
                return object;
            }
            Object objc = point.proceed();
            if (objc != null) {
                if (expire < 0) {
                    redisComponent.set(redisKey, objc);
                } else if (expire > 0) {
                    redisComponent.set(redisKey, objc, expire);
                }
            }
            return objc;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static boolean equalsClass(String className, String className1) {
        if (className.equals(className1)) {
            return true;
        }
        //System.out.println(className+"====>"+className1);
        switch (className) {
            case "java.lang.Boolean":
                return className1.equals("boolean");
            case "java.lang.Byte":
                return className1.equals("byte");
            case "java.lang.Integer":
                return className1.equals("int");
            case "java.lang.Long":
                return className1.equals("long");
            case "java.lang.Float":
                return className1.equals("flaot");
            case "java.lang.Double":
                return className1.equals("double");
            default:
                return className.equals(className1);
        }
    }
}

    @BusRedisCache(prefix = App.REDIXPREFIX_DBCONFIGBEAN, expire = 3600L)
    public String confKey(String key){
        ConfigBeanMapper mapper = this.getBaseMapper();
        ConfigBean bean = mapper.selectById(key);
        if(bean != null){
            return bean.getContent();
        }
        return null;
    }