-------------------------------概述------------------------------------------
从以下两点来认识Struts2中的自定义类型转换器,你能得到什么?你可以掌握它的整个生命周期。
一、自定义数据类型转换器初始化
二、使用自定义数据类型转换器(Struts2框架来完成),
只需要在xwork-conversion.properties配置自定义的数据类型转换器,
和实现自定义类型转换器(通过继承DefaultTypeConverter,也可以继承其子类StrutsTypeConverter)即可。
---------------------------------来龙去脉-------------------------------------------
一、自定义数据类型转换器初始化,具体过程
1.服务器启动,加载和管理web.xml中的Filter:
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
2.调用StrutsPrepareAndExecuteFilter类的初始化方法:init(FilterConfig) void
//初始化调度器Dispatcher(笔者叫法,感觉很贴切)
Dispatcher dispatcher = init.initDispatcher(config);
3.调度器Dispatcher中进行加载和解析配置文件,初始化IOC容器等的初始化工作,主要初始化方法如下:
Container container = init_PreloadConfiguration();
接着,加载容器:reloadContainer(List<ContainerProvider>)
4.设置引导容器,比如 XWorkConverter(数据类型转换器,此文章关注点)
// Set the bootstrap container for the purposes of factory creation
Container bootstrap = createBootstrapContainer();
5.DefaultConfiguration中使用构造器模式构建引导程序,源码片段如下:
protected Container createBootstrapContainer() {
ContainerBuilder builder = new ContainerBuilder();
builder.factory(ObjectFactory.class, Scope.SINGLETON);
builder.factory(ReflectionProvider.class, OgnlReflectionProvider.class, Scope.SINGLETON);
builder.factory(ValueStackFactory.class, OgnlValueStackFactory.class, Scope.SINGLETON);
//类型转换器
builder.factory(XWorkConverter.class, Scope.SINGLETON);
builder.factory(XWorkBasicConverter.class, Scope.SINGLETON);
builder.factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON);
builder.factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON);
builder.factory(PropertyAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON);
builder.factory(OgnlUtil.class, Scope.SINGLETON);
builder.constant("devMode", "false");
//使用struts2 IOC容器创建引导程序
return builder.create(true);
}
6.Struts2 IOC容器通过Constructor反射(Struts2 IOC容器创建对象的其中一种方式)来调用XWorkConverter类的构造方法(使用struts2 IOC容器创建引导程序时)
protected XWorkConverter() {
}
7.创建XWorkConverter实例后,进行依赖注入(@Inject注解 + IOC容器再配合一系列操作来完成)
ContainerBuilder类中的方法: create(boolean) Container
public Container create(boolean loadSingletons) {
ensureNotCreated();
created = true;
//初始化Struts2 IOC容器中的对象工厂factories
final ContainerImpl container = new ContainerImpl(
new HashMap<Key<?>, InternalFactory<?>>(factories));
if (loadSingletons) {//单例
container.callInContext(new ContainerImpl.ContextualCallable<Void>() {
public Void call(InternalContext context) {
for (InternalFactory<?> factory : singletonFactories) {
//创建XWorkConverter等对象
factory.create(context);
}
return null;
}
});
}
//依赖注入
container.injectStatics(staticInjections);
return container;
}
8.依赖注入时,调用XWorkConvert类中的setObjectFactory方法,
此方法作用就是设置对象工厂和加载类路径下的xwork-conversion.properties和另一个properties文件。
xwork-conversion.properties这个配置文件中配置自定义的类型转换器,比如:java.util.Date=org.yl.util.DateConverterUtil
特别提醒:java.util.Date=org.yl.util.DateConverterUtil后面不要留有空格,否则报错误。
源码片段如下:
@Inject
public void setObjectFactory(ObjectFactory factory) {
//设置对象工厂,这个东西干嘛的?创建对象的。
//(Strut2中(1)基层对象创建由IOC容器来创建,比如创建ObjectFactory。
//(2)业务层对象创建通常是ObjectFactory,
//比如:Action, interceptor,Result还有这里的自定义类型转换器等)
this.objectFactory = factory;
// note: this file is deprecated
loadConversionProperties("xwork-default-conversion.properties");
//加载类路径下的xwork-conversion.properties文件
loadConversionProperties("xwork-conversion.properties");
}
9.把配置文件中的自定义转换器放到Map容器中,key为"java.util.Date",value为:org.yl.util.DateConverterUtil
//比如笔者的xwork-conversion.properties中的配置:java.util.Date=org.yl.util.DateConverterUtil
//为什么把它放到Map容器中?
//存到Map中的目的是便于后面使用org.yl.util.DateConverterUtil时,get()提取出来使用。(是不是有点费话,我也觉得!哈哈。)
protected HashMap<String, TypeConverter> defaultMappings = new HashMap<String, TypeConverter>();
方法:loadConversionProperties(String, boolean) void 片段,如下:
try {
//创建自定义类型转换器,org.yl.util.DateConverterUtil
TypeConverter _typeConverter = createTypeConverter((String) entry.getValue());
if (LOG.isDebugEnabled()) {
LOG.debug("\t" + key + ":" + entry.getValue() + " [treated as TypeConverter " + _typeConverter + "]");
}
//把org.yl.util.DateConverterUtil放入Map中
//key为xwork-conversion.properties配置文件中的"java.util.Date"
//value为xwork-conversion.properties配置文件中的org.yl.util.DateConverterUtil实例对象
defaultMappings.put(key, _typeConverter);
} catch (Exception e) {
LOG.error("Conversion registration error", e);
}
休息一小会,接下来看看,Struts2框架是如何使用开发人员自定义的数据类型转换器的。马上就OK了,继续往下看,有不错的收获喔!
二、使用自定义数据类型转换器
1。前端页面(假设含有表单数据)向服务器发送一个请求,访问服务器中的一个项目(比如叫:ylstruts2,这个项目MVC框架使用的是Struts2)
2。web.xml中配置了"映射配置",拦截所有的请求(包括静态页面)
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.当前端发送一个请求时,Struts2框架的StrutsPrepareAndExecuteFilter会调用以下方法:
doFilter(ServletRequest, ServletResponse, FilterChain) : void
此方法做了许多工作,这里大家只需关注与Struts2自定义数据类型转换器相关的工作。
包装请求: request = prepare.wrapRequest(request); 通过request获取前端表单中的数据(ajax类型这里不讨论:struts2中有四种方式处理ajax请求)
4.执行Action,前面一些准备工作,如:Mapping,ActionProxy,获取Action的拦截器等不讨论
execute.executeAction(request, response, mapping);
4.1 执行Action前要经过Action的拦截器,工作在DefaultActionInvocation类的invoke()方法中进行,源码片段如下:
这个invoke方法其实使用的是一个责任链设计模式来实现AOP。
if (interceptors.hasNext()) {
final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
resultCode = invokeActionOnly();
}
4.2 其中一个重要的拦截器ParametersInterceptor,从ActionContext中获取前端请求的参数(前面第3项中包装request请求时,把前端参数放置到了ActionContext中)
final Map<String, Object> parameters = retrieveParameters(ac);
4.3.接着ParametersInterceptor拦截器,把前端参数封装到后端Action中对应的属性中。这个过程,涉及Ognl,OgnlValueStack和XWorkConverter等。
这个封装过程可以说是由强大的Ognl来完成的,其中有数据类型转换(此文章所关注的),关于数据类型转换使用到的是Struts2框架中的转换器。
(题外话,Struts2框架对Ognl中的数据类型转换器,做了两件事:
1。原原本本的Copy了Ognl原来的数据类型转功能,然后给类改了一下名称。
2。对Ognl数据类型转换器进行了扩展,比如文本类型转换成Date,这个功能在原生的Ognl框架中是没有的。)
5.数据类型转换,Ognl + OgnlValueStack + XWorkConverter 组合进行。
ParametersInterceptor类的setParameters(Object, ValueStack, Map<String, Object>)方法进行了以下业务逻辑处理:
//5.1 把前端参数设置到Action中对应的属性中.它是怎么把前端数据设置到后端的Action属性中的?
//它又是如何知道前端的弱类型数据要转换成java后端强类型数据的那一种(是int,Long,Float,Date还是Object)
//我用四个字来解决这两个问题:“控制反转”,具体的思路分析不在此细述。
for (Map.Entry<String, Object> entry : acceptableParameters.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
try {
//OgnlValueStack设值
newStack.setValue(name, value);
} catch (RuntimeException e) {
}
}
//5.2 借助Ognl设值
ognlUtil.setValue(expr, context, root, value);
Ognl.setValue(compile(name), context, root, value);
//5.3 Ognl框架中使用AST抽象语法树,得到ASTChain(如果使用的是user.name这种形式的表达式时)
//5.4 取得访问器和数据类型转换器,比如ObjectAccessor访问器,使用对象中对应的的setXXX方法,比如:setValidateDate。
在给java后端的Action属性赋值前,先进行类型转换。比如前端参数"1986/05/16",后端用java.util.Date validateDate来接收前端参数。
那么就得先给弱类型"1986/05/16"转换成java强类型数据Date,此时需要获取"数据类型转换器"
用到OgnlRuntime类中的getConvertedType(OgnlContext, Object, Member, String, Object, Class) 方法。
typeConverter.convertValue(context, target, member, propertyName, value, toType);
typeConverter为XWorkConverter,调用是的convertValue方法,进行以下逻辑处理:
//5.4.1 when converting from a string, use the toClass’s converter
tc = lookup(toClass);
//5.4.2 Looks for a TypeConverter in the default mappings.
public TypeConverter lookup(Class clazz) {
return lookup(clazz.getName());
}
//5.4.3 紧接着调用以下方法,这个熟悉吧,就是第一条初始化时的那个Map,从这个Map中找"java.util.Date"为key的数据类型转换器
//protected HashMap<String, TypeConverter> defaultMappings = new HashMap<String, TypeConverter>();
public TypeConverter lookup(String className) {
if (unknownMappings.contains(className) && !defaultMappings.containsKey(className)) {
return null;
}
//取得HashMap中key为:"java.util.Date", value为:org.yl.util.DateConverterUtil实例对象
//(这里有必要说明一下:取到的org.yl.util.DateConverterUtil实例对象与初始化时put进去的org.yl.util.DateConverterUtil实例对象
//不是同一个实例对象(我怎么知道的?因为它俩的内存地址不一样),我推测从初始化到使用这个过程有一定的时间段,
//不可能一直存在内存中,可能被GC回收了。所以借助ObjectFactory重新创建了DateConverterUtil实例对象,导致存与取两个操作时的。
//org.yl.util.DateConverterUtil实例对象不是同一个对象。有兴趣的同学可以去验证下。)
//这里的className为"java.util.Date"
TypeConverter result = defaultMappings.get(className);
…………
}
//5.4.4 取得自定义类型转换器后,调用自定义数据类型转的器的convertValue方法,
//若没有获取到自定义的数据类型转换器,使用默认的数据类型转换器defaultTypeConverter,
//获取到了自定义数据类型转换器后,进行如下操作:
tc.convertValue(context, target, member, property, value, toClass);
tc:为自定义数据类型转换器对象的实例:org.yl.util.DateConverterUtil@16bd01d。
笔者的自定义数据类型转换器org.yl.util.DateConverterUtil中的代码片段,如下:
/* 支持转换的日期格式 */
private static final DateFormat[] SUPPORT_DATE_FORMATS = {
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),//这个要放在new SimpleDateFormat("yyyy-MM-dd")的前面,防止截掉HH:mm:ss。
new SimpleDateFormat("yyyy-MM-dd"),
new SimpleDateFormat("yyyy/MM/dd HH/mm/ss"),
new SimpleDateFormat("yyyy/MM/dd"),
};
public Object convertValue(Map<String,Object> context, Object value, Class toType) {
if (toType == Date.class) {
//参数是数组类型,是使用了request.getParameterMap()后得到的数据key-value(数组类型),
//使用ParameterInterceptor + ongl把value传递至此,进行数据类型转换的。
String[] params = (String[]) value;
//数组中元素是一个所以取params[0]
String dateString = params[0];
for (DateFormat format : SUPPORT_DATE_FORMATS) {
try {
//遍历支持的日期格式把文本类型转换java.util.Date
return format.parse(dateString);
} catch (ParseException e) {
continue;
}
}
return null;
}
6.数据类型转换完成后,把转换后的数据设置到后端的Action对应的属性中(具体怎么设置的还得细说Ognl,不是本文章所关注的重点)。OK完成。
写了这么多,不知道对大家有没有点帮助。希望对大家有点帮助,觉得有用点个赞呗,纯手工写作。