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的類繼承圖如下
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爲例分析其實現邏輯
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框架時想加入自定義的註解,或者想自己搞個類似的功能都有了很好的參考思路。