记一次“Shiro+任务调度”开发过程中出现UnavailableSecurityManagerException解决思路

背景介绍:某工厂设备管理项目中一个定时任务用于生成设备维护工单,在点击下方立即执行按钮时,会抛出下图所示的异常信息或者出现“No SecurityManager accessible to the calling code”

java.util.concurrent.ExecutionException: com.sugon.utils.RRException: 执行定时任务失败
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.sugon.utils.ScheduleJob.executeInternal(ScheduleJob.java:55)
	at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75)
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)
Caused by: com.sugon.utils.RRException: 执行定时任务失败
	at com.sugon.utils.ScheduleRunnable.run(ScheduleRunnable.java:40)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.sugon.utils.ScheduleRunnable.run(ScheduleRunnable.java:37)
	... 5 more
Caused by: org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton.  This is an invalid application configuration.

分析:简单来说定时任务执行时会开辟一个新的线程用来执行当前JOB,新线程在执行JOB逻辑时触发了UnavailableSecurityManagerException,请注意异常提示信息:“No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton.  This is an invalid application configuration.”,简而言之就是在当前线程中找不到shiro框架必要的SecurityManager对象,要解决这个问题要么绑定SecurityManager对象到ThreadContext中,要么将SecurityManager声明为VM静态单例。由于在spring框架中,所以采用将SecurityManager对象绑定到每个线程的ThreadContext对象(shiro提供的用来绑定/解绑对象到当前线程的方式或手段)中。

解决思路:简单来说可以在每个JOB逻辑执行前判断下当前线程是否已有SecurityManager实例,如果没有则从Spring容器中获取SecurityManager并绑定到当前ThreadContext中。大致代码如下:

SecurityManager securityManager = ThreadContext.getSecurityManager();
if (securityManager == null) {
	securityManager = SpringContextUtils.getBean("securityManager", DefaultWebSecurityManager.class);
	ThreadContext.bind(securityManager);
}

考虑到如果项目中有多个JOB的话,重复代码大量出现在多个地方。本着高可用的原则,采用注解+切面的方式,对每个JOB执行前都增加上述逻辑,实现代码如下,每个代码已简化结构和去除注释:

1、声明校验性注解

public @interface ShiroSecurityManagerChecker {
}

2、编写逻辑处理AOP,对每个采用@ShiroSecurityManagerChecker注解的逻辑执行前都执行确保每个线程存在SecurityManager的逻辑

@Aspect
@Component
public class ShiroSecurityManagerCheckerAspect {

    @Pointcut("@annotation(com.sugon.annotation.ShiroSecurityManagerChecker)")
    public void dataFilterCut() {

    }

    @Before("dataFilterCut()")
    public void dataFilter(JoinPoint point) {
        SecurityManager securityManager = ThreadContext.getSecurityManager();
        if (securityManager == null) {
            securityManager = SpringContextUtils.getBean("securityManager", DefaultWebSecurityManager.class);
            ThreadContext.bind(securityManager);
        }
    }

}

3、在需要的地方添加注解,demo如下,按需添加:

@ShiroSecurityManagerChecker
public void process() {
     
}

总结:由于shiro中大部分功能都依赖于SecurityManager对象,所以在多线程环境下一定要多注意SecurityManager是否存在。当然如果多线程执行逻辑中不涉及Shiro相关内容,则无视!一般来说只要是出现UnavailableSecurityManagerException异常或者提示找不到SecurityManager对象(No SecurityManager accessible to the calling code)等,都可以从此切入点考虑下。

以上,完了!!

你可能感兴趣的:(Spring,Shiro,java之任务调度,spring,aop,多线程,shiro,java)