模仿读取struts.xml TDD重写

解析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;
    }    

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值