背景介绍:某工厂设备管理项目中一个定时任务用于生成设备维护工单,在点击下方立即执行按钮时,会抛出下图所示的异常信息或者出现“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)等,都可以从此切入点考虑下。
以上,完了!!