首页
关于
友链
Search
1
wlop 4K 壁纸 4k8k 动态 壁纸
723 阅读
2
Docker搭建Typecho博客
570 阅读
3
Nacos持久化MySQL问题-解决方案
433 阅读
4
SpringBoot整合SpringCache
393 阅读
5
keytool证书导入
384 阅读
解决方案
JAVA基础
JVM
多线程
开源框架
数据库
前端
分布式
框架整合
中间件
容器部署
设计模式
数据结构与算法
开发工具
百度网盘资源
天翼网盘资源
阿里网盘资源
登录
Search
标签搜索
java
javase
docker
java8
springboot
thread
spring
分布式
mysql
锁
linux
redis
源码
typecho
centos
git
map
lambda
stream
nginx
少年
累计撰写
183
篇文章
累计收到
10
条评论
首页
栏目
解决方案
JAVA基础
JVM
多线程
开源框架
数据库
前端
分布式
框架整合
中间件
容器部署
设计模式
数据结构与算法
开发工具
百度网盘资源
天翼网盘资源
阿里网盘资源
页面
关于
友链
搜索到
9
篇与
的结果
2022-04-26
SpringBoot源码解析(一):启动过程
springboot源码解析(一):启动过程1、springboot的入口程序@SpringBootApplication public class StartupApplication { public static void main(String[] args) { SpringApplication.run(StartupApplication.class, args); } }当程序开始执行之后,会调用SpringApplication的构造方法,进行某些初始参数的设置//创建一个新的实例,这个应用程序的上下文将要从指定的来源加载Bean public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { //资源初始化资源加载器,默认为null this.resourceLoader = resourceLoader; //断言主要加载资源类不能为 null,否则报错 Assert.notNull(primarySources, "PrimarySources must not be null"); //初始化主要加载资源类集合并去重 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //推断当前 WEB 应用类型,一共有三种:NONE,SERVLET,REACTIVE this.webApplicationType = WebApplicationType.deduceFromClasspath(); //设置应用上线文初始化器,从"META-INF/spring.factories"读取ApplicationContextInitializer类的实例名称集合并去重,并进行set去重。(一共7个) setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //设置监听器,从"META-INF/spring.factories"读取ApplicationListener类的实例名称集合并去重,并进行set去重。(一共11个) setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //推断主入口应用类,通过当前调用栈,获取Main方法所在类,并赋值给mainApplicationClass this.mainApplicationClass = deduceMainApplicationClass(); }在上述构造方法中,有一个判断应用类型的方法,用来判断当前应用程序的类型: static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; } //WebApplicationType的类型 public enum WebApplicationType { /** * The application should not run as a web application and should not start an * embedded web server. * 非web项目 */ NONE, /** * The application should run as a servlet-based web application and should start an * embedded servlet web server. * servlet web 项目 */ SERVLET, /** * The application should run as a reactive web application and should start an * embedded reactive web server. * 响应式 web 项目 */ REACTIVE;springboot启动的运行方法,可以看到主要是各种运行环境的准备工作public ConfigurableApplicationContext run(String... args) { //1、创建并启动计时监控类 StopWatch stopWatch = new StopWatch(); stopWatch.start(); //2、初始化应用上下文和异常报告集合 ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //3、设置系统属性“java.awt.headless”的值,默认为true,用于运行headless服务器,进行简单的图像处理,多用于在缺少显示屏、键盘或者鼠标时的系统配置,很多监控工具如jconsole 需要将该值设置为true configureHeadlessProperty(); //4、创建所有spring运行监听器并发布应用启动事件,简单说的话就是获取SpringApplicationRunListener类型的实例(EventPublishingRunListener对象),并封装进SpringApplicationRunListeners对象,然后返回这个SpringApplicationRunListeners对象。说的再简单点,getRunListeners就是准备好了运行时监听器EventPublishingRunListener。 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { //5、初始化默认应用参数类 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //6、根据运行监听器和应用参数来准备spring环境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //将要忽略的bean的参数打开 configureIgnoreBeanInfo(environment); //7、创建banner打印类 Banner printedBanner = printBanner(environment); //8、创建应用上下文,可以理解为创建一个容器 context = createApplicationContext(); //9、准备异常报告器,用来支持报告关于启动的错误 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //10、准备应用上下文,该步骤包含一个非常关键的操作,将启动类注入容器,为后续开启自动化提供基础 prepareContext(context, environment, listeners, applicationArguments, printedBanner); //11、刷新应用上下文 refreshContext(context); //12、应用上下文刷新后置处理,做一些扩展功能 afterRefresh(context, applicationArguments); //13、停止计时监控类 stopWatch.stop(); //14、输出日志记录执行主类名、时间信息 if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } //15、发布应用上下文启动监听事件 listeners.started(context); //16、执行所有的Runner运行器 callRunners(context, applicationArguments); }catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { //17、发布应用上下文就绪事件 listeners.running(context); }catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } //18、返回应用上下文 return context; }下面详细介绍各个启动的环节:1、创建并启动计时监控类,可以看到记录当前任务的名称,默认是空字符串,然后记录当前springboot应用启动的开始时间。StopWatch stopWatch = new StopWatch(); stopWatch.start(); //详细源代码 public void start() throws IllegalStateException { start(""); } public void start(String taskName) throws IllegalStateException { if (this.currentTaskName != null) { throw new IllegalStateException("Can't start StopWatch: it's already running"); } this.currentTaskName = taskName; this.startTimeNanos = System.nanoTime(); }2、初始化应用上下文和异常报告集合ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();3、设置系统属性java.awt.headless的值:/* java.awt.headless模式是在缺少显示屏、键盘或者鼠标的系统配置 当配置了如下属性之后,应用程序可以执行如下操作: 1、创建轻量级组件 2、收集关于可用的字体、字体指标和字体设置的信息 3、设置颜色来渲染准备图片 4、创造和获取图像,为渲染准备图片 5、使用java.awt.PrintJob,java.awt.print.*和javax.print.*类里的方法进行打印 */ private void configureHeadlessProperty() { System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless))); }4、创建所有spring运行监听器并发布应用启动事件SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); //创建spring监听器 private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); } SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) { this.log = log; this.listeners = new ArrayList<>(listeners); } //循环遍历获取监听器 void starting() { for (SpringApplicationRunListener listener : this.listeners) { listener.starting(); } } //此处的监听器可以看出是事件发布监听器,主要用来发布启动事件 @Override public void starting() { //这里是创建application事件‘applicationStartingEvent’ this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args)); } //applicationStartingEvent是springboot框架最早执行的监听器,在该监听器执行started方法时,会继续发布事件,主要是基于spring的事件机制 @Override public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); //获取线程池,如果为空则同步处理。这里线程池为空,还未初始化 Executor executor = getTaskExecutor(); for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { if (executor != null) { //异步发送事件 executor.execute(() -> invokeListener(listener, event)); } else { //同步发送事件 invokeListener(listener, event); } } }5、初始化默认应用参数类ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); public DefaultApplicationArguments(String... args) { Assert.notNull(args, "Args must not be null"); this.source = new Source(args); this.args = args; }6、根据运行监听器和应用参数来准备spring环境ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //详细环境的准备 private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // 获取或者创建应用环境 ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置应用环境,配置propertySource和activeProfiles configureEnvironment(environment, applicationArguments.getSourceArgs()); //listeners环境准备,广播ApplicationEnvironmentPreparedEvent ConfigurationPropertySources.attach(environment); listeners.environmentPrepared(environment); //将环境绑定给当前应用程序 bindToSpringApplication(environment); //对当前的环境类型进行判断,如果不一致进行转换 if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } //配置propertySource对它自己的递归依赖 ConfigurationPropertySources.attach(environment); return environment; } // 获取或者创建应用环境,根据应用程序的类型可以分为servlet环境、标准环境(特殊的非web环境)和响应式环境 private ConfigurableEnvironment getOrCreateEnvironment() { //存在则直接返回 if (this.environment != null) { return this.environment; } //根据webApplicationType创建对应的Environment switch (this.webApplicationType) { case SERVLET: return new StandardServletEnvironment(); case REACTIVE: return new StandardReactiveWebEnvironment(); default: return new StandardEnvironment(); } } //配置应用环境 protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } //配置property sources configurePropertySources(environment, args); //配置profiles configureProfiles(environment, args); }7、创建banner的打印类Banner printedBanner = printBanner(environment); //打印类的详细操作过程 private Banner printBanner(ConfigurableEnvironment environment) { if (this.bannerMode == Banner.Mode.OFF) { return null; } ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader : new DefaultResourceLoader(getClassLoader()); SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner); if (this.bannerMode == Mode.LOG) { return bannerPrinter.print(environment, this.mainApplicationClass, logger); } return bannerPrinter.print(environment, this.mainApplicationClass, System.out); }8、创建应用的上下文:根据不同哦那个的应用类型初始化不同的上下文应用类context = createApplicationContext(); protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; default: contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }9、准备异常报告器exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }10、准备应用上下文prepareContext(context, environment, listeners, applicationArguments, printedBanner); private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { //应用上下文的environment context.setEnvironment(environment); //应用上下文后处理 postProcessApplicationContext(context); //为上下文应用所有初始化器,执行容器中的applicationContextInitializer(spring.factories的实例),将所有的初始化对象放置到context对象中 applyInitializers(context); //触发所有SpringApplicationRunListener监听器的ContextPrepared事件方法。添加所有的事件监听器 listeners.contextPrepared(context); //记录启动日志 if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // 注册启动参数bean,将容器指定的参数封装成bean,注入容器 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); //设置banner if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } // 加载所有资源,指的是启动器指定的参数 Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); //将bean加载到上下文中 load(context, sources.toArray(new Object[0])); //触发所有springapplicationRunListener监听器的contextLoaded事件方法, listeners.contextLoaded(context); } ------------------- //这里没有做任何的处理过程,因为beanNameGenerator和resourceLoader默认为空,可以方便后续做扩展处理 protected void postProcessApplicationContext(ConfigurableApplicationContext context) { if (this.beanNameGenerator != null) { context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, this.beanNameGenerator); } if (this.resourceLoader != null) { if (context instanceof GenericApplicationContext) { ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader); } if (context instanceof DefaultResourceLoader) { ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader()); } } if (this.addConversionService) { context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); } } --------------------- //将启动器类加载到spring容器中,为后续的自动化配置奠定基础,之前看到的很多注解也与此相关 protected void load(ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); } BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources); if (this.beanNameGenerator != null) { loader.setBeanNameGenerator(this.beanNameGenerator); } if (this.resourceLoader != null) { loader.setResourceLoader(this.resourceLoader); } if (this.environment != null) { loader.setEnvironment(this.environment); } loader.load(); } --------------------- //springboot会优先选择groovy加载方式,找不到在选择java方式 private int load(Class<?> source) { if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { // Any GroovyLoaders added in beans{} DSL can contribute beans here GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); load(loader); } if (isComponent(source)) { this.annotatedReader.register(source); return 1; } return 0; }11、刷新应用上下文refreshContext(context); private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } } ------------ public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. //刷新上下文环境,初始化上下文环境,对系统的环境变量或者系统属性进行准备和校验 prepareRefresh(); // Tell the subclass to refresh the internal bean factory. //初始化beanfactory,解析xml,相当于之前的xmlBeanfactory操作 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. //为上下文准备beanfactory,对beanFactory的各种功能进行填充,如@autowired,设置spel表达式解析器,设置编辑注册器,添加applicationContextAwareprocessor处理器等等 prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. //提供子类覆盖的额外处理,即子类处理自定义的beanfactorypostProcess postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. //激活各种beanfactory处理器 invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. //注册拦截bean创建的bean处理器,即注册beanPostProcessor registerBeanPostProcessors(beanFactory); // Initialize message source for this context. //初始化上下文中的资源文件如国际化文件的处理 initMessageSource(); // Initialize event multicaster for this context. //初始化上下文事件广播器 initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. //给子类扩展初始化其他bean onRefresh(); // Check for listener beans and register them. //在所有的bean中查找listener bean,然后 注册到广播器中 registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. //初始化剩余的非懒惰的bean,即初始化非延迟加载的bean finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. //发完成刷新过程,通知声明周期处理器刷新过程,同时发出ContextRefreshEvent通知别人 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }12、应用上下文刷新后置处理afterRefresh(context, applicationArguments); //当前方法的代码是空的,可以做一些自定义的后置处理操作 protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { }13、停止计时监控类:计时监听器停止,并统计一些任务执行信息stopWatch.stop(); public void stop() throws IllegalStateException { if (this.currentTaskName == null) { throw new IllegalStateException("Can't stop StopWatch: it's not running"); } long lastTime = System.nanoTime() - this.startTimeNanos; this.totalTimeNanos += lastTime; this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime); if (this.keepTaskList) { this.taskList.add(this.lastTaskInfo); } ++this.taskCount; this.currentTaskName = null; }14、输出日志记录执行主类名、时间信息if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); }15、发布应用上下文启动完成事件:触发所有SpringapplicationRunListener监听器的started事件方法listeners.started(context); void started(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.started(context); } }16、执行所有Runner执行器:执行所有applicationRunner和CommandLineRunner两种运行器callRunners(context, applicationArguments); private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet<>(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } }17、发布应用上下文就绪事件:触发所有springapplicationRunnListener将挺起的running事件方法listeners.running(context); void running(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.running(context); } }18、返回应用上下文return context;注意:整个springboot框架中获取factorys文件的方式统一如下:private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } ------------------------------------- private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; } ----------------------------- public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } ------------------------- private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<>(names.size()); for (String name : names) { try { //装载class文件到内存 Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); //通过反射创建实例 T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); } } return instances; }spring.factory文件中的类的作用:# PropertySource Loaders 属性文件加载器 org.springframework.boot.env.PropertySourceLoader=\ # properties文件加载器 org.springframework.boot.env.PropertiesPropertySourceLoader,\ # yaml文件加载器 org.springframework.boot.env.YamlPropertySourceLoader # Run Listeners 运行时的监听器 org.springframework.boot.SpringApplicationRunListener=\ # 程序运行过程中所有监听通知都是通过此类来进行回调 org.springframework.boot.context.event.EventPublishingRunListener # Error Reporters 错误报告器 org.springframework.boot.SpringBootExceptionReporter=\ org.springframework.boot.diagnostics.FailureAnalyzers # Application Context Initializers org.springframework.context.ApplicationContextInitializer=\ # 报告spring容器的一些常见的错误配置 org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ # 设置spring应用上下文的ID org.springframework.boot.context.ContextIdApplicationContextInitializer,\ # 使用环境属性context.initializer.classes指定初始化器进行初始化规则 org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\ # 将内置servlet容器实际使用的监听端口写入到environment环境属性中 org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer # Application Listeners org.springframework.context.ApplicationListener=\ # 应用上下文加载完成后对缓存做清除工作,响应事件ContextRefreshEvent org.springframework.boot.ClearCachesApplicationListener,\ # 监听双亲应用上下文的关闭事件并往自己的孩子应用上下文中传播,相关事件ParentContextAvailableEvent/ContextClosedEvent org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ # 如果系统文件编码和环境变量中指定的不同则终止应用启动。具体的方法是比较系统属性file.encoding和环境变量spring.mandatory-file-encoding是否相等(大小写不敏感)。 org.springframework.boot.context.FileEncodingApplicationListener,\ # 根据spring.output.ansi.enabled参数配置AnsiOutput org.springframework.boot.context.config.AnsiOutputApplicationListener,\ # EnvironmentPostProcessor,从常见的那些约定的位置读取配置文件,比如从以下目录读取#application.properties,application.yml等配置文件: # classpath: # file:. # classpath:config # file:./config/: # 也可以配置成从其他指定的位置读取配置文件 org.springframework.boot.context.config.ConfigFileApplicationListener,\ # 监听到事件后转发给环境变量context.listener.classes指定的那些事件监听器 org.springframework.boot.context.config.DelegatingApplicationListener,\ # 一个SmartApplicationListener,对环境就绪事件ApplicationEnvironmentPreparedEvent/应用失败事件ApplicationFailedEvent做出响应,往日志DEBUG级别输出TCCL(thread context class loader)的classpath。 org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ # 检测正在使用的日志系统,默认时logback,,此时日志系统还没有初始化 org.springframework.boot.context.logging.LoggingApplicationListener,\ # 使用一个可以和Spring Boot可执行jar包配合工作的版本替换liquibase ServiceLocator org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\ org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\ org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor # Failure Analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer # FailureAnalysisReporters org.springframework.boot.diagnostics.FailureAnalysisReporter=\ org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter # Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener # Application Listeners org.springframework.context.ApplicationListener=\ # 另外单独启动一个线程实例化并调用run方法,包括验证器、消息转换器等 org.springframework.boot.autoconfigure.BackgroundPreinitializer # Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener # Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\ org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveRestClientAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\ org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\ org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\ org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\ org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\ org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\ org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\ org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\ org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\ org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\ org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\ org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\ org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\ org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\ org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\ org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\ org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration # Failure analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\ org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\ org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\ org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\ org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer # Template availability providers org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
2022年04月26日
101 阅读
0 评论
1 点赞
2022-03-12
Springboot同一Server类方法调用事务解决方案
Springboot同一Server类方法调用事务解决方案1、引入springboot-aop start<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>主要是使用里面的动态代理 <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> <scope>compile</scope> </dependency>2、开启动态代理@EnableAspectJAutoProxy(exposeProxy = true) @EnableDiscoveryClient @SpringBootApplication public class FamilyBookingApplication { public static void main(String[] args) { SpringApplication.run(FamilyBookingApplication.class, args); } }@EnableAspectJAutoProxy(exposeProxy = true):开启aspectj动态代理功能。以后所有的动态代理都是aspectj对象暴露代理对象。3、本类互调用代理对象调用@Service("userService") public class UserServiceImpl implements userService { @Transactional public void a(){ UserService userService = (UserService)AopContext.currentProxy(); userService.b(); userService.c(); } @Transactional public void b(){ } @Transactional public void c(){ } }
2022年03月12日
117 阅读
0 评论
4 点赞
2022-03-10
Springboot拦截器配置
Springboot拦截器配置1、拦截器配置,主要实现HandlerInterceptor接口import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @description: 拦截器,配合FamilyWebConfig 配置使用 * @author: <a href="mailto:batis@foxmail.com">清风</a> * @date: 2022/3/10 22:36 * @version: 1.0 */ @Component public class LoginUserInterceptor implements HandlerInterceptor { public static ThreadLocal<Object> objUser = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginusr = request.getSession().getAttribute("LOGIN_USR"); if(loginusr != null){ objUser.set(loginusr); //登录了才能访问 return true; }else{ //没登录,跳转去登录 return false; } } } 由于拦截器需要配合web配置使用,因此创建web配置。2、web配置主要实现WebMvcConfigurer接口import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @description: WEB配置 * @author: <a href="mailto:batis@foxmail.com">清风</a> * @date: 2022/3/10 22:40 * @version: 1.0 */ public class FamilyWebConfig implements WebMvcConfigurer { @Autowired LoginUserInterceptor loginUserInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**"); } }
2022年03月10日
114 阅读
0 评论
2 点赞
2022-02-28
Shiro权限管理框架
首先知道Shiro架构有3个主要概念:Subject、SecurityManager和Realms。 Shiro基础架构图 : 基础架构说明:Subject: As we’ve mentioned in our Tutorial, the Subject is essentially a security specific ‘view’ of the the currently executing user. Whereas the word ‘User’ often implies a human being, a Subject can be a person, but it could also represent a 3rd-party service, daemon account, cron job, or anything similar - basically anything that is currently interacting with the software.Subject instances are all bound to (and require) a SecurityManager. When you interact with a Subject, those interactions translate to subject-specific interactions with the SecurityManager.(大概意思:Subject一词是一个安全术语,其基本意思是“当前的操作用户”。称之为“用户”并不准确,因为“用户”一词通常跟人相关。在安全领域,术语“Subject”可以是人,也可以是第三方进程、后台帐户(Daemon Account)、定时作业(Corn Job)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。在程序中你都能轻易的获得Subject,允许在任何需要的地方进行安全操作。每个Subject对象都必须与一个SecurityManager进行绑定,你访问Subject对象其实都是在与SecurityManager里的特定Subject进行交互。)SecurityManager: The SecurityManager is the heart of Shiro’s architecture and acts as a sort of ’umbrella’ object that coordinates its internal security components that together form an object graph. However, once the SecurityManager and its internal object graph is configured for an application, it is usually left alone and application developers spend almost all of their time with the Subject API.We will talk about the SecurityManager in detail later on, but it is important to realize that when you interact with a Subject, it is really the SecurityManager behind the scenes that does all the heavy lifting for any Subject security operation. This is reflected in the basic flow diagram above.(大概意思:Subject的“幕后”推手是SecurityManager。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。它是Shiro框架的核心,充当“保护伞”,引用了多个内部嵌套安全组件,它们形成了对象图。但是,一旦SecurityManager及其内部对象图配置好,它就会退居幕后,应用开发人员几乎把他们的所有时间都花在Subject API调用上。那么,如何设置SecurityManager呢?嗯,这要看应用的环境。例如,Web应用通常会在Web.xml中指定一个Shiro Servlet Filter,这会创建SecurityManager实例,如果你运行的是一个独立应用,你需要用其他配置方式,但有很多配置选项。一个应用几乎总是只有一个SecurityManager实例。它实际是应用的Singleton(尽管不必是一个静态Singleton)。跟Shiro里的几乎所有组件一样,SecurityManager的缺省实现是POJO,而且可用POJO兼容的任何配置机制进行配置 - 普通的Java代码、Spring XML、YAML、.properties和.ini文件等。基本来讲,能够实例化类和调用JavaBean兼容方法的任何配置形式都可使用。)Realms: Realms act as the ‘bridge’ or ‘connector’ between Shiro and your application’s security data. When it comes time to actually interact with security-related data like user accounts to perform authentication (login) and authorization (access control), Shiro looks up many of these things from one or more Realms configured for an application.In this sense a Realm is essentially a security-specific DAO: it encapsulates connection details for data sources and makes the associated data available to Shiro as needed. When configuring Shiro, you must specify at least one Realm to use for authentication and/or authorization. The SecurityManager may be configured with multiple Realms, but at least one is required.(大概意思:Shiro的第三个也是最后一个概念是Realm。Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当与像用户帐户这类安全相关数据进行交互,执行认证(登录)和授权(访问控制)时,Shiro会从应用配置的Realm中查找很多内容。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件 等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。象其他内部组件一样,由SecurityManager来管理如何使用Realms来获取安全的身份数据。)看了上面一大段介绍后肯定还不知道怎么回事,来个简单比喻吧: 比如:新到一家公司,网管给你一台开发机,一个账号密码(这对应账号密码),你此时不知道账号是属于什么角色,是超级管理员,还是普通账号(这里就是对应角色),如果是超级管理员那权限就比较打了,比如设置一张自己喜欢的桌面壁纸,如果是普通用户那就没有这个权限了,因为公司统一宣传公司形象,使用统一公司宣传桌面壁纸(这里对应权限也就是资源), 重点 ,网管给的账号密码是属于那个角色,拥有什么权限,不用我自己去判断,而是交给了电脑自己去判断,不用麻烦自己了。这里的账号密码也就类似上面的Subject,而电脑操作系统就类似SecurityManager,电脑操作系统怎么判断属于那个角色,有哪些权限,这个判断的逻辑就类似Realms。以后我们使用Shrio就主要就是SecurityManager中的Realms要我们 自己去实现自己的方法 ,比如这个账号有哪些对应的角色、有哪些权限。在用账号密码进行登录操作系统,这个过程就类似 认证 的过程,如果认证失败(账号或面错误登录失败)就返回提示信息登录失败,如果认证成功了,就把这个Subject(账号)对应的角色、权限一起返回给操作系统,登录成功后通过账号查找对应的角色、权限,并返回给操作系统的过程就类似 授权 了。最后我们就可以操作电脑, 判断 是超级管理还是普通用户了, 看看 有没有权限换壁纸了。到这里也许你大概明白了大体怎么回事,接下来我们就要看看Shiro的完整架构图,类似看看这个操作系统里面是怎么回事,有哪些东西,来实现认证、授权的。 Shiro核心架构图 : 核心架构说明:Subject (org.apache.shiro.subject.Subject)A security-specific ‘view’ of the entity (user, 3rd-party service, cron job, etc) currently interacting with the software.(当前与软件交互的实体(用户、第 3 方服务、cron 作业等)的特定于安全的“视图”。)SecurityManager (org.apache.shiro.mgt.SecurityManager)As mentioned above, the SecurityManager is the heart of Shiro’s architecture. It is mostly an ‘umbrella’ object that coordinates its managed components to ensure they work smoothly together. It also manages Shiro’s view of every application user, so it knows how to perform security operations per user.(如上所述,这SecurityManager是 Shiro 架构的核心。它主要是一个“伞”对象,用于协调其托管组件以确保它们一起顺利工作。它还管理 Shiro 对每个应用程序用户的视图,因此它知道如何为每个用户执行安全操作。)Authenticator (org.apache.shiro.authc.Authenticator)The Authenticator is the component that is responsible for executing and reacting to authentication (log-in) attempts by users. When a user tries to log-in, that logic is executed by the Authenticator. The Authenticator knows how to coordinate with one or more Realms that store relevant user/account information. The data obtained from these Realms is used to verify the user’s identity to guarantee the user really is who they say they are.(身份认证/登录,验证用户是不是拥有相应的身份,例如账号密码登陆)Authentication Strategy (org.apache.shiro.authc.pam.AuthenticationStrategy)If more than one Realm is configured, the AuthenticationStrategy will coordinate the Realms to determine the conditions under which an authentication attempt succeeds or fails (for example, if one realm succeeds but others fail, is the attempt successful? Must all realms succeed? Only the first?).(认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;)Authorizer (org.apache.shiro.authz.Authorizer)The Authorizer is the component responsible determining users’ access control in the application. It is the mechanism that ultimately says if a user is allowed to do something or not. Like the Authenticator, the Authorizer also knows how to coordinate with multiple back-end data sources to access role and permission information. The Authorizer uses this information to determine exactly if a user is allowed to perform a given action.(Authorizer是部件负责确定用户在该应用程序的访问控制。它是最终决定是否允许用户做某事的机制。和 一样Authenticator,Authorizer也知道如何协调多个后端数据源来访问角色和权限信息。在Authorizer使用该信息来准确确定是否允许用户执行特定的操作。)SessionManager (org.apache.shiro.session.mgt.SessionManager)The SessionManager knows how to create and manage user Session lifecycles to provide a robust Session experience for users in all environments. This is a unique feature in the world of security frameworks - Shiro has the ability to natively manage user Sessions in any environment, even if there is no Web/Servlet or EJB container available. By default, Shiro will use an existing session mechanism if available, (e.g. Servlet Container), but if there isn’t one, such as in a standalone application or non-web environment, it will use its built-in enterprise session management to offer the same programming experience. The SessionDAO exists to allow any datasource to be used to persist sessions.(SessionManager负责管理shiro自己封装的session的生命周期。)SessionDAO (org.apache.shiro.session.mgt.eis.SessionDAO)The SessionDAO performs Session persistence (CRUD) operations on behalf of the SessionManager. This allows any data store to be plugged in to the Session Management infrastructure.(SessionDAO执行Session持久代(CRUD)操作SessionManager。这允许将任何数据存储插入会话管理基础架构。)CacheManager (org.apache.shiro.cache.CacheManager)The CacheManager creates and manages Cache instance lifecycles used by other Shiro components. Because Shiro can access many back-end data sources for authentication, authorization and session management, caching has always been a first-class architectural feature in the framework to improve performance while using these data sources. Any of the modern open-source and/or enterprise caching products can be plugged in to Shiro to provide a fast and efficient user-experience.(CacheManager创建和管理Cache其他四郎组件使用实例的生命周期。由于 Shiro 可以访问许多后端数据源进行身份验证、授权和会话管理,因此缓存一直是框架中的一流架构特性,以在使用这些数据源时提高性能。任何现代开源和/或企业缓存产品都可以插入 Shiro,以提供快速高效的用户体验。)Cryptography (org.apache.shiro.crypto.*)Cryptography is a natural addition to an enterprise security framework. Shiro’s crypto package contains easy-to-use and understand representations of crytographic Ciphers, Hashes (aka digests) and different codec implementations. All of the classes in this package are carefully designed to be very easy to use and easy to understand. Anyone who has used Java’s native cryptography support knows it can be a challenging animal to tame. Shiro’s crypto APIs simplify the complicated Java mechanisms and make cryptography easy to use for normal mortal human beings.(Cryptography是企业安全框架的自然补充。Shiro 的crypto软件包包含易于使用和理解的密码学、哈希(又名摘要)和不同编解码器实现的表示。这个包中的所有类都经过精心设计,非常易于使用和理解。任何使用过 Java 本地加密支持的人都知道,驯服它可能是一种具有挑战性的动物。Shiro 的加密 API 简化了复杂的 Java 机制并使密码学易于普通人使用。)Realms (org.apache.shiro.realm.Realm)As mentioned above, Realms act as the ‘bridge’ or ‘connector’ between Shiro and your application’s security data. When it comes time to actually interact with security-related data like user accounts to perform authentication (login) and authorization (access control), Shiro looks up many of these things from one or more Realms configured for an application. You can configure as many Realms as you need (usually one per data source) and Shiro will coordinate with them as necessary for both authentication and authorization.(如上所述,Realms 充当 Shiro 和应用程序安全数据之间的“桥梁”或“连接器”。当需要与安全相关数据(如用户帐户)进行实际交互以执行身份验证(登录)和授权(访问控制)时,Shiro 从为应用程序配置的一个或多个 Realms 中查找其中的许多内容。您可以根据Realms需要配置任意数量(通常每个数据源一个),Shiro 将根据需要与他们协调进行身份验证和授权。)看了上面一大段肯定有头特了,到底是啥意思没搞明白,那直接来看个有史以来最简单的Shiro使用列子的代码吧:列子核心代码,至于导的是那个包啊,为什么我照着来的就不行呢,可以直接拉取我的代码看看,然后自己手动写一次,自己写一次完全不一样哦。** * @description: shiro最简单的学习,重点login()、checkRoles()里面源码最终都是通过realm来获取用户信息、角色、权限信息,以完成认证、授权 * @author: <a href="mailto:batis@foxmail.com">yanxizhu.com</a> * @date: 2021/6/25 22:24 * @version: 1.0 */ public class ShiroDemoT { SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm(); @Before public void addUser(){ simpleAccountRealm.addAccount("yanxi", "123456","admin","user"); } @Test public void shiroDt(){ //1、构建环境 DefaultSecurityManager securityManager = new DefaultSecurityManager(); securityManager.setRealm(simpleAccountRealm); //2、获取主体,提交认证请求 Subject subject = SecurityUtils.getSubject(); //认证 UsernamePasswordToken token= new UsernamePasswordToken("yanxi", "123456","admin"); subject.login(token); //是否认证成功 boolean authenticated = subject.isAuthenticated(); System.out.println("authenticated:"+authenticated); //角色校验 subject.checkRoles("admin","user"); //3、退出登录 subject.logout(); System.out.println("退出登录后-authenticated:"+authenticated); } }看完这段代码有没有豁然开朗,原来就这样的啊,上面的subject就是我们的主体,就是用网管分配的账号密码,那我么是怎么认证、授权的呢?怎么用的呢?用就是subject.login(token)这个了。那认证、授权呢?认证授权就是securityManager里面的2个方法了,一个方法负责认证,一个负责授权,而我们自定义的认证、授权规则是怎么样的呢?也就是上面的securityManager.setRealm(simpleAccountRealm)中的simpleAccountRealm了,具体securityManager是怎么实现认证、授权的呢?那就可以看看认证subject.login(token)、授权subject.checkRoles("admin","user")这2方法内部源码怎么实现的了。源码后面大体看一下。完整流程应该是这样的: 1、构建securityManager环境2、获得主体subject,怎么获得的,就是Shiro提供的SecurityUtils工具类型就获得了。3、主体subject是怎么使用认证的,非常简单subject.login(token)这样就完了。4、通过securityManager提供的认证、授权方法完成后,会返回主体subject拥有的角色、权限,我们通过subject.checkRoles("admin","user")就可以验证主体subject是否认证有对应的角色了,当然还有个校验是有有对应权限的方法//资源权限,删除用户权限subject.checkPermission("user:update")。上面这个简单的Shiro就完了,以后开发中simpleAccountRealm这个Realm就要我们自己去实现了,以及后面的认证、授权、校验是否有这个角色、权限那可能就要复杂一点了。到这里你应该明白Shiro的简单使用了吧,那我们看看另2种Realm的实现方法,一种通过读ini文件、一种通过jdbcRealm读数据库来实现simpleAccountRealm.addAccount("yanxi", "123456","admin","user")的列子吧:方式一、通过读取ini文件,实现我们的Realm,代码如下:/** * @description: 通过inir(配置用户角色,权限)的方法验证用户角色、资源权限,前提是要先登录成功,也就是认证成功 * @author: <a href="mailto:batis@foxmail.com">清风</a> * @date: 2021/6/25 22:24 * @version: 1.0 */ public class ShiroDemoT2 { @Test public void shiroDt(){ IniRealm ini = new IniRealm("classpath:user.ini"); //1、构建环境 DefaultSecurityManager securityManager = new DefaultSecurityManager(); ThreadContext.bind(securityManager); securityManager.setRealm(ini); //2、获取主体,提交认证请求 Subject subject = SecurityUtils.getSubject(); //认证 UsernamePasswordToken token= new UsernamePasswordToken("yanxi", "123456","admin"); subject.login(token); //是否认证成功 boolean authenticated = subject.isAuthenticated(); System.out.println("authenticated:"+authenticated); //角色校验,是否是admin角色 subject.checkRoles("admin"); //资源权限,删除用户权限 subject.checkPermission("user:update"); } }说明 :user.ini文件是放在我们的resource资源文件夹里面的一个文件,后缀为.ini,文件内容如何:[users] yanxi=123456,admin [roles] admin=user:delete,user:update方式二、通过jdbcRealm读数据库来实现,代码如下:/** * @description: 通过jdbcRealm(配置用户角色,权限)的方法验证用户角色、资源权限,前提是要先登录成功,也就是认证成功 * @author: <a href="mailto:batis@foxmail.com">清风</a> * @date: 2021/6/25 22:24 * @version: 1.0 */ public class ShiroDemoT3 { DruidDataSource dataSource = new DruidDataSource(); { dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("root"); } @Test public void shiroDt(){ JdbcRealm jdbcRealm = new JdbcRealm(); jdbcRealm.setDataSource(dataSource); //1、构建环境 DefaultSecurityManager securityManager = new DefaultSecurityManager(); ThreadContext.bind(securityManager); securityManager.setRealm(jdbcRealm); //2、获取主体,提交认证请求 Subject subject = SecurityUtils.getSubject(); //认证 UsernamePasswordToken token= new UsernamePasswordToken("yanxi", "123456"); subject.login(token); //是否认证成功 boolean authenticated = subject.isAuthenticated(); System.out.println("authenticated:"+authenticated); //角色校验,是否是admin角色 subject.checkRoles("admin","user"); subject.checkRoles("user"); //资源权限,删除用户权限 subject.checkPermission("user:select"); } }说明:有人会说为什么没写sql查询就可以直接认证、授权了呢?因为jdbcRealm里面已经有这些的基本实现了,点开JdbcRealm源码,里面就可以看到如下代码了:public class JdbcRealm extends AuthorizingRealm { protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?"; protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?"; protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?"; protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?"; private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class); protected DataSource dataSource; protected String authenticationQuery = "select password from users where username = ?"; protected String userRolesQuery = "select role_name from user_roles where username = ?"; protected String permissionsQuery = "select permission from roles_permissions where role_name = ?";当然我们还可以自己写的sql实现,代码如下:/** * @description: 通过jdbcRealm(配置用户角色,权限)的方法验证用户角色、资源权限,前提是要先登录成功,也就是认证成功 * @author: <a href="mailto:batis@foxmail.com">清风</a> * @date: 2021/6/25 22:24 * @version: 1.0 */ public class ShiroDemoT3 { DruidDataSource dataSource = new DruidDataSource(); { dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("root"); } @Test public void shiroDt(){ JdbcRealm jdbcRealm = new JdbcRealm(); jdbcRealm.setDataSource(dataSource); //jdbcRealm不会默认查询资源权限,需要手动开启 jdbcRealm.setPermissionsLookupEnabled(true); // **这个地方就是自己写sql实现了** //jdbcRealm默认有查询语句,这里可以使用自己的sql查询 //用户sql String sql="select password from test_user where user_name=?"; jdbcRealm.setAuthenticationQuery(sql); //角色sql String sql2="select role_name from test_user_role where user_name=?"; jdbcRealm.setUserRolesQuery(sql2); //1、构建环境 DefaultSecurityManager securityManager = new DefaultSecurityManager(); ThreadContext.bind(securityManager); securityManager.setRealm(jdbcRealm); //2、获取主体,提交认证请求 Subject subject = SecurityUtils.getSubject(); //认证 // UsernamePasswordToken token= new UsernamePasswordToken("yanxi", "123456"); UsernamePasswordToken token= new UsernamePasswordToken("xixi", "654321"); subject.login(token); //是否认证成功 boolean authenticated = subject.isAuthenticated(); System.out.println("authenticated:"+authenticated); // //角色校验,是否是admin角色 // subject.checkRoles("admin","user"); subject.checkRoles("user"); // // //资源权限,删除用户权限 // subject.checkPermission("user:select"); } }权限的自定义同理,就不在写SQL建表了,贴一下数据库对应的表及结构,非常简单的表结构:默认的jdbcRealm使用的表: 自定义sql使用的表: 整体表结构: 好了,这个你应该明白了Shiro的整体过程了,那么我们就要写一个自定义的Realm了,一般开发中也是这样用法,代码如下:/** * @description: 自定义Realm完整认证 * @author: <a href="mailto:batis@foxmail.com">清风</a> * @date: 2021/6/26 0:16 * @version: 1.0 */ public class CustomRealmTest { @Test public void shiroDt(){ //自定义的Realm CustomRealm customRealm = new CustomRealm(); //1、构建环境 DefaultSecurityManager securityManager = new DefaultSecurityManager(); ThreadContext.bind(securityManager); securityManager.setRealm(customRealm); //设置加密信息 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); //设置加密的名称 matcher.setHashAlgorithmName("md5"); //设置加密的次数 matcher.setHashIterations(1); //设置加密信息到Realm中 customRealm.setCredentialsMatcher(matcher); //2、获取主体,提交认证请求 Subject subject = SecurityUtils.getSubject(); //认证 UsernamePasswordToken token= new UsernamePasswordToken("xixi", "654321"); subject.login(token); //是否认证成功 boolean authenticated = subject.isAuthenticated(); System.out.println("authenticated:"+authenticated); //角色校验,是否是admin角色 subject.checkRoles("admin","user"); //资源权限,删除用户权限 subject.checkPermissions("user:delete","user.add"); } }说明: //设置加密信息 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); //设置加密的名称 matcher.setHashAlgorithmName("md5"); //设置加密的次数 matcher.setHashIterations(1); //设置加密信息到Realm中 customRealm.setCredentialsMatcher(matcher);这里就是我们设置加密的地方,具体实现在自定义Realm类中。注意这里哦customRealm.setCredentialsMatcher(matcher);不然自定义Realm类中加密算法没有用到哦。这才是自定义Realm类的地方哦:/** * @description: 自定义Realm,为什么要继承AuthorizingRealm? * 因为默认的jdbcRealm就是重写AuthorizingRealm里面这2个方法完成认证和授权的 * @author: <a href="mailto:batis@foxmail.com">清风</a> * @date: 2021/6/26 0:02 * @version: 1.0 */ public class CustomRealm extends AuthorizingRealm { Map<String,String> userMap = new HashMap<String,String>(16); { //通过md5加密后,就不能使用铭文了,使用md5加密后的c33367701511b4f6020ec61ded352059 //md5加密方法,最下面的main临时方法 // userMap.put("xixi", "654321"); //md5加密后的密码 // userMap.put("xixi", "c33367701511b4f6020ec61ded352059"); //使用md5加盐后的密码 userMap.put("xixi", "87b441673e676a402074c349ef983c07"); super.setName("customRealm"); } /** * 自定义授权 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String userName = (String)principalCollection.getPrimaryPrincipal(); //通过用户名查询角色信息 Set<String> roles = getRolesByName(userName); //通过用户名查询权限信息 Set<String> permissions = getPermissionsByName(userName); //返回角色、资源信息 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.setRoles(roles); simpleAuthorizationInfo.setStringPermissions(permissions); return simpleAuthorizationInfo; } /** * 自定义的认证 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //通过authenticationToken获取用户名 String userName = (String)authenticationToken.getPrincipal(); //获取密码 String password = getPasswordByName(userName); if(null == password){ return null; } //第一个用户名、密码、realm名称 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName,password,"customRealm"); //使用md5加盐时,需要返回盐的名称,不适用盐时,不用加 //注意:如果盐名称和使用md5加盐时用的盐名称不一样,就不同通过认证哦,因为加密用的盐名称不一样肯定得到的加密密码不一样 simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("yanxi")); return simpleAuthenticationInfo; } /** * 模拟数据库查询操作 * @param userName * @return */ public String getPasswordByName(String userName){ //这里通过mysql的基本查询获取密码,这里模拟不操作数据库了 return userMap.get(userName); } /** * 模拟从数据库查询角色信息 * @param userName * @return */ public Set<String> getRolesByName(String userName){ Set<String> roles = new HashSet<String>(16); roles.add("admin"); roles.add("user"); return roles; } /** * 模拟从数据库查询权限(资源)信息 * @param userName * @return */ public Set<String> getPermissionsByName(String userName){ Set<String> permissions = new HashSet<String>(16); permissions.add("user:delete"); permissions.add("user.add"); return permissions; } //md5计算出加密后的密码 // public static void main(String[] args) { // Md5Hash md5Hash = new Md5Hash("654321"); // System.out.println(md5Hash.toString()); // } //使用md5加盐后的计算结果,这种更安全.这里假设盐名称为yanxi public static void main(String[] args) { Md5Hash md5Hash = new Md5Hash("654321","yanxi"); System.out.println(md5Hash.toString()); } } 这里有人肯能会问为什么要extends AuthorizingRealm,那你看看之前最简单的例子里面是不是也是继承这个类啊,最主要原因是securityManager使用login进行认证、授权的具体实现都是在这里面,而我们实现自己的Realm,那就必须继承AuthorizingRealm,并重写里面的doGetAuthenticationInfo()认证方法、doGetAuthorizationInfo()授权方法。那我把关键代码在贴一下吧。 自定义认证、授权关键代码:/** * 自定义授权 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String userName = (String)principalCollection.getPrimaryPrincipal(); //通过用户名查询角色信息 Set<String> roles = getRolesByName(userName); //通过用户名查询权限信息 Set<String> permissions = getPermissionsByName(userName); //返回角色、资源信息 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.setRoles(roles); simpleAuthorizationInfo.setStringPermissions(permissions); return simpleAuthorizationInfo; } /** * 自定义的认证 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //通过authenticationToken获取用户名 String userName = (String)authenticationToken.getPrincipal(); //获取密码 String password = getPasswordByName(userName); if(null == password){ return null; } //第一个用户名、密码、realm名称 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName,password,"customRealm"); //使用md5加盐时,需要返回盐的名称,不适用盐时,不用加 //注意:如果盐名称和使用md5加盐时用的盐名称不一样,就不同通过认证哦,因为加密用的盐名称不一样肯定得到的加密密码不一样 simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("yanxi")); return simpleAuthenticationInfo; }注意:自己看上面的自定义Realm类CustomRealm,里面的md5加密、加盐,以及模拟数据库的操作。 代码注意点一: super.setName("customRealm");这里设置的自定义Realm名称要和认证中返回的名称一致。//第一个用户名、密码、realm名称SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName,password,"customRealm"); 代码注意点二: Md5Hash md5Hash = new Md5Hash("654321","yanxi");加密时使用的盐名称要和我们认证返回信息中设置的盐名称一致。simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("yanxi"));此时我们数据库存在就是md5+加盐后加密的密码87b441673e676a402074c349ef983c07,这里只是模拟了数据库的操作,实际中肯定是使用dao层的数据库操作完成。到这里你应该知道Shiro怎么使用了,以及Shiro里面怎么实现加密,怎么自定义Realm了。最后我们看一下subject.login(token)、subject.checkRoles("admin","user")、subject.checkPermissions("user:delete","user.add")内部源码怎么用到我们自定义Realm类CustomRealm的:login认证源码:public void login(AuthenticationToken token) throws AuthenticationException { this.clearRunAsIdentitiesInternal(); Subject subject = this.securityManager.login(this, token); String host = null; PrincipalCollection principals; if (subject instanceof DelegatingSubject) { DelegatingSubject delegating = (DelegatingSubject)subject; principals = delegating.principals; host = delegating.host; } else { principals = subject.getPrincipals(); } if (principals != null && !principals.isEmpty()) { this.principals = principals; this.authenticated = true; if (token instanceof HostAuthenticationToken) { host = ((HostAuthenticationToken)token).getHost(); } if (host != null) { this.host = host; } Session session = subject.getSession(false); if (session != null) { this.session = this.decorate(session); } else { this.session = null; } } else { String msg = "Principals returned from securityManager.login( token ) returned a null or empty value. This value must be non null and populated with one or more elements."; throw new IllegalStateException(msg); } }重点看:Subject subject = this.securityManager.login(this, token);点击login跟进去看代码如下: public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info; try { info = this.authenticate(token); } catch (AuthenticationException var7) { AuthenticationException ae = var7; try { this.onFailedLogin(token, ae, subject); } catch (Exception var6) { if (log.isInfoEnabled()) { log.info("onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.", var6); } } throw var7; } Subject loggedIn = this.createSubject(token, info, subject); this.onSuccessfulLogin(token, info, loggedIn); return loggedIn; }重点看:info = this.authenticate(token);authenticate方法继续跟进代码: public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException { return this.authenticator.authenticate(token); }继续跟进this.authenticator.authenticate(token);代码:public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException { if (token == null) { throw new IllegalArgumentException("Method argument (authentication token) cannot be null."); } else { log.trace("Authentication attempt received for token [{}]", token); AuthenticationInfo info; try { info = this.doAuthenticate(token); if (info == null) { String msg = "No account information found for authentication token [" + token + "] by this " + "Authenticator instance. Please check that it is configured correctly."; throw new AuthenticationException(msg); } } catch (Throwable var8) { AuthenticationException ae = null; if (var8 instanceof AuthenticationException) { ae = (AuthenticationException)var8; } if (ae == null) { String msg = "Authentication failed for token submission [" + token + "]. Possible unexpected " + "error? (Typical or expected login exceptions should extend from AuthenticationException)."; ae = new AuthenticationException(msg, var8); if (log.isWarnEnabled()) { log.warn(msg, var8); } } try { this.notifyFailure(token, ae); } catch (Throwable var7) { if (log.isWarnEnabled()) { String msg = "Unable to send notification for failed authentication attempt - listener error?. Please check your AuthenticationListener implementation(s). Logging sending exception and propagating original AuthenticationException instead..."; log.warn(msg, var7); } } throw ae; } log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info); this.notifySuccess(token, info); return info; } }重点看:info = this.doAuthenticate(token);继续跟进代码如下: protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { this.assertRealmsConfigured(); Collection<Realm> realms = this.getRealms(); return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken); }重点看这,这里最终还是使用realms来获取我们的角色、资源数据。Collection<Realm> realms = this.getRealms(); return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);接下来看看subject.checkRoles("admin","user")的源码:subject.checkRoles("admin","user");继续跟进代码: public void checkRoles(String... roleIdentifiers) throws AuthorizationException { this.assertAuthzCheckPossible(); this.securityManager.checkRoles(this.getPrincipals(), roleIdentifiers); }this.securityManager.checkRoles(this.getPrincipals(), roleIdentifiers);继续跟进代码:public void checkRoles(PrincipalCollection principals, String... roles) throws AuthorizationException { this.assertRealmsConfigured(); if (roles != null) { String[] var3 = roles; int var4 = roles.length; for(int var5 = 0; var5 < var4; ++var5) { String role = var3[var5]; this.checkRole(principals, role); } } }this.checkRole(principals, role);继续跟进代码: public void checkRole(PrincipalCollection principals, String role) throws AuthorizationException { this.assertRealmsConfigured(); if (!this.hasRole(principals, role)) { throw new UnauthorizedException("Subject does not have role [" + role + "]"); } }this.hasRole(principals, role)继续跟进代码:public boolean hasRole(PrincipalCollection principals, String roleIdentifier) { this.assertRealmsConfigured(); Iterator var3 = this.getRealms().iterator(); Realm realm; do { if (!var3.hasNext()) { return false; } realm = (Realm)var3.next(); } while(!(realm instanceof Authorizer) || !((Authorizer)realm).hasRole(principals, roleIdentifier)); return true; }这里也可以看出是通过realm来实现授权的。所以我们只需要自定义realm中的认证、授权方法即可。有些理解不对的还请谅解,谢谢。最后附上Github代码地址:https://github.com/willxwu/shiro-learn
2022年02月28日
143 阅读
0 评论
4 点赞
1
2