Spring切面编程(AOP)初实践 最近项目中有涉及,就稍微学习了下。

仅就个人感受而言,切面编程能够在不干扰程序已有业务逻辑的基础上很好地处理流程中某个阶段点的问题,就像在三明治中再插入一片火腿或番茄一样,但AOP(面向切面编程)并不仅仅是这些,更多思想层面的东西有兴趣的可以百度看看。我也是最近在开发过程中有这个需要,才略微接触了一些,感觉AOP在代码低耦合度的情况下,能够集中处理一些“点”,免去了批量修改代码的麻烦。

项目中只是很简单的使用,并没有用到高级特性。这里展示几段spring-boot中,配合注解使用切面(Aspect)来校验权限的代码。

首先定义权限的注解,也可以定义到类级别,我这里只定义到了Method级别。其中anchor是业务模块,func是该模块下的权限,如增、删、改。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 
 * @Description 后台管理权限校验注解
 * @date 2016年8月9日 下午1:59:08
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@Documented
public @interface ConsoleAuth {
    String anchor() default "";
    String func() default "";
}


spring在2.0后已经整合进了一些AOP的框架,我这里是使用spring自带的Aspectj,使用十分简单,这里直接贴切面处理类的代码:

import java.lang.reflect.Method;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import com.nd.annotation.ConsoleAuth;
import com.nd.common.base.BaseController;
import com.nd.common.utils.ObjectUtils;
import com.nd.exception.AuthenticationException;
import com.nd.po.ConsolePermissionEntity;
import com.nd.po.ConsolePermissionFunc;
import com.nd.po.UserInfo;
import com.nd.service.IConsolePermissionUserService;
/**
 * 
 * @Description 后台权限校验切面处理类
 * @date 2016年8月9日 下午2:29:57
 */
@Aspect
@Component
public class ConsoleAuthAspect extends BaseController {
    @Autowired
    ApplicationContext            context;
    @Autowired
    IConsolePermissionUserService iConUserService;
    /**
     * 
     * @Description Controller层切入点
     * @date 2016年8月9日 下午2:43:28
     */
    @Pointcut("@annotation(com.nd.annotation.ConsoleAuth)")
    public void aspectPoint() {
        System.out.println("=====");
    }
    @Before("aspectPoint()")
    public void handle(JoinPoint joinPoint) {
        UserInfo userInfo = getSessionUser();
        if (ObjectUtils.isEmpty(userInfo)) {
            throw new AuthenticationException("请先登录");
        }
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        ConsoleAuth consoleAuth = method.getAnnotation(ConsoleAuth.class);
//        System.out.println(consoleAuth.anchor());
//        System.out.println(consoleAuth.func());
        // 获取当前登录用户的权限
        List<ConsolePermissionEntity> permissions = iConUserService.getUserPerms(userInfo.getUid());
        boolean isAuth = false;
        for (ConsolePermissionEntity permission : permissions) {
            if (permission.getAnchor().equals(consoleAuth.anchor())) {// 是否拥有该菜单的权限
                // 方法级别权限校验
                List<ConsolePermissionFunc> funcs = permission.getFuncs();
                if (ObjectUtils.isEmpty(funcs)) {
                    continue;
                }
                for (ConsolePermissionFunc func : funcs) {
                    if (func.getCls().equals(consoleAuth.func())) {// 是否拥有该接口的权限
                        isAuth = true;
                        break;
                    }
                }
            }
        }
        if (!isAuth) {
            throw new AuthenticationException("权限不足");
        }
    }
}

如上面的代码所示,在切面类定义上添加注解@Aspect,然后声明一个切入点方法,并为方法增加注解@Pointcut,“@Pointcut”即是切入点的意思,后面的括号是进行切入的条件,比如我的代码中是“@annotation(com.nd.annotation.ConsoleAuth)”,即表示在有注解且注解为ConsoleAuth的时候进行切入,换句话说,我在哪个方法上添加了@ConsoleAuth,哪个方法就会被改切面类处理。

另外,Aspectj能够设置切入的时机,如上图中handle方法的注解@Before("aspectPoint()"),就表示该方法是在切入点即@ConsoleAuth所注解的方法执行前执行,同样的还有@After等等,总的来说,就类似于前置过滤、后置过滤一样。

在controller层面的使用:

    /**
     * @Description 创建用户权限
     * @date 2016年7月26日 下午5:44:16
     * @param consolePermissionUser
     * @return
     */
    @ConsoleAuth(anchor = "user/list", func = "add")
    @ResponseBody
    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public ConsolePermissionUser insertConsolePermissionUser(@RequestBody ConsolePermissionUser consolePermissionUser) {
        Log4J.info("PermissionController", "insertConsolePermissionUser", "创建用户权限");
        consolePermissionUser = iConsolePermissionUserService.insert(consolePermissionUser);
        return consolePermissionUser;
    }


这样对于我希望进行严格权限控制的接口只需要增加该注解即可。切面编程的方式在Log记录、权限控制等方面十分好用。