Nutzboot简介:
NutzBoot为一个基于Nutz框架的微服务方案,以下简称NB!其余略...
NB文档简介:http://www.nutzam.com/core/bo...
简介:
首先关于Nutz以及NB,shiro等介绍以及微服务的概念在此略过,本文中介绍的方法基于Nutz框架,但实现思路和核心类应该可以在其他框架中通用.
一.首先阐述一下研究这个东西的出发点.
一开始在NB中也是使用的shiro.ini配置文件完成对shiro的配置的,但是在实际开发过程中发现,在配置微服务的过程中,shiro文件需要配置或复制多次,这里插一嘴我们的实现方式,我们服务的实现方式为,所有需要独立部署的服务器都会实现同一个基础服务器运行库,来完成通用配置等.
在配置多个服务器的时候发现不同服务器之间如果只使用一个shiro.ini配置文件会出现,[urls]配置下的url重复的情况,即例如前后台的/user/login接口,既是在NB的配置中配置不同服务器上下文,也无法避免同一个shiro配置文件出现此问题
为了解决这个问题其实也想过将不同服务的配置文件分离(当然这在NB结构中是可以实现的),但是出现shiro.ini中[main]等配置被复制多次的情况,不美观也不利于修改.
二.解决思路
为了解决这个问题其实一开始想到的是通过NB中shiro-stater中已有的 shiro.ini.urls 配置解决(这个配置允许在其他一个文件中手写shiro urls 配置列表)
后来发现这个配置无法和 shiro.ini配置文件一起使用(这个配置大概是给使用默认配置的同学使用的)
而已有的shiro大部分配置都在shiro.ini配置文件中,不太可能写成代码形式,只能另寻他法
最后经过几经寻找,最终确定能解决这个问题的方法是(也修改了很多次,这里说了最后两种)
1.复制shiro-starter源码,利用其中已有的重写shiro的 EnvironmentLoaderListener的类,以和之前部分相同的逻辑初始化Shiro.
2.修改上述Listener在传入Environment处做修改(此处省略了starter获取配置文件路径并传入的部分,以及配置非空部分),传入一个重写的Environment,这个重写的类中大部分实现逻辑是由 shiro处理源码IniWebEnvironment类复制过来的,用来完成 shiro.ini文件的配置加载.
3.修改重写类代码,使其可以获取到NB starter的配置(略,用来传入路径或urls),并修改源码中获取Filter的方法createFilterChainResolver()
在其创建Filter实例之前插入代码
ini.load(String);
源码:(此处检查了是否有 urls和filter配置)
// only create a resolver if the 'filters' or 'urls' sections are defined:
Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS);
Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
if (!CollectionUtils.isEmpty(urls) || !CollectionUtils.isEmpty(filters)) {
// either the urls section or the filters section was defined. Go ahead and create the resolver:
IniFilterChainResolverFactory factory = new IniFilterChainResolverFactory(ini, this.objects);
resolver = factory.getInstance();
}
修改后的代码(此处需要注意我们逻辑中没有 filter配置,这里就没判断)
//urls为符合 ini语法的字符串 例如 /user/login = anon
String iniUrls = "[urls]\r\n" + urls;
ini.load(iniUrls);
// 此代码前,在此处ini会被加载成类
// ini对象在此方法之前就存在,this.object则由父类中负责
IniFilterChainResolverFactory factory = new IniFilterChainResolverFactory(ini, this.objects);
resolver = factory.getInstance();
这里需要注意在调用 ini.load后原文件中的 [urls] 配置会被覆盖,而当只有[urls] 但并没有实际内容时并不会被覆盖(没试过空行,请注意)
4.在完成这段代码后发现代码非常难看,shiro urls被配置到了java文件中,为了避免这个问题使用了NB-start直接带的配置方式以及已经存在的 shiro.ini.urls属性,将配置文件中的 urls传入代码中
所以在NB中就成了这样
//获取配置文件中urls
String urls = conf.get(ShiroEnvStarter.PROP_INIT_URLS, null);
//urls为符合 ini语法的字符串 例如 /user/login = anon
String iniUrls = "[urls]\r\n" + urls;
ini.load(iniUrls);
// 此代码前,在此处ini会被加载成类
// ini对象在此方法之前就存在,this.object则由父类中负责
IniFilterChainResolverFactory factory = new IniFilterChainResolverFactory(ini, this.objects);
resolver = factory.getInstance();
在NB的application.properties配置文件中写法为
shiro.ini.urls:
/unathenticated = anon
/user/login = anon
#end
这里还要手动@并感谢下 @wendal @nutzcn
之前并不知道application.properties中多行的写法
至此,整个在代码中覆盖 shiro urls 配置的思路基本就完成了,只剩下一些类似于非空判断参数获取的代码.在此实现下即可实现项目中Shiro main配置为统一文件,而urls配置为专用单独文件
之后要介绍的就是优化部分以及,完整代码
三.优化(完全基于NB)
1.在使用中发现虽然现在 urls已经可以独立文件配置,但多行之间并不可以写注解(在NB层就不会读取完整),并且和 shiro.ini中语法并不完全一样.
最后解决办法为:在项目中添加另一个.ini的配置文件,并在项目中读取ini文件并使用 ini.load加载
2.在添加了ini配置文件后发现最好可以有一部分urls可以通用配置,另一部分可以独立配置.
最后解决方式为:添加两个shiro-url.ini配置文件,并在代码中读取并拼接,在库服务中添加,一个ini文件,并在每个独立的服务中添加完全同路径和名称的文件,使用其打包时互相覆盖的特性,达到独立配置的目的,而另一个不进行覆盖达到通用配置的目的.
而在NB中通过在 starter源码中添加新的配置达到url可以配置并传入的目的.
@PropDoc(value = "urls过滤清单ini文件1路径")
public static final String PROP_INIT_URLS_PATH1 = "shiro.ini.urls.path1";
@PropDoc(value = "urls过滤清单ini文件2路径")
public static final String PROP_INIT_URLS_PATH2 = "shiro.ini.urls.path2";
这里附上读取并拼接两个 ini的代码
private boolean loadIniPath1ANd2(Ini ini) {
String urlsAll = "";
String urls1 = readConfig(ShiroEnvStarter.PROP_INIT_URLS_PATH1);
String urls2 = readConfig(ShiroEnvStarter.PROP_INIT_URLS_PATH2);
urlsAll = urls1 + urls2;
if (!urlsAll.equals("")) {
String iniUrls = "[urls]\r\n" + urlsAll;
log.info("shiro ini urls ---> \r\n" + iniUrls);
// Ini ini = new Ini();
// ini.load(iniUrls);
// Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
// Log.info(section.toString());
// 注意这个代码中这里load过一次了
ini.load(iniUrls);
// 此处验证了 ini并没有全被覆盖,只覆盖了 加载的配置文件部分
// 如果新加载的内容中 只有 [main] 标签不会被覆盖,但是如果 [main]标签下有内容 则会覆盖之前的配置
// for (Entry<String, Ini.Section> entry : ini.entrySet()) {
// jline.internal.Log.info(entry.getKey());
// for (Entry<String, String> entryStr : entry.getValue().entrySet()) {
// jline.internal.Log.info(entryStr);
// }
// }
return true;
}
return false;
}
private String readConfig(String confPath) {
String urls1 = "";
try {
String path = conf.get(confPath, "").trim();
log.info("path:" + path);
if (path != null && appContext.getResourceLoader().has(path)) {
InputStream is = ResourceUtils.getInputStreamForPath("classpath:" + path);
if (is != null) {
urls1 = readIniFile(is);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return urls1;
}
private String readIniFile(InputStream is) throws IOException {
// InputStreamReader reader =new InputStreamReader(new
// FileInputStream(file),"UTF-8");
InputStreamReader reader = new InputStreamReader(is);
BufferedReader br = new BufferedReader(reader);
StringBuffer sbf = new StringBuffer();
while (true) {
String str = br.readLine();
if (str != null) {
sbf.append(str).append("\r\n");
} else {
break;
}
}
br.close();
reader.close();
return sbf.toString();
}
当然代码中依旧有可以优化的地方,不过这里就先不写了,嘿嘿
不过至此已经达到能想到的最好的效果了,父子项目中 父项目配置 shiro main中验证和DB,Redis的大部分通用配置,以及所有通用的urls过滤,而子类可以添加新的urls配置,并且各个子项目中 shiro配置并不冲突.
至此整偏博客结束,如果其中有错误也请大家告诉我了~~
下面是源码部分,不看可以跳过了!~
四.源码部分
starter源码部分
不包括starter中未修改的类
ShiroEnvStarter.java
//添加常量
@PropDoc(value = "urls过滤清单ini文件1路径")
public static final String PROP_INIT_URLS_PATH1 = "shiro.ini.urls.path1";
@PropDoc(value = "urls过滤清单ini文件2路径")
public static final String PROP_INIT_URLS_PATH2 = "shiro.ini.urls.path2";
NbShiroEnvironmentLoaderListener.java
//修改
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import org.apache.shiro.ShiroException;
import org.apache.shiro.config.ConfigurationException;
import org.apache.shiro.util.ClassUtils;
import org.apache.shiro.util.UnknownClassException;
import org.apache.shiro.web.env.EnvironmentLoader;
import org.apache.shiro.web.env.EnvironmentLoaderListener;
import org.apache.shiro.web.env.IniWebEnvironment;
import org.nutz.boot.AppContext;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.lang.Lang;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jline.internal.Log;
public class NbShiroEnvironmentLoaderListener extends EnvironmentLoaderListener {
private static final Logger log = LoggerFactory.getLogger(NbShiroEnvironmentLoaderListener.class);
protected PropertiesProxy conf;
protected AppContext appContext;
@Override
public void contextInitialized(ServletContextEvent sce) {
PropertiesProxy conf = appContext.getConfigureLoader().get();
try {
boolean hasUrlsPath = hasIniUrlsPath(conf, appContext);
// 走原生API的shiro.ini文件吗?
String iniPath = conf.get("shiro.ini.path", "shiro.ini");
boolean hasIniPath = conf.has("shiro.ini.path") || appContext.getResourceLoader().has(iniPath);
if (hasIniPath || (hasIniPath && hasUrlsPath)) {
sce.getServletContext().setAttribute(EnvironmentLoader.CONFIG_LOCATIONS_PARAM, iniPath);
super.contextInitialized(sce);
return;
}
} catch (Exception e) {
throw Lang.wrapThrow(e);
}
// 没有配置文件 走nb(默认)配置
sce.getServletContext().setAttribute(ENVIRONMENT_CLASS_PARAM, NbResourceBasedWebEnvironment.class.getName());
super.contextInitialized(sce);
}
protected Class<?> determineWebEnvironmentClass(ServletContext servletContext) {
// nb 默认配置
String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM);
if (className != null) {
try {
return ClassUtils.forName(className);
} catch (UnknownClassException ex) {
throw new ConfigurationException("Failed to load custom WebEnvironment class [" + className + "]", ex);
}
} else {
try {
boolean hasUrlsPath = hasIniUrlsPath(conf, appContext);
// 如果有配置文件
String iniPath = conf.get("shiro.ini.path", "shiro.ini");
boolean hasIniPath = conf.has("shiro.ini.path") || appContext.getResourceLoader().has(iniPath);
if (hasIniPath && hasUrlsPath) {
return IniMixWebEnvironment.class;
} else if (hasIniPath) {
return IniWebEnvironment.class;
}
} catch (IOException e) {
throw new ShiroException(e);
}
return NbResourceBasedWebEnvironment.class;
}
}
private boolean hasIniUrlsPath(PropertiesProxy conf, AppContext appContext) throws IOException {
String iniUrlsPath1 = conf.get(ShiroEnvStarter.PROP_INIT_URLS_PATH1, null);
boolean hasIniUrlsPath1 = false;
if (iniUrlsPath1 != null) {
hasIniUrlsPath1 = (conf.has(ShiroEnvStarter.PROP_INIT_URLS_PATH1)
|| appContext.getResourceLoader().has(iniUrlsPath1));
}
String iniUrlsPath2 = conf.get(ShiroEnvStarter.PROP_INIT_URLS_PATH2, null);
boolean hasIniUrlsPath2 = false;
if (iniUrlsPath2 != null) {
hasIniUrlsPath2 = (conf.has(ShiroEnvStarter.PROP_INIT_URLS_PATH2)
|| appContext.getResourceLoader().has(iniUrlsPath2));
}
if (hasIniUrlsPath1 || hasIniUrlsPath2) {
return true;
}
return false;
}
}
基于 shiro原生实现的实现类
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Map;
import javax.servlet.ServletContext;
import org.apache.shiro.config.ConfigurationException;
import org.apache.shiro.config.Ini;
import org.apache.shiro.config.IniFactorySupport;
import org.apache.shiro.io.ResourceUtils;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.util.Destroyable;
import org.apache.shiro.util.Initializable;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.config.IniFilterChainResolverFactory;
import org.apache.shiro.web.config.WebIniSecurityManagerFactory;
import org.apache.shiro.web.env.IniWebEnvironment;
import org.apache.shiro.web.env.ResourceBasedWebEnvironment;
import org.apache.shiro.web.env.WebEnvironment;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.util.WebUtils;
import org.nutz.boot.AppContext;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.impl.PropertiesProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link WebEnvironment} implementation configured by an {@link Ini} instance
* or {@code Ini} resource locations.
*
* 代码主要实现部分由shiro IniWebEnvironment源码抄过来的 <br>
* 修改了 createFilterChainResolver 方法,让 ini又load了一个 urls 配置 <br>
* 目前从现象看 ini.load 似乎不会覆盖整个配置
*
* @since 1.2
*/
public class IniMixWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {
// *********非源码部分**********
protected AppContext appContext;
protected Ioc ioc;
protected PropertiesProxy conf;
// ****************************
public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
private static final Logger log = LoggerFactory.getLogger(IniWebEnvironment.class);
/**
* The Ini that configures this WebEnvironment instance.
*/
private Ini ini;
/**
* Initializes this instance by resolving any potential (explicit or
* resource-configured) {@link Ini} configuration and calling
* {@link #configure() configure} for actual instance configuration.
*/
public void init() {
// *********非源码部分**********
appContext = AppContext.getDefault();
ioc = appContext.getIoc();
conf = appContext.getConfigureLoader().get();
// ****************************
Ini ini = getIni();
String[] configLocations = getConfigLocations();
if (log.isWarnEnabled() && !CollectionUtils.isEmpty(ini) && configLocations != null
&& configLocations.length > 0) {
log.warn("Explicit INI instance has been provided, but configuration locations have also been "
+ "specified. The {} implementation does not currently support multiple Ini config, but this may "
+ "be supported in the future. Only the INI instance will be used for configuration.",
IniWebEnvironment.class.getName());
}
if (CollectionUtils.isEmpty(ini)) {
log.debug("Checking any specified config locations.");
ini = getSpecifiedIni(configLocations);
}
if (CollectionUtils.isEmpty(ini)) {
log.debug("No INI instance or config locations specified. Trying default config locations.");
ini = getDefaultIni();
}
if (CollectionUtils.isEmpty(ini)) {
String msg = "Shiro INI configuration was either not found or discovered to be empty/unconfigured.";
throw new ConfigurationException(msg);
}
setIni(ini);
configure();
}
protected void configure() {
this.objects.clear();
WebSecurityManager securityManager = createWebSecurityManager();
setWebSecurityManager(securityManager);
FilterChainResolver resolver = createFilterChainResolver();
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
protected Ini getSpecifiedIni(String[] configLocations) throws ConfigurationException {
Ini ini = null;
if (configLocations != null && configLocations.length > 0) {
if (configLocations.length > 1) {
log.warn(
"More than one Shiro .ini config location has been specified. Only the first will be "
+ "used for configuration as the {} implementation does not currently support multiple "
+ "files. This may be supported in the future however.",
IniWebEnvironment.class.getName());
}
// required, as it is user specified:
ini = createIni(configLocations[0], true);
}
return ini;
}
protected Ini getDefaultIni() {
Ini ini = null;
String[] configLocations = getDefaultConfigLocations();
if (configLocations != null) {
for (String location : configLocations) {
ini = createIni(location, false);
if (!CollectionUtils.isEmpty(ini)) {
log.debug("Discovered non-empty INI configuration at location '{}'. Using for configuration.",
location);
break;
}
}
}
return ini;
}
/**
* Creates an {@link Ini} instance reflecting the specified path, or
* {@code null} if the path does not exist and is not required.
* <p/>
* If the path is required and does not exist or is empty, a
* {@link ConfigurationException} will be thrown.
*
* @param configLocation the resource path to load into an {@code Ini} instance.
* @param required if the path must exist and be converted to a non-empty
* {@link Ini} instance.
* @return an {@link Ini} instance reflecting the specified path, or
* {@code null} if the path does not exist and is not required.
* @throws ConfigurationException if the path is required but results in a null
* or empty Ini instance.
*/
protected Ini createIni(String configLocation, boolean required) throws ConfigurationException {
Ini ini = null;
if (configLocation != null) {
ini = convertPathToIni(configLocation, required);
}
if (required && CollectionUtils.isEmpty(ini)) {
String msg = "Required configuration location '" + configLocation + "' does not exist or did not "
+ "contain any INI configuration.";
throw new ConfigurationException(msg);
}
return ini;
}
protected FilterChainResolver createFilterChainResolver() {
FilterChainResolver resolver = null;
Ini ini = getIni();
if (!CollectionUtils.isEmpty(ini)) {
// only create a resolver if the 'filters' or 'urls' sections are defined:
// Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS);
// Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
// *********非源码部分**********
// boolean loadUrls = loadIniUrls(ini);
boolean loadUrls = loadIniPath1ANd2(ini);
if (loadUrls) {
// ****************************
// if (!CollectionUtils.isEmpty(urls) || !CollectionUtils.isEmpty(filters)) {
// either the urls section or the filters section was defined. Go ahead and create the resolver:
IniFilterChainResolverFactory factory = new IniFilterChainResolverFactory(ini, this.objects);
resolver = factory.getInstance();
}
}
return resolver;
}
// *********非源码部分**********
private boolean loadIniPath1ANd2(Ini ini) {
String urlsAll = "";
String urls1 = readConfig(ShiroEnvStarter.PROP_INIT_URLS_PATH1);
String urls2 = readConfig(ShiroEnvStarter.PROP_INIT_URLS_PATH2);
urlsAll = urls1 + urls2;
if (!urlsAll.equals("")) {
String iniUrls = "[urls]\r\n" + urlsAll;
log.info("shiro ini urls ---> \r\n" + iniUrls);
// Ini ini = new Ini();
// ini.load(iniUrls);
// Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
// Log.info(section.toString());
ini.load(iniUrls);
// 此处验证了 ini并没有全被覆盖,只覆盖了 加载的配置文件部分
// 如果新加载的内容中 只有 [main] 标签不会被覆盖,但是如果 [main]标签下有内容 则会覆盖之前的配置
// for (Entry<String, Ini.Section> entry : ini.entrySet()) {
// jline.internal.Log.info(entry.getKey());
// for (Entry<String, String> entryStr : entry.getValue().entrySet()) {
// jline.internal.Log.info(entryStr);
// }
// }
return true;
}
return false;
}
private String readConfig(String confPath) {
String urls1 = "";
try {
String path = conf.get(confPath, "").trim();
log.info("path:" + path);
if (path != null && appContext.getResourceLoader().has(path)) {
InputStream is = ResourceUtils.getInputStreamForPath("classpath:" + path);
if (is != null) {
urls1 = readIniFile(is);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return urls1;
}
private String readIniFile(InputStream is) throws IOException {
// InputStreamReader reader =new InputStreamReader(new
// FileInputStream(file),"UTF-8");
InputStreamReader reader = new InputStreamReader(is);
BufferedReader br = new BufferedReader(reader);
StringBuffer sbf = new StringBuffer();
while (true) {
String str = br.readLine();
if (str != null) {
sbf.append(str).append("\r\n");
} else {
break;
}
}
br.close();
reader.close();
return sbf.toString();
}
private boolean loadIniUrls(Ini ini) {
// 使用了 原 start中定义的 shiro.ini.urls
String config = conf.get(ShiroEnvStarter.PROP_INIT_URLS, "").trim();
if (config != null && !config.equals("")) {
String iniUrls = "[urls]\r\n" + config;
log.info("shiro ini urls ---> \r\n" + iniUrls);
// Ini ini = new Ini();
// ini.load(iniUrls);
// Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
// Log.info(section.toString());
ini.load(iniUrls);
return true;
}
return false;
}
// ****************************
protected WebSecurityManager createWebSecurityManager() {
WebIniSecurityManagerFactory factory;
Ini ini = getIni();
if (CollectionUtils.isEmpty(ini)) {
factory = new WebIniSecurityManagerFactory();
} else {
factory = new WebIniSecurityManagerFactory(ini);
}
WebSecurityManager wsm = (WebSecurityManager) factory.getInstance();
// SHIRO-306 - get beans after they've been created (the call was before the
// factory.getInstance() call,
// which always returned null.
Map<String, ?> beans = factory.getBeans();
if (!CollectionUtils.isEmpty(beans)) {
this.objects.putAll(beans);
}
return wsm;
}
/**
* Returns an array with two elements, {@code /WEB-INF/shiro.ini} and
* {@code classpath:shiro.ini}.
*
* @return an array with two elements, {@code /WEB-INF/shiro.ini} and
* {@code classpath:shiro.ini}.
*/
protected String[] getDefaultConfigLocations() {
return new String[] { DEFAULT_WEB_INI_RESOURCE_PATH, IniFactorySupport.DEFAULT_INI_RESOURCE_PATH };
}
/**
* Converts the specified file path to an {@link Ini} instance.
* <p/>
* If the path does not have a resource prefix as defined by
* {@link org.apache.shiro.io.ResourceUtils#hasResourcePrefix(String)}, the path
* is expected to be resolvable by the {@code ServletContext} via
* {@link javax.servlet.ServletContext#getResourceAsStream(String)}.
*
* @param path the path of the INI resource to load into an INI instance.
* @param required if the specified path must exist
* @return an INI instance populated based on the given INI resource path.
*/
private Ini convertPathToIni(String path, boolean required) {
// TODO - this logic is ugly - it'd be ideal if we had a Resource API to
// polymorphically encaspulate this behavior
Ini ini = null;
if (StringUtils.hasText(path)) {
InputStream is = null;
// SHIRO-178: Check for servlet context resource and not only resource paths:
if (!ResourceUtils.hasResourcePrefix(path)) {
is = getServletContextResourceStream(path);
} else {
try {
is = ResourceUtils.getInputStreamForPath(path);
} catch (IOException e) {
if (required) {
throw new ConfigurationException(e);
} else {
if (log.isDebugEnabled()) {
log.debug("Unable to load optional path '" + path + "'.", e);
}
}
}
}
if (is != null) {
ini = new Ini();
ini.load(is);
} else {
if (required) {
throw new ConfigurationException("Unable to load resource path '" + path + "'");
}
}
}
return ini;
}
// TODO - this logic is ugly - it'd be ideal if we had a Resource API to
// polymorphically encaspulate this behavior
private InputStream getServletContextResourceStream(String path) {
InputStream is = null;
path = WebUtils.normalize(path);
ServletContext sc = getServletContext();
if (sc != null) {
is = sc.getResourceAsStream(path);
}
return is;
}
/**
* Returns the {@code Ini} instance reflecting this WebEnvironment's
* configuration.
*
* @return the {@code Ini} instance reflecting this WebEnvironment's
* configuration.
*/
public Ini getIni() {
return this.ini;
}
/**
* Allows for configuration via a direct {@link Ini} instance instead of via
* {@link #getConfigLocations() config locations}.
* <p/>
* If the specified instance is null or empty, the fallback/default
* resource-based configuration will be used.
*
* @param ini the ini instance to use for creation.
*/
public void setIni(Ini ini) {
this.ini = ini;
}
}
如果以后想到什么再补充吧
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。