shiro源碼分析(二)-- 註解實現原理

shiro的註解實現藉助於aspectj框架,先通過一個例子熟悉下aspectj用法

一、小demo

先在pom.xml文件添加相關依賴

        <!--aspectj依賴-->
		<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
		...
		
        <!--編譯插件依賴-->
		<plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.4</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <showWeaveInfo>true</showWeaveInfo>
            </configuration>
            <executions>
                <execution>
                    <id>aspectj-compile</id>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <dependencies>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjtools</artifactId>
                    <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的類繼承圖如下
AspectjAnnotationsAuthorizingMethodInterceptor
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爲例分析其實現邏輯
RoleAnnotationMethodInterceptor

	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框架時想加入自定義的註解,或者想自己搞個類似的功能都有了很好的參考思路。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章