shiro源码分析(二)-- 注解实现原理

shiro的注解实现借助于aspectj框架,先通过一个例子熟悉下aspectj用法

一、小demo

先在pom.xml文件添加相关依赖

        
		<dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjrtartifactId>
            <version>${aspectj.version}version>
        dependency>
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>${aspectj.version}version>
        dependency>
		...
		
        
		<plugin>
            <groupId>org.codehaus.mojogroupId>
            <artifactId>aspectj-maven-pluginartifactId>
            <version>1.4version>
            <configuration>
                <source>1.8source>
                <target>1.8target>
                <showWeaveInfo>trueshowWeaveInfo>
            configuration>
            <executions>
                <execution>
                    <id>aspectj-compileid>
                    <goals>
                        <goal>compilegoal>
                        <goal>test-compilegoal>
                    goals>
                execution>
            executions>
            <dependencies>
                <dependency>
                    <groupId>org.aspectjgroupId>
                    <artifactId>aspectjtoolsartifactId>
                    <version>${aspectj.version}version>
                dependency>
            dependencies>
        plugin>

新建一个注解@AspectTest,类似于shiro中的@RequiresUser等

	@Target({ElementType.TYPE,ElementType.METHOD})
	@Retention(RetentionPolicy.RUNTIME)
	public @interface AspectTest {
	    String[] value() default "";
	}

新建一个类,用于待会增强其方法

	@AspectTest("类上的注解")
	public class AspectDemo {
    @AspectTest("方法上的注解")
	    public void hello(String name) {
	        System.out.println("hello " + name);
	    }
	}

新建一个切面类

	@Aspect
	public class MyAspect {

		//表示当执行的是带有@AspectTest注解的任意返回值的任意名称的方法
    	private static final String pointCupExpression =
            "execution(@aspectdemo.AspectTest * *(..))";
		//一个切点
	    @Pointcut(pointCupExpression)
	    public void anyMethod(){}
		//一个切点
	    @Pointcut(pointCupExpression)
	    public void anyMethodCall(JoinPoint joinPoint){}
		//执行切点前
	    @Before("anyMethodCall(joinPoint)")
	    public void executeAnnotatedMethod(JoinPoint joinPoint) throws Throwable {
	        System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
	        System.out.println("目标方法所属类的简单类名:" +        joinPoint.getSignature()
	                .getDeclaringType().getSimpleName());
	        System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
	        System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
	        //获取传入目标方法的参数
	        Object[] args = joinPoint.getArgs();
	        for (int i = 0; i < args.length; i++) {
	            System.out.println("第" + (i+1) + "个参数为:" + args[i]);
	        }
	        System.out.println("被代理的对象:" + joinPoint.getTarget());
	        System.out.println("代理对象自己:" + joinPoint.getThis());
	        //获取AspectTest注解的值
	        AspectTest aspectTest = ((MethodSignature) joinPoint.getSignature()).getMethod()
	                .getAnnotation(AspectTest.class);
	        if(aspectTest!=null) {
	            String[] value = aspectTest.value();
	            for (int i = 0; i < value.length; i++) {
	                System.out.println("第" + (i+1) + "个注解值为:" + value[i]);
	            }
	        }
	    }
		//执行切点前
	    @Before("anyMethod()")
	    public void executeAnnotatedMethod() throws Throwable {}
	
	    public static void main(String... args){
	        AspectDemo demo=new AspectDemo();
	        demo.hello("season");
	    }
	}

JoinPoint对象提供了丰富的api来获取待执行方法的相关信息

执行上面的main方法,得到的输出结果是:

目标方法名为:hello
目标方法所属类的简单类名:AspectDemo
目标方法所属类的类名:aspectdemo.AspectDemo
目标方法声明类型:public
第1个参数为:season
被代理的对象:aspectdemo.AspectDemo@72ea2f77
代理对象自己:aspectdemo.AspectDemo@72ea2f77
第1个方法上的注解值为:方法上的注解
第1个类上的注解值为:类上的注解
hello season


二、源码分析

接下来看看shiro对权限注解方式的的实现。

首先有个切面类

	@Aspect()
	public class ShiroAnnotationAuthorizingAspect {

	    private static final String pointCupExpression =
	            "execution(@org.apache.shiro.authz.annotation.RequiresAuthentication * *(..)) || " +
	                    "execution(@org.apache.shiro.authz.annotation.RequiresGuest * *(..)) || " +
	                    "execution(@org.apache.shiro.authz.annotation.RequiresPermissions * *(..)) || " +
	                    "execution(@org.apache.shiro.authz.annotation.RequiresRoles * *(..)) || " +
	                    "execution(@org.apache.shiro.authz.annotation.RequiresUser * *(..))";
	
	    @Pointcut(pointCupExpression)
	    public void anyShiroAnnotatedMethod(){}
	
	    @Pointcut(pointCupExpression)
	    void anyShiroAnnotatedMethodCall(JoinPoint thisJoinPoint) {
	    }
	
	    private AspectjAnnotationsAuthorizingMethodInterceptor interceptor =
	            new AspectjAnnotationsAuthorizingMethodInterceptor();
	
	    @Before("anyShiroAnnotatedMethodCall(thisJoinPoint)")
	    public void executeAnnotatedMethod(JoinPoint thisJoinPoint) throws Throwable {
	        interceptor.performBeforeInterception(thisJoinPoint);
	    }
	}

可以看到上面对带有RequiresAuthentication、RequiresGuest、RequiresPermissions、RequiresRoles、RequiresUser注解的方法执行时,会进行代理。在执行这些方法前,调用了interceptor.performBeforeInterception(thisJoinPoint)。

	protected void performBeforeInterception(JoinPoint aJoinPoint) throws Throwable {

        // 转换成shiro自己封装的BeforeAdviceMethodInvocationAdapter
        BeforeAdviceMethodInvocationAdapter mi = BeforeAdviceMethodInvocationAdapter.createFrom(aJoinPoint);
        // 开始调用
        super.invoke(mi);
    }

	public static BeforeAdviceMethodInvocationAdapter createFrom(JoinPoint aJoinPoint) {
        if (aJoinPoint.getSignature() instanceof MethodSignature) {
            return new BeforeAdviceMethodInvocationAdapter(aJoinPoint.getThis(),
                    ((MethodSignature) aJoinPoint.getSignature()).getMethod(),
                    aJoinPoint.getArgs());

        } else if (aJoinPoint.getSignature() instanceof AdviceSignature) {
            return new BeforeAdviceMethodInvocationAdapter(aJoinPoint.getThis(),
                    ((AdviceSignature) aJoinPoint.getSignature()).getAdvice(),
                    aJoinPoint.getArgs());

        } else {
			//不支持
            throw ...
        }
    }

shiro自己定义了一个接口MethodInvocation,这个类似于aspectj里的JoinPoint。
.
public interface MethodInvocation {
//调用方法链
Object proceed() throws Throwable;
Method getMethod();
Object[] getArguments();
Object getThis();
}
而BeforeAdviceMethodInvocationAdapter只是简单地实现该接口。
.
public class BeforeAdviceMethodInvocationAdapter implements MethodInvocation {
private Object _object;
private Method _method;
private Object[] _arguments;
public BeforeAdviceMethodInvocationAdapter(Object anObject, Method aMethod, Object[] someArguments) {
_object = anObject;
_method = aMethod;
_arguments = someArguments;
}
public Object[] getArguments() {
return _arguments;
}
public Method getMethod() {
return _method;
}
public Object proceed() throws Throwable {
// Do nothing since this adapts a before advice
return null;
}
public Object getThis() {
return _object;
}
}

	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		//验证运行该方法是否满足权限要求
        assertAuthorized(methodInvocation);
		//如果上面没有抛异常,那么调用方法或方法链(上面的proceed方法是什么都不做)
        return methodInvocation.proceed();
    }

AspectjAnnotationsAuthorizingMethodInterceptor的类继承图如下
shiro源码分析(二)-- 注解实现原理_第1张图片
MethodInterceptor接口定义了invoke方法;
MethodInterceptorSupport增加了getSubject方法来获取当前用户; AuthorizingMethodInterceptor则是实现了invoke方法逻辑(上面的代码),并提供assertAuthorized方法给子类实现
AnnotationsAuthorizingMethodInterceptor实现了assertAuthorized方法

来看看AnnotationsAuthorizingMethodInterceptor的实现逻辑

	public abstract class AnnotationsAuthorizingMethodInterceptor extends AuthorizingMethodInterceptor {
	    protected Collection<AuthorizingAnnotationMethodInterceptor> methodInterceptors;
		//一开始就添加shiro支持的注解插值器
	    public AnnotationsAuthorizingMethodInterceptor() {
	        methodInterceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
	        methodInterceptors.add(new RoleAnnotationMethodInterceptor());
	        methodInterceptors.add(new PermissionAnnotationMethodInterceptor());
	        methodInterceptors.add(new AuthenticatedAnnotationMethodInterceptor());
	        methodInterceptors.add(new UserAnnotationMethodInterceptor());
	        methodInterceptors.add(new GuestAnnotationMethodInterceptor());
	    }
	
	    public Collection<AuthorizingAnnotationMethodInterceptor> getMethodInterceptors() {
	        return methodInterceptors;
	    }
	    public void setMethodInterceptors(Collection<AuthorizingAnnotationMethodInterceptor> methodInterceptors) {
	        this.methodInterceptors = methodInterceptors;
	    }
		//遍历已存在的注解插值器,进行鉴权
	    protected void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException {
	        Collection<AuthorizingAnnotationMethodInterceptor> aamis = getMethodInterceptors();
	        if (aamis != null && !aamis.isEmpty()) {
	            for (AuthorizingAnnotationMethodInterceptor aami : aamis) {
					//如果遍历到的注解插值器支持处理当前方法的注解,就进行鉴权
	                if (aami.supports(methodInvocation)) {
	                    aami.assertAuthorized(methodInvocation);
	                }
	            }
	        }
	    }
	}   

可以看到,这里先会调用注解插值器的supports方法,如果返回true,再调用其assertAuthorized方法。

下面以RoleAnnotationMethodInterceptor为例分析其实现逻辑
shiro源码分析(二)-- 注解实现原理_第2张图片

	public class RoleAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {

	    public RoleAnnotationMethodInterceptor() {
	        super( new RoleAnnotationHandler() );
	    }
		...
	}

	public AuthorizingAnnotationMethodInterceptor( AuthorizingAnnotationHandler handler ) {
        super(handler);
    }

	public AnnotationMethodInterceptor(AnnotationHandler handler) {
        this(handler, new DefaultAnnotationResolver());
    }

	public AnnotationMethodInterceptor(AnnotationHandler handler, AnnotationResolver resolver) {
        ...
        setHandler(handler);
        setResolver(resolver != null ? resolver : new DefaultAnnotationResolver());
    }

参照上面的类继承图看代码就没那么晕,可以看到实例化RoleAnnotationMethodInterceptor的时候,会创建RoleAnnotationHandler和DefaultAnnotationResolver对象。

下面看supports方法实现逻辑

	public boolean supports(MethodInvocation mi) {
		//获取相关注解不为空
        return getAnnotation(mi) != null;
    }
	protected Annotation getAnnotation(MethodInvocation mi) {
        return getResolver().getAnnotation(mi, getHandler().getAnnotationClass());
    }

可以看到是通过getResolver得到的对象来获取注解,其实就是上面说到的DefaultAnnotationResolver对象;而getHandler得到的就是上面说到的RoleAnnotationHandler对象,其getAnnotationClass返回的是它支持处理的注解

	public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
        //省略判空异常处理...
        Method m = mi.getMethod();
        //省略判空异常处理...
		//先尝试从方法获取注解
        Annotation annotation = m.getAnnotation(clazz);
        if (annotation == null ) {//为空就继续尝试从类上获取注解
            Object miThis = mi.getThis();
            annotation = miThis != null ? miThis.getClass().getAnnotation(clazz) : null;
        }
        return annotation;
    }

可以看到shiro对注解的处理时,方法上的优先级比类上的优先级高

当supports方法返回true就开始调用插值器的assertAuthorized方法

	public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
        try {
            ((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi));
        }
        catch(AuthorizationException ae) {
            throw ae;
        }         
    }

可以看到实现逻辑是调用getHandler得到的对象的assertAuthorized方法。

RoleAnnotationMethodInterceptor的getHandler返回的是RoleAnnotationHandler对象,来看一下其实现逻辑

	public class RoleAnnotationHandler extends AuthorizingAnnotationHandler {
	
	    public RoleAnnotationHandler() {
	        super(RequiresRoles.class);//设置支持处理的注解
	    }
	    public void assertAuthorized(Annotation a) throws AuthorizationException {
	        if (!(a instanceof RequiresRoles)) return;
			
	        RequiresRoles rrAnnotation = (RequiresRoles) a;
	        String[] roles = rrAnnotation.value();//获取注解值
	
	        if (roles.length == 1) {//只有一个值,那直接鉴权
	            getSubject().checkRole(roles[0]);
	            return;
	        }
			//下面是有多个的情况,分两种情况处理,AND和OR
	        if (Logical.AND.equals(rrAnnotation.logical())) {
				//需要满足所有角色要求
	            getSubject().checkRoles(Arrays.asList(roles));
	            return;
	        }
			//OR只要符合一个角色就鉴权成功
	        if (Logical.OR.equals(rrAnnotation.logical())) {
	            boolean hasAtLeastOneRole = false;
				//先用hasRole方法遍历判断一遍,hasRole是不抛异常的
	            for (String role : roles) //可以根据hasAtLeastOneRole标志判断下提前结束循环
					if (getSubject().hasRole(role)) 
						hasAtLeastOneRole = true;
	            if (!hasAtLeastOneRole) //如果上面都找不到,就调checkRole抛异常
					getSubject().checkRole(roles[0]);
	        }
	    }
	}

总结

通过分析shiro的注解实现原理,以后自己在用shiro框架时想加入自定义的注解,或者想自己搞个类似的功能都有了很好的参考思路。

你可能感兴趣的:(shiro源码学习)