Struts2是由Apache社区的Struts1和Opensysmphony的Webwork整合而来,Struts负责对http请求的处理,Webwork负责业务逻辑处理。
1.导入13个jar包(在Struts2自带的demo中可以找到最少所需包)
2.需要在web.xml文件中配置StrutsPrepareAndExecuteFilter(ctrl+shif+t查找)
3.创建一个struts.xml文件,它的位置是可以放置在src下。(如果没有联网,需要配置约束,DTD映射类型为URL)
大致流程:
(1) 客户端(Client)向Action发用一个请求(Request)
(2) Container通过web.xml映射请求,并获得控制器(Controller)的名字
(3) 容器(Container)调用控制器(StrutsPrepareAndExecuteFilter)。在Struts2.1以前调用FilterDispatcher,Struts2.1以后调用StrutsPrepareAndExecuteFilter
(4) 控制器(Controller)通过ActionMapper获得Action的信息
(5) 控制器(Controller)调用ActionProxy
(6) ActionProxy读取struts.xml文件获取action和interceptor stack的信息。
(7) ActionProxy把request请求传递给ActionInvocation
(8) ActionInvocation根据配置文件加载相关的所有Interceptor拦截器,通过代理模式调用Action和interceptor
(9) 根据action的配置信息,产生result
(10) Result信息返回给ActionInvocation根据struts.xml中配置的result,决定进行下一步输出
(11) 产生一个HttpServletResponse响应
(12) 产生的响应行为发送给客服端。
详细流程:
1.启动服务器(tomcat),StrutsPrepareAndExecuteFilter的init方法执行将会自动加载配置文件
default.properties 在 struts2-core-2.3.7.jar 中 org.apache.struts2包里面(常量的默认值)
struts-default.xml 在 struts2-core-2.3.7.jar(Bean、273行18个默认拦截器、结果类型 )
struts-plugin.xml 在struts-Xxx-2.3.7.jar(在插件包中存在 ,配置插件信息 ) struts-config-browser-plugin-2.3.7.jar里面有
struts.xml 该文件是web应用默认的struts配置文件 (实际开发中,通常写struts.xml )
struts.properties 该文件是Struts的默认配置文件 (配置常量 )
web.xml 该文件是Web应用的配置文件 (配置常量 )
2.请求经过一系列的过滤器(Filter),StrutsPrepareAndExecuteFilter被调用doFilter方法被执行
HttpServletRequestrequest = (HttpServletRequest) req;
HttpServletResponse response =(HttpServletResponse) res;
prepare.setEncodingAndLocale(request,response);//设置编码格式:默认utf-8,根据
default.properties
prepare.createActionContext(request, response);//创建actionContext
prepare.assignDispatcherToThread();//整个的过程都在当前线程中执行
request =prepare.wrapRequest(request);//对request进行了包装
ActionMapping mapping = prepare.findActionMappin(request, response,true);//询问 ActionMapper来决定这个请求是否需要调用某个Action。如果是.action: dispatcher.serviceAction()
3.createAcitonContext(){
//利用容器创建值栈
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
//构建值栈的结构,把request,session,application等封装成一些map,再把这些map放入到 大map中
stack.getContext().putAll(dispatcher.createContextMap(request, response,null, servletContext));
//把大map的引用指向了ActionContext中的Map<String,Object> context;
ctx = new ActionContext(stack.getContext());
//把整个actionContext放入到了当前线程中,因为actionContext中有valueStack,所以 valueStack也在当前线程中,
//这样就保证了数据的安全性并且在一个线程范围内可以共享数据
ActionContext.setContext(ctx);
}
说明:
1、创建actionContext对象
2、创建ValueStack(实现类OnglValueStack)
3、把整个的actionContext放入到了ThreadLocal中
4.
serviceAction(){
ValueStack stack = (ValueStack)request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
/**
* 因为在struts2容器中有太多的参数request,response,valueStack,session,application,paramters
所以struts2容器对当前的请求中用到所有的数据封装在了ActionContext中的map中
*/
extraContext.put(ActionContext.VALUE_STACK,valueStackFactory.createValueStack(stack));
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
//创建actionProxy
ActionProxy proxy =config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace,name, method, extraContext, true, false);
proxy.execute()方法
prepare.cleanupRequest(request);//把struts2过程中的数据全部清空了
}
5.在createActionProxy中执行了如下的内容:
重点:
执行了DefaultActionInvocation中的init方法
createAction(contextMap)调用ObjectFactory中buildAction 创建了action
stack.push(action); 把action放入到了对象的栈顶
contextMap.put("action", action);把action放入到map中
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();
获取所有的拦截器,并且返回了迭代器的形式
6.DefaultActionInvocation中的invoke方法
1、按照顺序的方式执行所有的拦截器
2、执行action中的方法
3、执行结果集
4、按照倒序的方式执行拦截器
invoke(){
//调用了拦截器
if (interceptors.hasNext()) {
final InterceptorMappinginterceptor = (InterceptorMapping) interceptors.next();
String interceptorMsg ="interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {resultCode =interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally{UtilTimerStack.pop(interceptorMsg);
}
}
resultCode = invokeActionOnly();//执行action
//在执行结果集之前执行PreResultListener
if (preResultListeners != null) {
for (ObjectpreResultListener : preResultListeners) {
PreResultListenerlistener = (PreResultListener) preResultListener;
String _profileKey ="preResultListener: ";
try {UtilTimerStack.push(_profileKey);
listener.beforeResult(this, resultCode);
}
finally{UtilTimerStack.pop(_profileKey);
}
}
}
executeResult();//执行结果集
}
总结:
在执行过滤器filter的过程中
1、创建ActionContext
1、创建actionContext
2、创建值栈
3、把actionContext加入到当前线程中
2、创建actionProxy
执行DefaultActionInvocation的init方法
1、创建action
2、把action放入到栈顶
3、创建所有的拦截器,并且产生拦截器的迭代器
3、执行Proxy的execute方法
proxy.execute()---->invocaction.invoke方法
1、执行所有的拦截器
2、执行当前请求的action
3、执行RreResultListener
4、执行结果集
4、清除数据 在finally中,这样即便出错,也能清除
优点:是一款功能强大,成熟而稳定的开源框架,它良好的实现了业务隔离,数据封装等功能,并具有可扩展性。
缺点:a:核心流程写死,无法动态添加内容
b:所有的错误处理采用try方法,使框架显得臃肿
c:与Ajax结合使用过,没有内置错误码,任何错误需要手动设置
第一个加载的是default.properties文件
位置:struts2-core.jar包 org.apache.struts2包下
作用:主要是声明了struts2框架的常量
示例:struts.i18n.encoding=UTF-8(设置post请求字符编码)
struts.multipart.maxSize=2097152(约束文件上传大小)
struts.devMode = false
第二个加载的是一批配置文件
struts-default.xml
位置:struts2-core.jar
作用:声明了interceptor result bean
示例:result 的type种类
dispatcher :Action 转发给 JSP
chain :Action转发到另一个Action (同一次请求)
redirect : Action重定向到 JSP
redirectAction :Action重定向到另一个Action
stream:下载用的(文件上传和下载时再议)
plainText:以纯文本的形式展现内容
struts-plugin.xml
位置:在strtus2的插件包中
作用:主要用于插件的配置声明
struts.xml
位置:在我们自己的工程中
作用:用于我们自己工程使用struts2框架的配置
第三个加载的是自定义的struts.properties
位置:都是在自己工程的src下
作用:定制常量
第四自定义配置提供
第五加载的是web.xml配置文件
主要是加载struts2框架在web.xml文件中的相关配置.
第六 bean相关配置
package配置
1.name属性 作用:定义一个包的名称,它必须唯一。
2.namespace属性 作用:主要是与action标签的name属性联合使用来确定一个action 的访问路径
3.extends属性 作用:指定继承自哪个包。一般值是struts-default
strtus-default包是在struts-default.xml文件中声明的。
4.abstruct属性 它代表当前包是一个抽象的,主要是用于被继承
action配置
1.name属性 作用:主要是与package的namespace联合使用来确定一个action的访问路 径
2.class属性 作用:用于指示当前的action类
3.method属性 作用:用于指示当前的action类中的哪个方法执行(默认execute,并默认返回success)
result配置
它主要是用于指示结果视图
1.name属性 作用是与action类的method方法的返回值进行匹配,来确定跳转路径
2.type属性 作用是用于指定跳转方式(默认dispatcher)
常量配置
struts.xml (推举)
格式 : <constant name="struts.devMode" value="true"/>
struts.properties(要求)
格式 : struts.devMode = true
web.xml <init-param>
<param-name>struts.devMode</param-name>
<param-value>true</param-value>
</init-param>
*<constantname="struts.i18n.encoding" value="UTF-8"/> 可以帮助我们解决post请求乱码问题
<constant name="struts.action.extension"value="action"/> 指定访问struts2框架路径的扩展名>
<constant name="struts.devMode" value="true">
配置这项后,它会提供更加详细报错信息,以及在struts.xml文件修改后不在需要重启服务器
拦截器配置
在要拦截的action所在的package里面声明拦截器
在要拦截的action里面使用拦截器
如果使用自定义的拦截器,默认的拦截器不会执行的,手动使用默认的拦截器
<package name="demo2" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="pre"class="cn.itcast.demo2.Invacaton"></interceptor>
</interceptors>
<action name="demo2" class="cn.itcast.demo2.DemoAction" >
result>/index.jsp</result>
<result name="login">/demo100.jsp</result>
<interceptor-ref name="pre"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
</action>
/package>
*指定拦截方法
我们可以使用Interceptor接口的一个实现类来完成操作
在配置时,就可以指定哪些方法拦截,哪些方法不拦截
*过滤器和拦截器的区别
过滤器:在目标资源之前进行的操作
过滤所有的内容,比如action、servlet、jsp、html
拦截器:在目标资源之前进行的操作
不能拦截所有的内容,拦截action,不能拦截jsp,不能拦截html
拦截器和过滤器之间有很多相同之处,但是两者之间存在根本的差别。其主要区别为以下几点:
1)拦截器是基于JAVA反射机制的,而过滤器是基于函数回调的。
2)拦截器不依赖于Servlet容器,而过滤器依赖于Servlet容器
3)拦截器只能对Action请求起作用,而过滤器可以对几乎所有的请求起作用。
4)拦截器可以访问Action上下文、值栈里的对象,而过滤器不能
5)在Action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
对指定的方法校验
1格式:Action类名-validation.xml 针对动作类中的指定方法进行校验,使用@SkipValidation注解
2格式 : Action类名-ActionName(<action>元素name属性)-validation.xml
<validators>
<validator type=”requiredstring”>
<param name=”fileName”>password</param>
<validator>
</validators>
引入标签库:<%@ taglib uri="/struts-tags" prefix="s"%>
常见标签
格式 作用
<s:propertyvalue=”ognl表达式”/> 获取数据并输入页面
<s:set scope=””var=”” value=””/> 令var=value存入scope中
<s:pushvalue=””/> 将value压入值栈
<s:if><s:elseif><s:else> 等同if(){}elseif(){}else{}
<s:iteratorvar=”” value=””......> 遍历集合或数组、域对象
<s:表单标签> 与HTML标签友好交互
<s:actionerror><s:filederror> 获取错误信息
<s:debug> 查看值栈信息
*addFieldError、addActionError有何区别?
都是com.opensymphony.xwork2.ActionSupport类下的方法.
addActionError (String anErrorMessage)
添加一个Action级别的错误消息到Action
anErrorMessage:错误消息,被存放在List列表中
显示消息的标签是(如放在jsp页面中):
<s:actionerror/> 显示全部的 Action级别的错误消息,可以加CSS代码
addFieldError (String fieldName, String errorMessage)
给一个字段(属性) 添加错误消息
fieldName:字段(属性)名
errorMessage:错误消息,被存放在一个Map<key, value>中(其中key存放的是 fieldName,value存放的是errorMessage)。
显示消息的标签是(如放在jsp页面中):
<s:fielderror/> 显示全部的错误消息(用addFieldError方法添加的 )
1. 创建一个pojo类
Pojo(plani Ordinary java object)简单的java对象
Pojo类就是没有实现任何接口没有继承任何类
优点:无耦合
缺点:所有的功能都要自己完成
2. 创建一个类实现一个Action接口
com.opensymphony.xwork2.Action
在Action接口中定义了五个常量,一个execute方法
五个常量:它们是默认的五个结果视图<result name=””>:
ERROR : 错误视图
INPUT: 它是struts2框架中interceptor中发现问题后会访问的一个视图
LOGIN:它是一个登录视图,可以在权限操作中使用
NONE:它代表的是null,什么都不做(也不会做跳转操作)
SUCCESS:这是一个成功视图
优点:耦合度低
缺点:还是需要自己来完成功能
3.创建一个类继承ActionSupport类(推举)
com.opensymphony.xwork2.ActionSupport
ActionSupport类也实现了Action接口。
主要有两种方式:
1.属性驱动
a.直接在action类中提供与请求参数匹配属性,提供get/set方法
b.在action类中创始一个javaBean,对其提供get/set ,在请求时页面上要进行修改, 例如 user.username user.password ,要使用ognl表达式
以上两种方式的优缺点:
第一种比较简单,在实际操作我们需要将action的属性在赋值给模型(javaBean) 去操作
第二种:不需要在直接将值给javaBean过程,因为直接将数据封装到了javaBean 中。它要求在页面上必须使用ognl表达式,就存在页面不通用问题。
2.模型驱动(推举)
步骤:
1.让Action类要实现一个指定接口ModelDriven
2.实例化模型对象(就是要new出来javaBean)
3.重写getModel方法将实例化的模型返回。
对于模型驱动它与属性驱动对比,在实际开发中使用比较多,模型驱动缺点,它只能对 一个模型数据进行封装。
ServletActionContext获取(推举)
采用注入方式
Struts2框架在运行时,请求会被StrutsPrepareAndExecuteFilter拦截,会根据请求,去 strtus.xml文件中查找到匹配的action,在action执行前,会走一些interceptor
默认执行的拦截器是struts-default.xml文件中定义的。
在默认执行的拦截器中有一个
查看一下ServletConfigInterceptor源代码
以下是部分源代码
ServletRequestAware, 实现这个接口可以获取HttpServletRequest
ServletResponseAware ,实现这个接口可以获取HttpServletResponse
ServletContextAware 实现这个接口可以获取ServletContext
依赖于request请求的一种数据容器,每次request请求都会创建一个action实例,而每一个action实例都对应一个valueStack(它保存在request中,是request的一个属性),valueStack存储action对象以及其他对象内容。
值栈由两部分组成
root:compoundRoot其实就是一个ArrayList.
context :OgnlContext其实就是一个Map。context中有root的引用,request、 session、application、 attr、 parameters等对象引用
* 操作值栈默认指 操作 root 元素
获取值栈
值的存取
ActionContext.getContext().getValueStack().push(Object) ,
将对象注入到栈顶对象栈 JSP页面通过 javabean 属性名获取
ActionContext.getContext().getValueStack().set(k ,v ) ,
把一个对象变成map存入到对象栈中 JSP页面 “key” 获得
ActionContext.getContext().put(k ,v )
把一个数据直接放入到map栈中 , JSP页面“#key”
*如何在JSP页面获取数据
访问root中数据 不需要#
访问 其它对象数据 加 #
通过下标获取root中对象
<s:property value="[0].top"/> //取值栈顶对象
直接在root中查找对象属性 (自上而下自动查找)
valueStack:<s:propertyvalue="username"/>
(1)对象保存到值栈
<s:propertyvalue="user.username"/>
(2) 集合保存到值栈
<s:propertyvalue="list[0].username"/>
在OgnlContext中获取数据
request:<s:propertyvalue="#request.username"/>
session:<s:propertyvalue="#session.username"/>
application:<s:propertyvalue="#application.username"/>
attr:<s:propertyvalue="#attr.username"/>
parameters:<s:propertyvalue="#parameters.cid"/>
struts内置存储过程
每次请求,访问action,这个对象会存储到valueStack中。
在DefaultActionInvocation的init方法内
在ModelDrivernInterceptor中
以上代码会将模型对象存储到valueStack中。
EL表达式取值
由于在struts2中重写了request的getAttribute方法,如果在request域对象中获取不到值就会在valueStack中查找。
#号:它是从非root中获取数据
%用于强制是否要解析ognl表达式
$它主要是从配置文件中来获取valueStack中数据
单文件上传
浏览器端注意事项:
表单提交方式method=post
表单中必须有一个<input type=”file” name=””>组件
表单中必须设置enctype=”multipart/form-data”
服务器端
导入 Commons-fileupoad.jar包
Struts2框架本身支持文件上传
Struts2框架使用一个fileupload的interceptor来完成文件上传,而我们要使用它
在action中我们可以提供类似以下的操作就能完成文件上传操作(必须有对应set方法)
在execute方法中将文件copy就可以完成文件上传。
多文件上传
页面
对象
方法
文件上传时出现了问题
现在的问题是我们的action中没有设置input视图
在页面上可以通过
在default.properties中
struts.multipart.maxSize=2097152 (2m)
它是描述文件上传时允许的最大值
只需要在struts.xml文件中
我们还可以查看FileuploadInterceptor
用HttpServletRuqest处理
在浏览器端
在服务器端
使用自带的json插件
首先要导入插件包:struts2-json-pligin-2.3.24.jar
然后在struts.xml中进行如下操作
1. 将我们自己配置文件中的<packageextends=”json-default”>.
2. Action的返回视图<result name=”” type=”json”>
浏览器端
服务器端
注意:要想使用struts2的注解,我们必须单独在导入一个jar包。
@Namespace来代替<package namespace=””>
@ParentPackage来代替<packageextends=””>
@Action来描述关于<action>配置
value属性<action name=””>
使用@Action的results来描述关于结果类型的配置<result>
<result name=”” type=””>
@Action(value=””,results={@Result(name=””,type=””,location=””)})
@Actions
作用:可以通过多个映射来访问同一个action
@Results 类似于全局的结果视图
@InterceptorRef 它是用于处理拦截器的
问题:我们在action类中定义了注解,strtus2框架怎样识别它们?
原因:我们必须查看插件包中的配置
是在action,actions,struts,struts2这样的包下扫描注解
文章浏览阅读3.2w次,点赞16次,收藏90次。对于这个问题我也是从网上找了很久,终于解决了这个问题。首先遇到这个问题,应该确认虚拟机能不能正常的上网,就需要ping 网关,如果能ping通说明能正常上网,不过首先要用命令route -n来查看自己的网关,如下图:第一行就是默认网关。现在用命令ping 192.168.1.1来看一下结果:然后可以看一下电脑上面百度的ip是多少可以在linux里面ping 这个IP,结果如下:..._linux桥接ping不通baidu
文章浏览阅读512次。小妹在这里已经卡了2-3天了,研究了很多人的文章,除了低版本api 17有成功外,其他的不是channel null 就是没反应 (channel null已解决)拜托各位大大,帮小妹一下,以下是我的程式跟 gradle, 我在这里卡好久又没有人可问(哭)![image](/img/bVcL0Qo)public class MainActivity extends AppCompatActivit..._android 权限申请弹窗 横屏
文章浏览阅读1.4k次,点赞4次,收藏6次。valid padding(有效填充):完全不使用填充。half/same padding(半填充/相同填充):保证输入和输出的feature map尺寸相同。full padding(全填充):在卷积操作过程中,每个像素在每个方向上被访问的次数相同。arbitrary padding(任意填充):人为设定填充。..._cnn “相同填充”(same padding)
文章浏览阅读790次,点赞29次,收藏28次。手绘了下图所示的kafka知识大纲流程图(xmind文件不能上传,导出图片展现),但都可提供源文件给每位爱学习的朋友一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长![外链图片转存中…(img-Qpoc4gOu-1712656009273)][外链图片转存中…(img-bSWbNeGN-1712656009274)]
文章浏览阅读469次。Date对象取得年份有getYear和getFullYear两种方法经 测试var d=new Date;alert(d.getYear())在IE中返回 2009,在Firefox中会返回109。经查询手册,getYear在Firefox下返回的是距1900年1月1日的年份,这是一个过时而不被推荐的方法。而alert(d.getFullYear())在IE和FF中都会返回2009。因此,无论何时都应使用getFullYear来替代getYear方法。例如:2016年用 getFullYea_getyear和getfullyear
文章浏览阅读182次。Unix传奇(上篇) 陈皓 了解过去,我们才能知其然,更知所以然。总结过去,我们才会知道我们明天该如何去规划,该如何去走。在时间的滚轮中,许许多的东西就像流星一样一闪而逝,而有些东西却能经受着时间的考验散发着经久的魅力,让人津津乐道,流传至今。要知道明天怎么去选择,怎么去做,不是盲目地跟从今天各种各样琳琅满目前沿技术,而应该是去 —— 认认真真地了解和回顾历史。 Unix是目前还在存活的操作系_unix传奇pdf
文章浏览阅读308次。哈希算法:将字符串映射为数字形式,十分巧妙,一般运用为进制数,进制据前人经验,一般为131,1331时重复率很低,由于字符串的数字和会很大,所以一般为了方便,一般定义为unsigned long long,爆掉时,即为对 2^64 取模,可以对于任意子序列的值进行映射为数字进而进行判断入门题目链接:AC代码:#include<bits/stdc++.h>using na..._ac算法 哈希
文章浏览阅读952次,点赞13次,收藏27次。由于觉得Qt的编辑界面比较丑,所以想用vs2022的编辑器写Qt加MySQL的项目。_在vs中 如何装qt5sqlmysql模块
文章浏览阅读1k次。选择题题目:下面的哪个调研内容属于经济环境调研?()题目:()的目的就是加强与客户的沟通,它是是网络媒体也是网络营销的最重要特性。题目:4Ps策略中4P是指产品、价格、顾客和促销。题目:网络市场调研是目前最为先进的市场调研手段,没有任何的缺点或不足之处。题目:市场定位的基本参数有题目:市场需求调研可以掌握()等信息。题目:在开展企业网站建设时应做好以下哪几个工作。()题目:对企业网站首页的优化中,一定要注意下面哪几个方面的优化。()题目:()的主要作用是增进顾客关系,提供顾客服务,提升企业_画中画广告之所以能有较高的点击率,主要由于它具有以下特点
文章浏览阅读1k次,点赞2次,收藏5次。以爬取CSDN为例子:第一步:导入请求库第二步:打开请求网址第三步:打印源码import urllib.requestresponse=urllib.request.urlopen("https://www.csdn.net/?spm=1011.2124.3001.5359")print(response.read().decode('utf-8'))结果大概就是这个样子:好的,继续,看看打印的是什么类型的:import urllib.requestresponse=urllib.r_urlopen the read operation timed out
文章浏览阅读304次。修正sina.com/sina.cn邮箱获取不到联系人,并精简修改了其他邮箱代码,以下就是升级版版本的介绍:完整版本,整合了包括读取邮箱通讯录、MSN好友列表的的功能,目前读取邮箱通讯录支持如下邮箱:gmail(Y)、hotmail(Y)、 live(Y)、tom(Y)、yahoo(Y)(有点慢)、 sina(Y)、163(Y)、126(Y)、yeah(Y)、sohu(Y) 读取后可以发送邮件(完..._通讯录 应用读取 邮件 的相关
文章浏览阅读213次。云计算及虚拟化教程学习云计算、虚拟化和计算机网络的基本概念。此视频教程共2.0小时,中英双语字幕,画质清晰无水印,源码附件全课程英文名:Cloud Computing and Virtualization An Introduction百度网盘地址:https://pan.baidu.com/s/1lrak60XOGEqMOI6lXYf6TQ?pwd=ns0j课程介绍:https://www.aihorizon.cn/72云计算:概念、定义、云类型和服务部署模型。虚拟化的概念使用 Type-2 Hyperv_云计算与虚拟化技术 教改