场景:
SpringMVC HandlerInterceptor只能提供基于http url request的AOP拦截,如果Controller中某些RequestMapping handler method需要权限才能访问----当然可以通过handler method对应url的HandlerInterceptor来完成这个要求,这样就比较麻烦:HandlerInterceptor配置文件中设置了一堆的url字符串,而且不停的增长、膨胀。。。
如果通过能用handler method 级别的aop来完成这个功能,只需要在handler method上添加一个Annotation表示当前方法需要权限验证即可,然后通过 AOP Advice来做这些判断。
代码:
1、定义Annotation:
/**
* true:user should be authed before the action.false:needn't.
*/
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthRequired {
public boolean value() default true;
}
2、在目标方法上定义:
@Controller
@RequestMapping("/")
public class UserController{
@RequestMapping(value="/demo")
@AuthRequired //http访问表示当前方法,需要用户先登陆
public @ResponseBody Map<String, String>> demo(@RequestAttribute("loginUserId") long loginUserId){
return Collections.emptyMap();
}
}
3、AOP定义
1、Advice
/**
* verify whether user is authed
*
*/
@Component("authAdvice")
public class AuthAdvice implements MethodBeforeAdvice {
private static final Logger logger = LoggerFactory.getLogger(AuthAdvice.class);
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();//获取request
Long userId = (Long) request.getAttribute("loginUserId");
AuthRequired needAuth = AnnotationUtils.findAnnotation(method, AuthRequired.class);
if (needAuth != null&&needAuth.value) {
if(userId == null)
throw new NotLoginException("not login");
}
}
2、PointcutAdvisor
/**
* aspect pointcut for the controller and verifying whether is logined.
*
*/
@Component("authAdvisor")
public class AuthPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor {
/**
* must be the {@link RequestMapping}
*/
@Override
public boolean matches(Method method, Class<?> targetClass) {
RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (mapping != null) {// 只拦截RequestMapping修饰的方法
return true;
}
return false;
}
/**
* must be {@link Controller}
*/
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
return AnnotationUtils.isAnnotationDeclaredLocally(Controller.class, clazz);
}
};
}
@Resource(name = "authAdvice")
public void setAdvice(Advice advice) {
super.setAdvice(advice);
}
}
4、启用AOP
<!-- 必须在MethodValidationPostProcessor之前初始化,否则在authAdvice执行之前会优先执行Spring Validation,导致权限认证服务执行之前就执行parameter validation 操作-->
<bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor" depends-on="autoProxyCreator"/>
注:
Spring Request Scope
在SpringMVC中,如果使用DispatcherServlet初始SpringMVC容器,那么spring web application context默认启用request scope。故((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();能够正常获取HttpServletRequest对象。
Spring AOP
DefaultAdvisorAutoProxyCreator 比使用AspectJ和Schema-based AOP的好处是能够获取当前被拦截方法method对象相关的信息,例如method 上的Annotation、args上的annotation等