spring aop获取目标对象的方法对象(包括方法上的注解)
这两天在学习权限控制模块。以前看过传智播客黎活明老师的巴巴运动网视频教程,里面就讲到权限控制的解决方案,当时也只是看看视频,没有动手实践,虽说看过几遍,可是对于系统中的权限控制还是很迷茫,所以借着这次机会动手实践一下。
黎活明老师的巴巴运动网使用的框架是struts + spring + jpa,大致思路是使用自定义注解,在需要权限控制的方法前使用注解定义方法所需的权限,然后使用AOP拦截访问的方法,在执行目标对象前通过反射取得目标对象所需的权限,然后从当前session中取得登陆用户,遍历用户所拥有的权限,如果有权限则继续执行目标对象,如果没有权限则跳转到错误提示页面。巴巴运动网使用的struts + spring + jpa应用这种方案是有问题的,大致是spring aop无法拦截通过反射调用的方法,然后黎活明老师通过定制RequestProcessor 解决了这个问题。具体权限实现方案及对这种方案应用到巴巴运动网的缺陷的分析可以参看黎活明老师的巴巴运动网视频教程。
我在实践的过程中采用的是spring mvc + spring + hibernate 的框架,因此使用spring aop拦截是完全可以实现这种权限方案的。因此我在系统中定义了一个切面,申明了切入点及一个环绕通知。
以下是被拦截的方法申明,方法上有做权限控制的注解Permission
- @Permission(module="user",operation="select")
- @RequestMapping(value="/detail/{uid}",method=RequestMethod.GET)
- public String detail(@PathVariable int uid,Model model){
- ....
- }
以下是AOP定义
- @Pointcut("execution(java.lang.String com.jiangnan.cms.controller..*.*(..))")
- public void controller(){}
- @Around("controller()")
- public Object introcepter(ProceedingJoinPoint pjp) throws Throwable{
- System.out.println("拦截到了" + pjp.getSignature().getName() +"方法...");
- }
测试的时候日志中的确输出了被拦截的方法。可是pjp.getSignature().getName()只是方法的名称,做权限控制需要得到方法上的注解Permission,那么就需要获取目标对象上的Method对象,通过Method对象的getAnnotation(Permission.class)方法获取注解。可是怎么怎么直接获取Method对象而不是方法名称呢?通过pjp.getSignature()方法获取的Signature方法上好像没有直接getMethod()方法,于是去问度娘,得出如下的转换:
- Signature signature = pjp.getSignature();
- MethodSignature methodSignature = (MethodSignature)signature;
- Method targetMethod = methodSignature.getMethod();
这下终于满足需求了,于是迅速的写出了权限控制的代码:
- public Object introcepter(ProceedingJoinPoint pjp) throws Throwable{
- System.out.println("拦截到了" + pjp.getSignature().getName() +"方法...");
- Signature signature = pjp.getSignature();
- MethodSignature methodSignature = (MethodSignature)signature;
- Method targetMethod = methodSignature.getMethod();
- Class clazz = targetMethod.getClass();
- if(clazz.isAnnotationPresent(Permission.class)){
- //获取方法上注解中表明的权限
- Permission permission = (Permission)clazz.getAnnotation(Permission.class);
- String module = permission.module();
- String operation = permission.operation();
- Privilege privilege = new Privilege(new PrivilegePK(module, operation));
- //获取当前用户拥有的权限
- User user = (User)ContextUtils.getHttpSession().getAttribute("employer");
- if(null != user){
- System.out.println(user.getUsername());
- }
- Set<Role> roles = user.getRoles();
- for(Role role : roles){
- if(role.getPrivileges().contains(privilege)){
- //如果当前用户拥有的权限包含方法注解上的权限,则执行被拦截到的方法
- return pjp.proceed();
- }
- }
- //如果没有权限,抛出异常,由Spring框架捕获,跳转到错误页面
- throw new PermissionException();
- }
- return pjp.proceed();
- }
然后接着测试,可是在测试的过程中发现对于需要权限验证的detail方法,居然直接执行了,权限控制并没有起作用,于是debug调试,发现
- clazz.isAnnotationPresent(Permission.class)
返回的是false,检查下,被拦截的方法上有Permission注解啊,而且注解的定义是方法级别的,作用范围是运行期啊,这个没错啊,重启了Eclipse,重新发布了,结果还是这样,郁闷啊。。。接着把生成的.class文件反编译,看到类上有Permission注解的呀,真是百思不得其解啊。。。实在想不明白,关灯睡觉了。
第二天,心里老是想着这个问题,牵挂着他,很难受啊!突然灵光一闪,会不会这个获取到得Method对象不是目标对象上的Method对象,因为通过检查,目标类上的Method上是的确有那个注解的,除非拦截到的Method对象不是目标对象上的,是代理对象上的,而这个代理对象上的这个方法上没有Permission注解。然后去网上搜了下这方面的资料,原来spring aop使用cglib生成的代理是不会加上父类的方法上的注解的,也就是这边生成的代理类上的方法上没有Permission注解,然后也看到了一篇老外的文章,上面有所提到,但那时针对接口实现的代理,大意是通过接口生成的代理,通过
- Signature signature = pjp.getSignature();
- MethodSignature methodSignature = (MethodSignature)signature;
- Method targetMethod = methodSignature.getMethod();
这段代码获取的targetMethod对象是接口上的方法,他上面也是没有注解的(原文地址http://stackoverflow.com/questions/5714411/getting-the-java-lang-reflect-method-from-a-proceedingjoinpoint)。但是我这边不是通过接口生成代理的啊,是使用cglib通过继承目标对象生成代理的啊,难道这边获取的targetMethod对象是代理对象上的?于是就想证明。于是在代码中输出以下信息:
- Signature signature = pjp.getSignature();
- MethodSignature methodSignature = (MethodSignature)signature;
- Method targetMethod = methodSignature.getMethod();
- System.out.println("classname:" + targetMethod.getDeclaringClass().getName());
- System.out.println("superclass:" + targetMethod.getDeclaringClass().getSuperclass().getName());
- System.out.println("isinterface:" + targetMethod.getDeclaringClass().isInterface());
- System.out.println("target:" + pjp.getTarget().getClass().getName());
- System.out.println("proxy:" + pjp.getThis().getClass().getName());
- System.out.println("method:" + targetMethod.getName());
结果如下:
- classname:com.jiangnan.cms.controller.LogonController
- superclass:java.lang.Object
- isinterface:false
- target:com.jiangnan.cms.controller.LogonController
- proxy:com.jiangnan.cms.controller.LogonController
EnhancerByCGLIBf6998fd8
- method:logon
其他的都可以理解,按理3来说classname输出的应该是代理对象,应该是com.jiangnan.cms.controller.LogonController
f6998fd8,而不是com.jiangnan.cms.controller.LogonController,因为通过methodSignature对象获取的Method上没有Permission注解,所以通过getDeclaringClass获取定义该method的对象应该是代理对象而不是目标对象啊,这个没法解释啊,实验结果不能令人满意啊,不知道如何进行,希望有大神知道,或者以后想明白了再来完善。。。
至此,通过MethodSignature也无法直接获取目标对象的被拦截Method对象,那就只能用最笨的办法了,通过反射获取,刚才提到的一篇文章(http://stackoverflow.com/questions/5714411/getting-the-java-lang-reflect-method-from-a-proceedingjoinpoint)有说明,代码如下:
- Class[] parameterTypes = new Class[pjp.getArgs().length];
- Object[] args = pjp.getArgs();
- for(int i=0; i<args.length; i++) {
- if(args[i] != null) {
- parameterTypes[i] = args[i].getClass();
- }else {
- parameterTypes[i] = null;
- }
- }
- String methodName = pjp.getSignature().getName();
- Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, parameterTypes);
这种方式能实现,但是麻烦了点,简化了下,最终代码如下:
- Method realMethod = pjp.getTarget().getClass().getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());
此处获取到的realMethod就是目标对象上的,realMethod.isAnnotationPresent(Permission.class)返回的是true。 对于这些东西还是要亲身实践的,视频要看,尤其是好的视频,要看不止一遍,但是看过了一定要动手实践,看看视频是简单的,完全不知道自己动手会遇到这些问题,不过遇到问题是好事,解决这些问题自己才能成长!