shiro的注解实现借助于aspectj框架,先通过一个例子熟悉下aspectj用法
先在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的类继承图如下
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框架时想加入自定义的注解,或者想自己搞个类似的功能都有了很好的参考思路。