Struts2
1. 表达式引擎(ognl)作用: 使用某些符合特定规则的字符串表达式来对Java中的对象进行读和写的操作.
2. Struts2在Web应用启动之初, 由入口程序(StrutsPrepareAndExecuteFilter.java)的init方法完成, 且只执行一次, 执行失败会导致整个Web应用启动失败.
3. 初始化主要完成框架元素的初始化和控制框架运行的必要条件. 为了更好的管理Struts2的内置对象, Struts2引入了"容器"的概念, 初始化阶段也就是围绕着这个"容器"展开. 另外还有一类配置元素PackageConfig也是Struts2初始化的主要内容之一.
4. Struts2的Http请求处理分两个阶段.
1) Http请求预处理. 在这个阶段, 程序执行的控制权在Struts2手中. 该阶段的特点是依赖于Web容器, 主要就是与容器打交道. 相关代码封装到了struts2-core-XXX.jar文件中.
2) Xwork执行业务逻辑. 在该阶段, 程序的控制权在Xwork手中. Struts2将Http请求中的参数封装成普通Java对象, 交由Xwork负责执行具体的业务逻辑. 该阶段不依赖于Web容器, 完全有Xwork框架驱动整个阶段的执行过程. 相关代码封装到了xwork-core-XXX.jar文件中.
5. 严格来说Struts2是由两个框架组成的: Struts2和Xwork. 从职责上来说, Xwork才是真正实现MVC的框架 将Web容器与MVC实现分离, 是Struts2区别于其他Web框架的最重要特性.
6. Struts2中的容器(要初始化的对象)
com.opensymphony.xwork2.inject.Container | 容器定义接口, 是Struts2内部进行对象管理的基础构建 |
com.opensymphony.xwork2.inject.ContainerImpl | 容器的实现类, 内部实现了Struts2进行对象生命周期管理和依赖注入的基本功能 |
com.opensymphony.xwork2.config.entities.PackageConfig | PackageConfig实体类, 其中定义了事件响应模型的完整数据结构 |
7. 初始化过程是由加载接口(Provider) 和 构造器(Builder) 配合完成, 相关元素:
com.opensymphony.xwork2.config.ConfigurationProvdider | 配置加载接口的统一接口. Struts2将初始化元素分为Container和PackageConfig两类, 这里使用了多重继承将两类配置加载接口进行统一 |
com.opensymphony.xwork2.config.ContainerProvider | Container的配置加载接口, 其实现类需要负责初始化容器中的所有对象 |
com.opensymphony.xwork2.config.PackageProvider | PackageConfig的配置加载接口, 其实现类需要负责上初始化用于处理事件请求的配置对象 |
com.opensymphony.xwork2.inject.ContainerBuilder | Container的构造器, 用于在初始化时构造容器 |
PackageConfig内部类 PackageConfigBuilder | PackageConfig的构造器, 用于在初始化时构造PackageConfig |
8. 初始化中的辅助元素
com.opensymphony.xwork2.config.ConfigurationManager |
配置行为操作代理类, 包含了所有ContainerProvider和PackageProvider的实现以及所有配置的结构化数据(Configuration) |
com.opensymphony.xwork2.config.Configuration |
Struts2配置数据的管理类, 作为运行时获取配置的基本接口. 承载所有配置的结构化数据和操作方法 |
9. Http请求预处理
org.apache.struts2.dispathcer.Dispatcher | Struts2的核心分发器, 是Struts2进行Http请求处理的实际场所 |
org.apache.struts2.dispathcer.ng.PrepareOperation | Struts2进行Http请求预处理的操作集合 |
org.apache.struts2.dispathcer.ng.ExecuteOperation | Struts2进行Http请求逻辑处理的操作集合 |
10. Xwork执行业务逻辑
com.opensymphony.xwork2.ActionProxy | Xwork生产线中的执行环境, 整个生产线的入口, 如一个口袋一样封装了所有执行细节 |
com.opensymphony.xwork2.ActionInvocation | Xwork生产线中的调度者, 负责调度整个生产线中的各个元素的执行次序 |
com.opensymphony.xwork2.interceptor.Interceptor | Xwork生产线中的工序序列, 可以丰富整个生产线的功能 |
com.opensymphony.xwork2.Action | Xwork生产线中的核心工序, 负责核心业务逻辑调用和实现 |
com.opensymphony.xwork2.ActionContext | Xwork生产线的辅助设备, 提供整个生产线工作运行所必需的数据环境 |
com.opensymphony.xwork2.util.ValueStack | Xwork数据环境中提供表达式运算的工具类, 也是Xwork中进行数据访问的基础 |
com.opensymphony.xwork2.Result | Xwork生产线中的末端设备, 负责生产线的生成结果 |
11. 配置文件, 所有的应用级别的配置文件都不是必须的(web.xml除外).
web.xml | /WEB-INF | 应用级 | Struts2的入口程序定义, 运行参数定义 |
struts-default.xml | /WEB-INF/lib/struts2-core.jar!struts-default.xml | 框架级 | Struts2默认的框架级的配置定义, 包含所有Struts2的基本构成元素 |
struts.xml | /WEB-INF/classes | 应用级 | Struts2默认的应用级的主配置文件, 包含所有应用级别对框架级别默认行为的覆盖定义 |
default.properties | /WEB-INF/struts2-core.jar!org.apache.struts2.default.properties | 框架级 | Struts2默认的框架级的运行参数配置 |
struts.properties | /WEB-INF/classes | 应用级 | Struts2默认的应用级运行参数配置, 包含所有应用级别对框架级别的运行参数的覆盖定义 |
struts-plugin.xml | 插件所在jar文件的根目录 | 应用级 | Struts2所支持的插件形式的配置文件, 文件结构与Struts.xml一致. 其定义作为struts.xml的扩展, 也可以覆盖框架级别的行为定义 |
12. Struts2使用ThreadLocal模式解决线程安全问题.(Servlet默认是单实例的)
- ThreadLocalMap变量属于线程的内部属性, 不同的线程拥有完全不同的ThreadLocalMap变量
- 线程中的ThreadLocalMap变量的值是在ThreadLocal对象进行set或get操作时创建的.
- 在创建ThreadLocalMap之前, 会首先检查当前线程中的ThreadLocalMap变量是否存在, 如果不存在则创建一个; 否则, 使用当前线程已创建的ThreadLocalMap.
- 使用当前线程的ThreadLocalMap的关键在于使用当前的ThreadLocal的实例作为key进行存储.
13. ThreadLocal最适合的使用场景: 在同一个线程的不同开发层次中共享数据.
14. 实现ThreadLocal模式的两个重要步骤:
- 建立一个类并在其中封装一个静态的ThreadLocal变量, 使其成为一个共享数据环境.
- 在类中实现访问静态ThreadLocal变量的静态方法(设值和取值).
1 public class Counter { 2 //新建一个静态的ThreadLocal变量, 并通过ge方法将其变为一个可访问对象 3 private static ThreadLocal<Integer> counterContext = new ThreadLocal<Integer>() { 4 protected synchronized Integer initialValue() { 5 return 10; 6 } 7 }; 8 9 public static Integer get() { 10 return counterContext.get(); 11 } 12 13 public static void set(Integer value) { 14 counterContext.set(value); 15 } 16 17 //封装业务逻辑 18 public static Integer getNextCounter() { 19 counterContext.set(counterContext.get() + 1); 20 return counterContext.get(); 21 } 22 } 23 24 public class ThreadTest extends Thread{ 25 public void run() { 26 for (int i = 0; i < 3; i++) { 27 System.out.println("Thread[" + Thread.currentThread().getName() + 28 "], counter = " + Counter.getNextCounter()); 29 } 30 } 31 32 public static void main(String[] args) { 33 ThreadTest t1 = new ThreadTest(); 34 ThreadTest t2 = new ThreadTest(); 35 ThreadTest t3 = new ThreadTest(); 36 37 t1.start(); 38 t2.start(); 39 t3.start(); 40 } 41 } 42 43 /* 44 输出: 45 Thread[Thread-2], counter = 11 46 Thread[Thread-2], counter = 12 47 Thread[Thread-2], counter = 13 48 Thread[Thread-1], counter = 11 49 Thread[Thread-1], counter = 12 50 Thread[Thread-1], counter = 13 51 Thread[Thread-0], counter = 11 52 Thread[Thread-0], counter = 12 53 Thread[Thread-0], counter = 13 54 */
15. Struts2引入容器(Container)来管理内部对象的生命周期和实现IoC
- 在程序的运行期, 对象实例的创建和应用机制
- 对象及其关联对象的依赖关系的处理机制
16. 容器接口 com.opensymphony.xwork2.inject.Container 包含的方法
- 获取对象实例: getInstance(), getInstanceNames()
- 处理对象依赖关系: inject()
- 处理对象的作用范围策略: setScopeStrategy(), removeScopeStrategy()
17. 容器中所管理的对象有三类:
- 在struts-default.xml等文件中 bean 节点中声明的框架内部对象
- 在 bean 节点中声明的自定义对象
- 在 constant 节点和 Properties 文件中声明的系统运行参数
18. 使用 @inject 注解将容器中管理的对象注入到任意实例中
19. 使用容器进行对象操作的要点
- 通过容器进行对象操作的基本前提是操作的主体能够获得全局的容器实例. 因此, 全局的容器实例应该在操作主体初始化时获取.
- 通过操作容器进行的对象操作都是运行时(Runtime)操作
- 通过操作容器所获取的对象都受到了容器托管.
- 通过操作容器进行的依赖注入操作, 可以针对任意对象进行, 该操作可以建立起任意对象与容器托管对象间的联系.
20.
1 @Inject 2 public void setContainer(Container container) { 3 this.container = container; 4 build(); 5 }
因为有了 @Inject 注解, 对象在初始化时会调用 setContainer(), 即使构造函数中没有调用.
21. bean 节点的 name, type 与 com.opensymphony.xwork2.inject.Key 中的name, type变量对应. com.opensymphony.xwork2.inject.ContainerImpl 的构造函数会传入一个Map型factories, 该Map以Key为键, com.opensymphony.xwork2.inject.InternalFactory 为值. InternalFactory接口中只有一个用于成绩对象的 create() 方法.
总结: 容器根据 bean 节点的type, name属性确定一个对象; 根据 bean的 type, name 获取 InternalFactory, 它包含了对应对象的创建方法. 容器只缓存了对象的创建方法, 而不是一个个对象.
22. XWork容器使用 注入器(Injector) 记录对象与对象间的依赖关系. ContainerImpl中保存了一个用Map引用的ReferenceCache型的变量injectors, 当调用Map接口的get()方法时, ReferenceCache首先会查找内部是否缓存了相应的对象(通过调用继承来的get()). 如果有, 则直接返回, 没有则会调用create()方法, 根据key的内容产生对象并缓存.
1 //com.opensymphony.xwork2.inject.ContainerImpl 2 final Map<Class<?>, List<Injector>> injectors = 3 new ReferenceCache<Class<?>, List<Injector>>() { 4 @Override 5 protected List<Injector> create(Class<?> key) { 6 List<Injector> injectors = new ArrayList<Injector>(); 7 addInjectors(key, injectors); 8 return injectors; 9 } 10 }; 11 12 /** 13 * Recursively adds injectors for fields and methods from the given class to 14 * the given list. Injects parent classes before sub classes. 15 */ 16 void addInjectors(Class clazz, List<Injector> injectors) { 17 if (clazz == Object.class) { return; } 18 19 // Add injectors for superclass first. 20 addInjectors(clazz.getSuperclass(), injectors); 21 22 // TODO (crazybob): Filter out overridden members. 23 addInjectorsForFields(clazz.getDeclaredFields(), false, injectors); 24 addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors); 25 } 26 27 void addInjectorsForMethods(Method[] methods, boolean statics, 28 List<Injector> injectors) { 29 addInjectorsForMembers(Arrays.asList(methods), statics, injectors, 30 new InjectorFactory<Method>() { 31 public Injector create(ContainerImpl container, Method method, 32 String name) throws MissingDependencyException { 33 return new MethodInjector(container, method, name); 34 } 35 }); 36 } 37 38 void addInjectorsForFields(Field[] fields, boolean statics, 39 List<Injector> injectors) { 40 addInjectorsForMembers(Arrays.asList(fields), statics, injectors, 41 new InjectorFactory<Field>() { 42 public Injector create(ContainerImpl container, Field field, 43 String name) throws MissingDependencyException { 44 return new FieldInjector(container, field, name); 45 } 46 }); 47 } 48 49 <M extends Member & AnnotatedElement> void addInjectorsForMembers( 50 List<M> members, boolean statics, List<Injector> injectors, 51 InjectorFactory<M> injectorFactory) { 52 for (M member : members) { 53 if (isStatic(member) == statics) { 54 Inject inject = member.getAnnotation(Inject.class); 55 if (inject != null) { 56 try { 57 injectors.add(injectorFactory.create(this, member, inject.value())); 58 } catch (MissingDependencyException e) { 59 if (inject.required()) { throw new DependencyException(e); } 60 } 61 } 62 } 63 } 64 } 65 66 //com.opensymphony.xwork2.inject.util.ReferenceMap 67 //com.opensymphony.xwork2.inject.util.ReferenceCache的父类 68 V internalGet(K key) { 69 Object valueReference = delegate.get(makeKeyReferenceAware(key)); 70 return valueReference == null ? null : (V) dereferenceValue(valueReference); 71 } 72 73 public V get(final Object key) { 74 ensureNotNull(key); 75 return internalGet((K) key); 76 }
23. OGNL3要素
- 表达式, 带有语法含义的字符串, 规定了操作的类型和内容.
- Root对象, 操作对象
- 上下文环境, 数据环境, 访问其中的参数时, 需通过 # 加上表达式, 如: #introduction.user.name
1 @Test 2 public void testGetValue() throws Exception { 3 User user = new User(); 4 user.setId(1); 5 user.setName("downpour"); 6 7 Map context = new HashMap(); 8 context.put("introduction", "My name is "); 9 10 Object name = Ognl.getValue(Ognl.parseExpression("name"), user); 11 assertEquals("downpour", name); 12 13 Object contextValue = Ognl.getValue(Ognl.parseExpression("#introduction"), context, user); 14 assertEquals("My name is ", contextValue); 15 16 Object hello = Ognl.getValue(Ognl.parseExpression("#introduction + name"), context, user); 17 assertEquals("My name is downpour", hello); 18 }
24. OGNL中通过 @[class]@[field/method] 语法访问静态变量或方法
25. OGNL表达式中能使用 +, -, *, /, ++, --, ==, mod, in, not in等
26. 投影与选择
- 投影: 选出集合中每个元素的相同属性, 然后组成新的集合. 选择: 过滤满足 selection 条件的集合元素. 有3中操作符
- ? 选择满足条件的所有元素
- ^ 选择满足条件的第一个元素
- $ 选择满足条件的最一个元素
//返回Root对象的group属性中的users集合中所有元素的name属性构成的集合 group.users.{name} //将group中users这个集合中的元素的code和name用 - 连接符拼起来构成的字符串集合 group.users.{code + '-' + name} //返回Root对象的group中的users这个集合中所有元素中name不为null的元素构成的集合 group.users.{? #this.name != null}
27. 构造对象(容易引起漏洞)
- 构造List: 使用 {}, 中间使用逗号隔开元素的方式表达列表
- 构造Map: 使用 #{}, 中间使用逗号隔开键值对, 并使用冒号隔开 key和 value来构造Map
- 构造对象: 直接使用已知对象的构造函数来构造对象
//构造一个List {"green", "red", "black"} //构造一个Map #{"key1" : "value1", "key2" : "value2"} //构造一个java.net.URL对象 new java.net.URL("http://localhost")
28. this 指针: 指向了当前计算的 "调用者" 对应的实例
//返回group中users这个集合中所有age大于3的元素构成的集合 user.{? #this.age > 3} //返回group中users这个集合里的大小 +1 后的值 group.users.size().(#this+1) //返回Root对象的group中users这个集合所有元素中name不为null的元素构成的集合 group.users.{? #this.name != null}
29. #的三种用途
- 加在普通OGNL表达式前面, 用于访问OGNL上下文中的变量
- 使用 #{} 语法动态创建 Map
- 加在 this 指针之前表示对 this指针的引用
30. Struts2中Action可以通过实现 ModelDriven<T> 接口实现解耦请求参数.(ModelDrivenInterceptor负责拦截)
1 public class LoginAction extends ActionSupport implements ModelDriven<User> { 2 private static final long serialVersionUID = 1L; 3 private User user = new User(); 4 5 @Override 6 public String execute() throws Exception { 7 8 return SUCCESS; 9 } 10 11 public User getModel() { 12 return user; 13 } 14 }
31. 通过在 struts.xml 中配置异常映射可以将简单的异常处理从action中解耦
<action name="index" class="tonny.study.struts.action.IndexAction"> <exception-mapping result="error" exception="java.sql.SQLException"/> <exception-mapping result="input" exception="tonny.exception.NoSuchUserException"/> <result>/index.jsp</result> <result name="error">/error.jsp</result> <result name="input">/input.jsp</result> </action>
32. 自定义转换器: web页面接收或传递的都是字符串类型, 当需要自定义Java对象到String或String到Java对象的转换时可以通过自定义转换器实现
- 继承 DefaultTypeConverter 或 StrutsTypeConverter 或者使用 @Conversion() + @TypeConversion()注解
- 新建 ActionName-conversion.properties 或 xwork-conversion.properties
- 为属性文件添加配置: 属性名称=类型转换器的全类名 或 待转型类型的全类名=类型转换器的全类名
- 其实struts默认会进行一些转换, 如传递user.name=aaa&user.password=adfd, struts会调用目标action中user变量所属类的无参构造函数并调用setName()和setPassword(). 如果user属性为List型, 页面可以这样传递user[0].name=aaa&user[1].name=dfdf, struts也会自动构建. 如果user为map类型, 可以这样传递user['foo'].name=aaa&user['bar'].name=aaaa;
1 public class LoginAction extends ActionSupport { 2 private static final long serialVersionUID = 1L; 3 private User user; 4 private Date birth; 5 6 @Override 7 public String execute() throws Exception { 8 System.out.println(user.getName()); 9 System.out.println(user.getPassword()); 10 System.out.println(birth); 11 return SUCCESS; 12 } 13 14 public User getUser() { 15 return user; 16 } 17 18 public void setUser(User user) { 19 this.user = user; 20 } 21 22 public Date getBirth() { 23 return birth; 24 } 25 26 public void setBirth(Date birth) { 27 this.birth = birth; 28 } 29 }
- 局部转换器(针对某个action内成员变量的转换) : 在与action相同的目录下新建ActionName-conversion.properties, 其内容为 property=xxx.xxx.CustomerConverter
1 /** 2 * 方式一: 继承 StrutsTypeConverter 3 * 4 * @author kaitang.yi 5 * @date 2013-8-13 下午4:09:24 6 */ 7 public class UserConverter extends StrutsTypeConverter { 8 @Override 9 public Object convertFromString(Map context, String[] values, Class toClass) { 10 User user = new User(); 11 String[] userValues = values[0].split(","); 12 user.setName(userValues[0]); 13 user.setPassword(userValues[1]); 14 return user; 15 } 16 17 @Override 18 public String convertToString(Map context, Object o) { 19 User user = (User) o; 20 return user.getName() + ":" + user.getPassword(); 21 } 22 }
//LoginAction-conversion.properties
user=tonny.study.struts.converter.UserConverter
- 全局转换器: 对于一些需要全局转换的类型, 如String转成 java.util.Date, 在src根目录下新建 xwork-conversion.properties, 内容类似: java.util.Date=xx.xx.DateConverter
1 /** 2 * 方式二: 继承 DefaultTypeConverter 3 * 4 * @author kaitang.yi 5 * @version 1.0 6 * @date 2013-8-13 下午4:14:55 7 */ 8 public class DateConverter extends DefaultTypeConverter { 9 @Override 10 public Object convertValue(Map<String, Object> context, Object value, Class toType) { 11 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 12 if (toType == Date.class) { 13 try { 14 String[] params = (String[]) value; //需将页面传递过来的参数转成String数组 15 return sdf.parse(params[0]); 16 } catch (ParseException e) { 17 e.printStackTrace(); 18 return null; 19 } 20 } else if(toType == String.class) { 21 return sdf.format((Date) value); 22 } 23 return null; 24 } 25 }
//xwork-conversion.properties
java.util.Date=tonny.study.struts.converter.DateConverter