解析xml文档,利用反射机制调用类中的方法
需求:0. 读取配置文件struts.xml
1. 根据actionName找到相对应的class , 例如LoginAction, 通过反射实例化(创建对象)
据parameters中的数据,调用对象的setter方法, 例如parameters中的数据是
("name"="test" , "password"="1234") ,
那就应该调用 setName和setPassword方法
2. 通过反射调用对象的exectue 方法, 并获得返回值,例如"success"
3. 通过反射找到对象的所有getter方法(例如 getMessage),
通过反射来调用, 把值和属性形成一个HashMap , 例如 {"message": "登录成功"} ,
放到View对象的parameters
4. 根据struts.xml中的 <result> 配置,以及execute的返回值, 确定哪一个jsp,
放到View对象的jsp字段中。
首先来看一下我自己开始只按照功能来写的代码
//--为重新审查代码
public static View runAction(String actionName, Map<String,String> parameters) {
/*
*/
File f1 = new File("config/struts.xml");
//--使用Document 解析xml 复杂,但是通用,下面tdd代码使用jdom2来解析
File f = new File(f1.getAbsolutePath());
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc=builder.parse(f);
Element root = doc.getDocumentElement();
//--这里写了这么复杂一个map,并且没有任何 语境性质,别人也看不懂
//--解决方法:构建内部类来实现,描述struts.xml中的结构
Map<String,Map<String,String>> actions = new HashMap<String,Map<String,String>>();
if (root.getNodeType() == Node.ELEMENT_NODE) {
NodeList actionList = root.getChildNodes();
for (int i = 0; i < actionList.getLength(); i++) {
Node n = actionList.item(i);
if (n.getNodeType() == Node.ELEMENT_NODE) {
//获取节点的属性 <actions name="login" class=".....">
NamedNodeMap nnmap = n.getAttributes();
Map<String,String> action = new HashMap<String,String>();
action.put(nnmap.item(0).getNodeValue(),nnmap.item(1).getNodeValue());
NodeList result= n.getChildNodes();
//获取子节点 <result name="success">/mgr/main.jsp</result>
for(int j = 0;j<result.getLength();j++){
Node r = result.item(j);
if(r.getNodeType() == Node.ELEMENT_NODE){
//获取属性name="success" *里面的文本需要用 getFirstChild()来获取,比较难理解*
NamedNodeMap nn = r.getAttributes();
action.put(nn.item(0).getNodeValue(), r.getFirstChild().toString());
}
}
actions.put(nnmap.item(0).getNodeValue(), action);
}
}
}
Map<String,String> requestAction=null;
if(actionName!=null){
requestAction=actions.get(actionName);
}else{
System.err.println("没有actionName");
}
try {
if(requestAction!=null){
Class<?> c = Class.forName(requestAction.get(actionName));
Object co = c.newInstance();
if("login".equals(actionName)){
String name = parameters.get("name");
String password = parameters.get("password");
//--我写的直接把方法名写上去了,实际中完全不知道这些方法名,也不知道对应有什么message属性的
Method m1=c.getMethod("setName", String.class);
Method m2=c.getMethod("setPassword", String.class);
Method m3 = c.getMethod("execute");
m1.invoke(co, name);
m2.invoke(co, password);
String rest = (String)m3.invoke(co);
Method m4 = c.getMethod("getMessage");
String message = (String)m4.invoke(co);
Map<String,String> pras =new HashMap<String,String>();
pras.put("message", message);
View view = new View();
view.setJsp(requestAction.get(rest));
view.setParameters(pras);
return view;
}
}else{
System.out.println("没有找到对应action");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
上面代码 比较混乱,除了自己,别人估计不好看懂,自己下次看也是要认真想想为啥这么写。
TDD原则
独立测试:不同代码的测试应该相互独立,一个类对应一个测试类
测试驱动:即利用测试来驱动开发,是TDD的核心。要实现某个功能,要编写某个类或某个函数,应首先编写测试代码,明确这个类、这个函数如何使用,如何测试,然后在对其进行设计、编码。
先写断言:编写测试代码时,应该首先编写判断代码功能的断言语句,然后编写必要的辅助语句。
及时重构:对结构不合理,重复等“味道”不好的代码,在测试通过后,应及时进行重构。
小步前进:软件开发是复杂性非常高的工作,小步前进是降低复杂性的好办法。
接下来膜拜TDD的代码 。清晰。漂亮。好的代码不需要过多的注释。
//分析:读取struts.xml 并把参数付给对应类,去执行该类的execute方法。辅助类 读取xml 反射设置参数,得到参数
//中文注释是分析用的,可以去掉都
//1.解析xml
//先写测试类,根据测试类里写的代码用IDE检查出错误直接生成我们需要的类和方法,就不用手动创建了
public class ConfigurationTest {
//构造方法中,根据一个文件名,同目录下找到这个文件。并解析内容
Configuration cfg = new Configuration("struts.xml");
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void testGetClassName() {
String clzName = cfg.getClassName("login");
Assert.assertEquals("com.coderising.litestruts.LoginAction", clzName);
clzName = cfg.getClassName("logout");
Assert.assertEquals("com.coderising.litestruts.LogoutAction", clzName);
}
//测试要全面,尽可能囊括各种情况
@Test
public void testGetResultView(){
String jsp = cfg.getResultView("login","success");
Assert.assertEquals("/jsp/homepage.jsp", jsp);
jsp = cfg.getResultView("login","fail");
Assert.assertEquals("/jsp/showLogin.jsp", jsp);
jsp = cfg.getResultView("logout","success");
Assert.assertEquals("/jsp/welcome.jsp", jsp);
jsp = cfg.getResultView("logout","error");
Assert.assertEquals("/jsp/error.jsp", jsp);
}
}
//生成的Configuration类
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
public class Configuration {
//ActionConfig 用于描述struts.xml中每个action
Map<String,ActionConfig> actions = new HashMap<>();
public Configuration(String fileName) {
String packageName = this.getClass().getPackage().getName();
packageName = packageName.replace('.', '/');
InputStream is = this.getClass().getResourceAsStream("/" + packageName + "/" + fileName);
parseXML(is);
try {
is.close();
} catch (IOException e) {
throw new ConfigurationException(e);
}
}
//用jdom2解析,代码清晰可多可很多
private void parseXML(InputStream is){
SAXBuilder builder = new SAXBuilder();
try {
Document doc = builder.build(is);
Element root = doc.getRootElement();
for(Element actionElement : root.getChildren("action")){
String actionName = actionElement.getAttributeValue("name");
String clzName = actionElement.getAttributeValue("class");
ActionConfig ac = new ActionConfig(actionName, clzName);
for(Element resultElement : actionElement.getChildren("result")){
String resultName = resultElement.getAttributeValue("name");
String viewName = resultElement.getText().trim();
ac.addViewResult(resultName, viewName);
}
this.actions.put(actionName, ac);
}
} catch (JDOMException e) {
throw new ConfigurationException(e);
} catch (IOException e) {
throw new ConfigurationException(e);
}
}
//解析完了可以去map了拿了
public String getClassName(String action) {
ActionConfig ac = this.actions.get(action);
if(ac == null){
return null;
}
return ac.getClassName();
}
public String getResultView(String action, String resultName) {
ActionConfig ac = this.actions.get(action);
if(ac == null){
return null;
}
return ac.getViewName(resultName);
}
//私有 不想让别人看到,静态类 直接可以使用,不用通过父类的对象
private static class ActionConfig{
String name;
String clzName;
Map<String,String> viewResult = new HashMap<>();
public ActionConfig(String actionName, String clzName) {
this.name = actionName;
this.clzName = clzName;
}
public String getClassName(){
return clzName;
}
public void addViewResult(String name, String viewName){
viewResult.put(name, viewName);
}
public String getViewName(String resultName){
return viewResult.get(resultName);
}
}
//2.反射得到一个类中的方法
//测试类
public class ReflectionUtilTest {
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void testGetSetterMethod() throws Exception {
String name = "com.coderising.litestruts.LoginAction";
Class<?> clz = Class.forName(name);
List<Method> methods = ReflectionUtil.getSetterMethods(clz);
Assert.assertEquals(2, methods.size());
List<String> expectedNames = new ArrayList<>();
expectedNames.add("setName");
expectedNames.add("setPassword");
Set<String> acctualNames = new HashSet<>();
for(Method m : methods){
acctualNames.add(m.getName());
}
//containsAll set方法 查看是否完全包含了 list
Assert.assertTrue(acctualNames.containsAll(expectedNames));
}
@Test
public void testSetParameters() throws Exception{
String name = "com.coderising.litestruts.LoginAction";
Class<?> clz = Class.forName(name);
Object o = clz.newInstance();
Map<String,String> params = new HashMap<String,String>();
params.put("name","test");
params.put("password","1234");
ReflectionUtil.setParameters(o,params);
//下面代码可以直接拿到某个对象的私有属性
Field f = clz.getDeclaredField("name");
f.setAccessible(true);
Assert.assertEquals("test", f.get(o));
f = clz.getDeclaredField("password");
f.setAccessible(true);
Assert.assertEquals("1234", f.get(o));
}
@Test
public void testGetGetterMethod() throws Exception{
String name = "com.coderising.litestruts.LoginAction";
Class<?> clz = Class.forName(name);
List<Method> methods = ReflectionUtil.getGetterMethods(clz);
Assert.assertEquals(3, methods.size());
List<String> expectedNames = new ArrayList<>();
expectedNames.add("getMessage");
expectedNames.add("getName");
expectedNames.add("getPassword");
Set<String> acctualNames = new HashSet<>();
for(Method m : methods){
acctualNames.add(m.getName());
}
Assert.assertTrue(acctualNames.containsAll(expectedNames));
}
@Test
public void testGetParamters() throws Exception{
String name = "com.coderising.litestruts.LoginAction";
Class<?> clz = Class.forName(name);
LoginAction action = (LoginAction)clz.newInstance();
action.setName("test");
action.setPassword("123456");
Map<String,Object> params = ReflectionUtil.getParamterMap(action);
Assert.assertEquals(3, params.size());
Assert.assertEquals(null, params.get("messaage") );
Assert.assertEquals("test", params.get("name") );
Assert.assertEquals("123456", params.get("password") );
}
}
//反射类
public class ReflectionUtil {
public static List<Method> getSetterMethods(Class clz) {
return getMethods(clz,"set");
}
public static void setParameters(Object o, Map<String, String> params) {
List<Method> methods = getSetterMethods(o.getClass());
for(String name : params.keySet() ){
String methodName = "set" + name;
for(Method m: methods){
if(m.getName().equalsIgnoreCase(methodName)){
try {
m.invoke(o, params.get(name));
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
public static List<Method> getGetterMethods(Class<?> clz) {
return getMethods(clz,"get");
}
private static List<Method> getMethods(Class<?> clz, String startWithName){
List<Method> methods = new ArrayList<>();
for(Method m : clz.getDeclaredMethods()){
if(m.getName().startsWith(startWithName)){
methods.add(m);
}
}
return methods;
}
public static Map<String, Object> getParamterMap(Object o) {
Map<String ,Object> params = new HashMap<>();
List<Method> methods = getGetterMethods(o.getClass());
for(Method m : methods){
String methodName = m.getName();
String name = methodName.replaceFirst("get", "").toLowerCase();
try {
Object value = m.invoke(o);
params.put(name, value);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
}
return params;
}
}
//做一个登录操作 LoginAction是要被反射调用的类,其中有 name password 的set get 方法 execute 方法来验证登录的。
//3.综合
public class StrutsTest {
@Test
public void testLoginActionSuccess() {
String actionName = "login";
Map<String,String> params = new HashMap<String,String>();
params.put("name","test");
params.put("password","1234");
View view = Struts.runAction(actionName,params);
Assert.assertEquals("/jsp/homepage.jsp", view.getJsp());
Assert.assertEquals("login successful", view.getParameters().get("message"));
}
@Test
public void testLoginActionFailed() {
String actionName = "login";
Map<String,String> params = new HashMap<String,String>();
params.put("name","test");
params.put("password","123456"); //密码和预设的不一致
View view = Struts.runAction(actionName,params);
Assert.assertEquals("/jsp/showLogin.jsp", view.getJsp());
Assert.assertEquals("login failed,please check your user/pwd", view.getParameters().get("message"));
}
}
//读取
public class Struts {
private final static Configuration cfg = new Configuration("struts.xml");
public static View runAction(String actionName, Map<String,String> parameters) {
String clzName = cfg.getClassName(actionName);
if(clzName == null){
return null;
}
try {
Class<?> clz = Class.forName(clzName);
Object action = clz.newInstance();
ReflectionUtil.setParameters(action, parameters);
//execute 里返回success /fail并给message赋值
Method m = clz.getDeclaredMethod("execute");
String resultName = (String)m.invoke(action);
//params里结果message
Map<String,Object> params = ReflectionUtil.getParamterMap(action);
String resultView = cfg.getResultView(actionName, resultName);
View view = new View();
view.setParameters(params);
view.setJsp(resultView);
return view;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}