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框架时想加入自定义的注解,或者想自己搞个类似的功能都有了很好的参考思路。

这是一个shiro的入门Demo.. 使用了Spring MVC,mybaits等技术.. 数据库设计 : User : name--password Role : id--userid--roleName Function : id--userid--url tinys普通用户只能访问index.jsp admin用户通过添加了admin的permission,所以可以访问admin.jsp role用户通过添加了role角色,所以可以访问role.jsp 这是最基本的shiro的运用..目的是让你快速了解shiro的机制.. 这个Demo体现shiro的地方主要在两个类以及shiro.xml的配置文件 CustomRealm : 处理了登录验证以及授权.. ShiroAction : 用来传递登录时的用户数据..转换为token传递给realm...之后根据结果做相应的逻辑处理.. shiro.xml : shiro的主要配置... 规则定义在以下地方 : <!-- 过滤链定义 --> <property name="filterChainDefinitions"> <value> /login.jsp* = anon /index.jsp* = authc /index.do* = authc /admin.jsp*=authc,perms[/admin] /role.jsp*=authc,roles[role] </value> </property> 2015-10-28更新 --通过添加了以下内容来使用注解方式配置权限.... <!-- Support Shiro Annotation 必须放在springMVC配置文件中 --> <!-- 异常处理,权限注解会抛出异常,根据异常返回相应页面 --> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="org.apache.shiro.authz.UnauthorizedException">unauth</prop> <prop key="org.apache.shiro.authz.UnauthenticatedException">login</prop> </props> </property> </bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" /> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> </bean> <!-- end --> --修改了过滤链 <!-- 过滤链定义 --> //简单的讲就是把需要特别处理的路径写到前面,越特殊写到越前 <property name="filterChainDefinitions"> <value> <!-- 注意这里需要把前缀写全.../shiro这里 --> /shiro/login.do*=anon /login.jsp* = anon /admin.jsp*=authc,perms[/admin] /role.jsp*=authc,roles[role] /** = authc </value> </property>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值