文章目录
EasySwing介绍
不知道大家在编写Java界面部分的时候有没有这种感觉,就是设计界面这部分步骤大部分都大同小异,都是首先先new出一个新控件,然后设置这个新控件的一些属性,最后将这个控件加进相应的面板(例如:Jpanel)或者容器(例如:Container)里。反正我在设置界面的时候对于这些纷繁复杂的控件类的设置方法记不清,总要去网上查相应文档才能设置。例如jfrmMain.setLocationRelativeTo(null);
设置窗口位于屏幕中央这句代码我总是漏写。
所以,对于这些大致逻辑相同的设置界面代码,我们可不可以方便点不写代码而进行写一些配置文件自动化(也就是一键)生成界面呢?答案是可以的,就是我准备写的EasySwing工具。
EasySwing:只需要写一个你想要的界面里的控件相关设置的XML文件,和有关的properties文件,就可以快速生成你想要的界面。编写这个Easyswing所需知识:XML文件解析、properties文件解析和最重要的反射机制。
EasySwing的实现
首先,我们可以先用以下代码简单的方式写个十分简单的界面。
public static void main(String[] args) {
JFrame jfrmMain = new JFrame("这是EasySwing的一次尝试");
jfrmMain.setSize(680, 560);
jfrmMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jfrmMain.setLocationRelativeTo(null);
Container container = new Container();
container.setLayout(new BorderLayout());
jfrmMain.setContentPane(container);
Font topicFont = new Font("微软雅黑", Font.BOLD, 32);
Color topicColor = new Color(3, 31, 203);
JLabel jlblTopic = new JLabel("EasySwing的尝试", JLabel.CENTER);
jlblTopic.setFont(topicFont);
jlblTopic.setForeground(topicColor);
container.add(jlblTopic, BorderLayout.NORTH);
Font normalFont = new Font("微软雅黑", Font.PLAIN, 16);
JPanel jpnlFoot = new JPanel();
container.add(jpnlFoot, BorderLayout.SOUTH);
JButton jbtnAdd = new JButton("添加");
jbtnAdd.setFont(normalFont);
jpnlFoot.add(jbtnAdd);
JButton jbtnDelete = new JButton("删除");
jbtnDelete.setFont(normalFont);
jpnlFoot.add(jbtnDelete);
JButton jbtnModify = new JButton("修改");
jbtnModify.setFont(normalFont);
jpnlFoot.add(jbtnModify);
JButton jbtnExit = new JButton("退出");
jbtnExit.setFont(normalFont);
jpnlFoot.add(jbtnExit);
jfrmMain.setVisible(true);
}
其运行结果如下,真的只是个简单的界面,甚至说简陋,大佬勿喷。最终我们就是要用工具和配置文件实现一键产生这个界面。
XML文件的写法以及规则的制订
xml文件其实是最难想的,因为它关乎这以后如何解析的基调;同时xml文件是最好写的,因为一旦规则定下来,方便给他人出个使用说明书,只需要按着规则去写即可。我写的EasySwing工具定的以下规则:
-
首先根标签是view
-
下一级标签是controller(控件)
(2.1) id:代表控件的名称,这个非常重要因为以后所有控件都是存在一个Map(控件池)中的,这个充当键值,所以请保证不要重复。
(2.2) para:代表该控件new出来的时候所需要的参数,其中空串代表无参构造,null代表以
null
进行构造,若是多参构造则’'里是字符串类型
,直接写数字带小数点是double
类型,不带小数点是int
类型,写true或者false就是boolean
类型,加=代表去properties文件去找相应的写好的常量(也可以直接写对应的值,这里只是为了语义性更强些和方便阅读),加#代表去所谓的控件池去找。(2.3) father:代表该控件要被加在哪个容器中,若无则可以不写,若有则写该父容器的id值。
(2.4) locate:代表该控件加在父容器中的位置,无父容器肯定不写,若直接加则写个空串。
(2.5) mainView:代表该控件是否是最后用来显示的主体JFrame,是要写为true,不是可以不写。
-
下一级标签是method。如果有的控件没有要执行的方法可以不写。
(3.1) name:代表该方法的名字,直接去properties文件查找。
(3.2) para:代表该方法需要的参数,与controller的para参数一样,在此不再累赘。
总结下:就是=就在properties文件里找,#就在控件池里面找,其余按相应规则去写就行。其实我们可以从XML文件发现para这个属性逻辑有些多变且复杂,确实是,我们工具就是以它开始分析和建立的。
以下是根据以上代码和规则写出XML文件
<?xml version="1.0" encoding="UTF-8"?>
<view>
<controller id="boderLayout" para="" ></controller>
<controller id= "topicFont" para= "'微软雅黑',=加粗字体, 32" ></controller>
<controller id= "topicColor" para= "3, 31, 203" ></controller>
<controller id= "normalFont" para= "'微软雅黑', =正常字体, 16"></controller>
<controller id= "container" para="" >
<method name= "设置布局模式" para= "#boderLayout"></method>
</controller>
<controller id= "jfrmMain" para= "'这是EasySwing的一次尝试'" mainView= "true">
<method name= "设置尺寸" para="680, 560"></method>
<method name= "设置默认关闭操作" para="=退出时关闭"></method>
<method name= "设置相对位置" para= "null"></method>
<method name= "设置底层面板" para= "#container"></method>
</controller>
<controller id= "jlblTopic" para= "'EasySwing的尝试',=标签居中" father= "container" locate= "边界布局.北">
<method name= "设置字体" para= "#topicFont"></method>
<method name= "设置背景颜色" para= "#topicColor"></method>
</controller>
<controller id= "jpnlFoot" para= "" father= "container" locate= "边界布局.南"> </controller>
<controller id= "jbtnAdd" para= "'添加'" father= "jpnlFoot" locate= "">
<method name= "设置字体" para= "#normalFont"></method>
</controller>
<controller id = "jbtnDelete" para= "'删除'" father= "jpnlFoot" locate= "">
<method name= "设置字体" para= "#normalFont"></method>
</controller>
<controller id = "jbtnModify" para= "'修改'" father= "jpnlFoot" locate= "">
<method name= "设置字体" para= "#normalFont"></method>
</controller>
<controller id = "jbtnExit" para= "'退出'" father= "jpnlFoot" locate= "">
<method name= "设置字体" para= "#normalFont"></method>
</controller>
</view>
properties文件的写法
properties文件就好写多了,它就是一些有关的键值对集合。其中主要包括三种。
-
控件类型 键:控件名称 值:控件的包名称 备注:导一个类后,代码最上面就有
-
方法名称 键:方法名称 值:相应类中方法的具体写法 备注:一定要查对写对,否则方法会执行失败
-
一些常量 键:常量名称 值:常量真正的值 备注:一定要查对写对,否则可能达不到预期目的
根据代码写如下properties文件
#控件类型
#jfrmMain=javax.swing.JFrame
jfrmMain=com.mec.Easyswing.support.MJframe
#container=java.awt.Container
container=com.mec.Easyswing.support.MContainer
jlblTopic=javax.swing.JLabel
boderLayout=java.awt.BorderLayout
topicFont=java.awt.Font
topicColor=java.awt.Color
normalFont=java.awt.Font
#jpnlFoot=javax.swing.JPanel
jpnlFoot=com.mec.Easyswing.support.MJpnlFoot
jbtnAdd=javax.swing.JButton
jbtnDelete=javax.swing.JButton
jbtnModify=javax.swing.JButton
jbtnExit=javax.swing.JButton
#方法名称
设置尺寸=setSize
设置默认关闭操作=setDefaultCloseOperation
设置相对位置=setLocationRelativeTo
设置底层面板=setContentPane
设置布局模式=setLayout
设置字体=setFont
设置背景颜色=setForeground
#一些常量
退出时关闭=3
正常字体=0
加粗字体=1
斜体字体=2
标签居中=0
边界布局.北=North
边界布局.南=South
边界布局.西=West
边界布局.东=East
边界布局.中间=Center
应该这样来说,代码出现一个新的控件,就去给XML文件和properties文件加东西,两个是同步的,相辅相成的一起走。
接下来就是重头戏了,开始写我们的工具代码部分。
EasySwing工具的编写
首先一上来别急着直接去解析XML文件,也不要急于编写什么逻辑复杂的东西。我们观察XML文件可以发现每个标签都存在para这个属性,这个属性就好像是所有控件的数据一样,是啊,构造需要参数,方法的执行也需要参数,发现这个参数需要多次编写多次使用,因此根据Java的面对对象思想我们将para独立封装成一个类ViewParameterModel。
ViewParameterModel类
public class ViewParameterModel {
//任何参数都有两个属性:类型,值
private Class<?> type;
private Object value;
public ViewParameterModel() {
}
void setPara(String para) throws Exception {
if (para.startsWith("#")) {
ViewFactory viewFactory = ViewFactory.currentInstance(); //这个类先别管,以后会用到
Object controller = viewFactory.getController(para.substring(1));
if (controller == null) {
return;
}
this.value = controller;
this.type = controller.getClass();
return;
}
if (para.startsWith("=")) {
para = ProperitiesParse.value(para.substring(1)); //这个PropertiesParse工具先忽略,我以后会给这个工具的链接
}
setType(para);
setValue(para);
}
private void setType(String para) {
if (para.equalsIgnoreCase("null")) {
this.type = Object.class;
return;
}
if (para.startsWith("'") && para.endsWith("'")) {
this.type = String.class;
return;
}
if (para.equalsIgnoreCase("true") || para.equalsIgnoreCase("false")) {
this.type = boolean.class;
return;
}
try {
Integer.valueOf(para);
this.type = int.class;
return;
} catch (NumberFormatException e) {
}
try {
Double.valueOf(para);
this.type = double.class;
return;
} catch (NumberFormatException e) {
}
return;
}
private void setValue(String para) throws Exception {
if (type == null) {
throw new Exception("XML文件para参数配置错误"); //也可以自己写一个异常类
}
if (type.equals(Object.class)) {
this.value = null;
return;
}
if (type.equals(String.class)) {
this.value = para.substring(1, para.length() - 1);
return;
}
if (type.equals(boolean.class)) {
this.value = Boolean.valueOf(para);
return;
}
if (type.equals(int.class)) {
this.value = Integer.valueOf(para);
return;
}
if (type.equals(double.class)) {
this.value = Double.valueOf(para);
return;
}
return;
}
Object getValue() {
return value;
}
Class<?> getType() {
return type;
}
}
可以看到编写这个类的目的十分简单,任何参数都有两个共同的属性,一个是参数类型(这里只涉及简单的类型:int
,double
,boolean
,String
),另一个是参数的值。这个类就是根据上面的XML编写规则的简单逻辑写出来的,不存在什么难度。关于代码中的ViewFactory(后面的工具类)。
tips:要记得局部性验证这个类对不对,可不可用,因为它是这个工具最基础的类,就像建高楼大厦一定要保证地基一样耐用。同时这个最基础的类不是一蹴而就写出来的,一定是经过再往前走一些步骤后才逐步完善出来的
接下来,我们知道一个方法未必只有一个参数(ViewParameterModel只代表一个参数),且参数是有顺序关系的,所以考虑用List存储相关参数是一个解决方法。所以要把刚才的ViewParameterModel类当成个工具使用,再生成个类ViewParameters。
ViewParameters类
public class ViewParameters {
private List<ViewParameterModel> parameters;
ViewParameters(String para) throws Exception { //传过来的参数直接就是XML文件para内容
String[] paras = para.split(",");
if (paras.length == 1 && paras[0].trim().length() <= 0) { //这里代表para内容是"",空串
return;
}
parameters = new ArrayList<>();
for (int i = 0; i < paras.length; i++) {
ViewParameterModel viewParameterModel = new ViewParameterModel();
viewParameterModel.setPara(paras[i].trim());
parameters.add(viewParameterModel);
}
}
Class<?>[] getParametersType() { //得到参数的类型数组,方便反射机制用来找方法
if (parameters == null) {
return new Class<?>[] {};
}
int count = parameters.size();
Class<?>[] result = new Class<?>[count];
for (int i = 0; i < count; i++) {
result[i] = parameters.get(i).getType();
}
return result;
}
Object[] getParametersValue() { //得到参数的值数组,方便反射机制执行方法
if (parameters == null) {
return new Object[] {};
}
int count = parameters.size();
Object[] result = new Object[count];
for (int i = 0; i < count; i++) {
result[i] = parameters.get(i).getValue();
}
return result;
}
}
这个类的核心数据就是parameters,它是由ViewParameterModel类组成的List。构造方法是用来建立parameters的,然后其他两个方法得到参数类型数组和得到参数值数组都是以后反射机制需要用的。这个类完美的体现了Java的面向对象思想。
Java面向对象思想:一个类的核心是成员,类的建立就是通过考虑成员才建立出来的,该类的所有方法都是围绕这个成员来进行操作的。考虑问题不是考虑完成这个问题的具体步骤(面向过程思想),而是先考虑能把问题分成由哪些对象或者成员组成,由多个对象的联系来完成问题。
tips:要记得进行局部性验证,保证做到现在为止是否正确。
接下来就要考虑XML文件中各个控件的存放了,还没有进行真正的XML解析,还是只考虑XML文件中的一些数据。此时,XML文件中controller标签中id属性才能体现出作用,起id这个名字才能说的通,它是主键。存放XML中各个控件用Map存放,键为该控件名称,值为该控件真正的对象。Map<String, Object>
关于这里值为Object类型,因为控件类型各有不同所以用Object来统一管理(Object是一切类的基类)。
但是!!!问题也出在Object!如果值为Object,说明他什么都可以接收,什么都可以向这个Map里面放,也就意味着可以放入根本不属于控件类型的对象。这麻烦就大了。所以"万物皆可Object"这句话既是褒义词也是贬义词。因为它是所有类基类,所以我们可以很方便用它,但是,也正是因为它是所有类的基类,也会带来危险。
说上面这些话是为了引出工具保护思想,就拿这里做例子,我们一定不能把这个存放控件的Map
传到外面让他人使用,防止他人放不应该放的东西进行破坏。细心的人可能发现我前面的工具类基本很少有public,都是用的包权限。这就是工具的保护!只能在这个包下进行操作,包外无权处理。所以做工具一定要有工具保护思想!!!。
上面说了这么多,只是提出一个小的工具编程思想。接下来真正的引出我们下一个工具类ViewFactory。
ViewFactory类
public class ViewFactory {
private Map<String, Object> viewControllerPool;
private static ViewFactory viewFactory;
private ViewFactory() {
viewControllerPool = new HashMap<>();
}
public static ViewFactory newInstance() { //代表新界面的ViewFactory
viewFactory = new ViewFactory();
return viewFactory;
}
public static ViewFactory currentInstance() { //代表当前界面的ViewFactory
return viewFactory == null ? newInstance() : viewFactory;
}
void addController(String id, Object object) {
viewControllerPool.put(id, object);
}
public Object getController(String id) {
return viewControllerPool.get(id);
}
}
这个类的核心是viewControllerPool,用来存放XML中相应控件的,关于增加控件和得到控件两个方法很简单,就不再赘述。
但是关于另一个成员viewFactory我要说下。这是一种十分简单的单例模式,就意思这个类只需要也只能有一个对象。考虑下面一种问题,如果已经产生了个界面,然后又要产生界面怎么办?两个界面肯定控件不同呀,不能往pool里一直加东西吧,如果键重复了可不就出问题了吗?所以给了两个得到ViewFactory的方法,一个是newnewInstance()
该方法表明新开个界面,其中的pool就重新申请了,不管以前的控件了。另一个是currentInstance()
意思是当前的ViewFactory对象,如果没有则申请,如果有则可得到当前的pool里面放着当前界面的已经加进去的控件。
接下来,是EasySwing工具最后一个类了。终于在前面我们分析并解决了XML文件中一些数据的存储问题,接下来就是真正的解析XML文件了,然后用已经建好的工具类去存储相关数据了。
ViewFactoryBuilder类
public class ViewFactoryBuilder {
private static final String jframe = "mainView";
public ViewFactoryBuilder() {
}
public static void showView() {
ViewFactory viewFactory = ViewFactory.currentInstance();
JFrame mainView = (JFrame) viewFactory.getController(jframe);
mainView.setVisible(true);
}
public static void loadPropertiesFileConfigPath(String configPath) {
ProperitiesParse.loadProperities(configPath);
//加载相关的properties文件,要用绝对路径
}
public static void loadXMLFileConfigPath(String configPath) { //参数是相关XML文件路径,可以是相对路径
ViewFactory viewFactory = ViewFactory.newInstance(); //解析一个新的XML文件代表新的一个ViewFactory里要存放新的控件
new XMLParse() {
@Override
public void dealElement(Element element, int index) {
String id = element.getAttribute("id");
String para = element.getAttribute("para");
String father = element.getAttribute("father");
String locate = element.getAttribute("locate");
String mainView = element.getAttribute("mainView");
try {
Class<?> klass = Class.forName(ProperitiesParse.value(id));
ViewParameters viewParameters = new ViewParameters(para);
Constructor<?> constructor = klass.getDeclaredConstructor(viewParameters.getParametersType());
Object controller = constructor.newInstance(viewParameters.getParametersValue());
if (mainView.equalsIgnoreCase("true")) {
viewFactory.addController(jframe, controller);
}
viewFactory.addController(id, controller);
parseMethod(element, klass, controller);
addController(father.trim(), locate.trim(), controller, viewFactory);
} catch (Exception e) {
e.printStackTrace();
}
}
}.parseTag(XMLParse.getDocument(configPath), "controller");
}
private static void parseMethod(Element element, Class<?> klass, Object controller) {
new XMLParse() {
@Override
public void dealElement(Element element, int index) {
String name = element.getAttribute("name");
String para = element.getAttribute("para");
try {
ViewParameters viewParameters = new ViewParameters(para);
Method method = klass.getMethod(ProperitiesParse.value(name), viewParameters.getParametersType());
method.invoke(controller, viewParameters.getParametersValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}.parseTag(element, "method");
}
private static void addController(String father, String locate, Object sonController, ViewFactory viewFactory) {
if (father.length() <= 0) {
return;
}
IPaneAddMethod fatherController = (IPaneAddMethod) viewFactory.getController(father);
if (locate.length() <= 0) {
fatherController.IAdd((Component) sonController);
} else {
String location = ProperitiesParse.value(locate);
fatherController.IAdd((Component) sonController, location);
}
}
}
这个类是最容易出错的地方,记录下我所踩过的坑。
-
反射得到方法,我用的是getDeclaredMethod(),这个可以获获得到本类的所有方法,包括public,private,protected修饰的,但是他不可以得到父类的方法。经过修改应该用getMethod(),能获取当前类和父类public修饰的方法,仅限public。因为有些控件的方法是父类的,所以要用getMethod()。
-
进行局部性验证的时候发现,总是出现NoSuchMethodException()异常,仔细查看,发现问题。
java.awt.Container.setLayout(java.awt.BorderLayout)
和javax.swing.JFrame.setLocationRelativeTo(java.lang.Object)
经过查看源码发现是参数类型不对,所以找不到该方法。
为了解决第二个问题,采用以下方式:
public class MJframe extends JFrame { //继承JFrame,以便完成某些特定方法
private static final long serialVersionUID = -324446629969726058L;
public MJframe() throws HeadlessException {
super();
}
public MJframe(GraphicsConfiguration gc) {
super(gc);
}
public MJframe(String title) throws HeadlessException {
super(title);
}
public MJframe(String title, GraphicsConfiguration gc) {
super(title, gc);
}
public void setLocationRelativeTo(Object object) {
super.setLocationRelativeTo(null);
}
public void setContentPane(MContainer contentPane) {
super.setContentPane(contentPane);
}
}
public interface IPaneAddMethod {//该接口是为了方便以后容器里面加控件的方便。
void IAdd(Component component);
void IAdd(Component component, Object object);
}
public class MContainer extends Container implements IPaneAddMethod {
//继承Container类,以便完成某些方法,同时实现IPaneAddMethod接口,以便以后加控件
private static final long serialVersionUID = 7116483843211133420L;
public MContainer() {
super();
}
public void setLayout(BorderLayout borderLayout) {
super.setLayout(borderLayout);
}
public void setLayout(FlowLayout flowLayout) {
super.setLayout(flowLayout);
}
public void setLayout(GridLayout gridLayout) {
super.setLayout(gridLayout);
}
@Override
public void IAdd(Component component) {
super.add(component);
}
@Override
public void IAdd(Component component, Object object) {
super.add(component, object);
}
}
public class MJpnlFoot extends JPanel implements IPaneAddMethod {//继承JPnel,方便以后加控件
private static final long serialVersionUID = -7489743937546425832L;
@Override
public void IAdd(Component component) {
super.add(component);
}
@Override
public void IAdd(Component component, Object object) {
super.add(component, object);
}
}
同时去修改properties文件里的配置即可解决。
该类(ViewFactoryBuilder)的主要任务是扫面XML文件,并根据XML文件结合以前的工具类,并将加载配置方法和显示最后的主界面对外用。
至此,EasySwing工具的主要四个工具类就全部做完了!
EasySwing工具具体的使用步骤
- 首先将想要生成的界面有关配置文件(XML文件和properties文件)根据上面所述规则写好。
- ViewFactoryBuilder.loadPropertiesFileConfigPath(“properties文件的路径”)
- ViewFactoryBuilder.loadXMLFileConfigPath(“XML文件的路径”);
- ViewFactoryBuilder.showView();就可以生成想要的界面了。
public static void main(String[] args) {
ViewFactoryBuilder.loadPropertiesFileConfigPath("/initViewStore.properties");
ViewFactoryBuilder.loadXMLFileConfigPath("/initViewCfg.xml");
ViewFactoryBuilder.showView();
}
运行上述代码后,结果如下:
我保证绝对不是只把上面图copy过来,就是用EasySwing工具运行出来的。不信你们可以用这个工具去试一试。
至此,EasySwing工具的编写告一段落了。
但是!!! 其实这个工具还没有达到完美。有两个问题。
- 不知道你有没有发现我写的XML文件的顺序是不能变的,也就是说,一些必须要先生成的控件必须写在XML文件前面,后生成的控件才可以用它。这样给用户带来不便了,理想情况应该是写XML文件的顺序对于生成界面要没有影响。所以,我这还是有点瑕疵的,等待进一步完善。
- 控件的响应事件没有添加,按按钮应该要产生相应的事件啊。是,这是锦上添花的事,以后等我进一步完善。
解决了问题2,因为所有控件都存在一个池子里面,用id得到它,强转就行。
public class Demo {
public static void main(String[] args) {
ViewFactoryBuilder.loadPropertiesCfgPath("/viewCfg.properties");
ViewFactoryBuilder.loadXMLPath("/viewCfg.xml");
ViewFactoryBuilder.showView();
JFrame jfrm = (JFrame) ViewFactoryBuilder.getController("jfrmMain");
jfrm.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
jfrm.dispose();
}
});
}
}
遇到的问题和总结
- properties文件为了转换为中文和神仙数字情况的出现。
- para里出现=去properties找,#在控件池子里找,字符串为"‘字符串’"
- XML规则根标签,标签属性有id,para,father,locate,mainView,标签有name,para。
- 观察都有参数,封装底层类,ViewParameter,只是最底层还没有那个控件池,所以只有字符串,数字,null,boolean基本的进行判断。
- 做完最底层,一定要牢记局部性测试,像盖高楼一样,底层地基要打好。
- 遇到了输入流问题,properties文件读的数据显示出来乱码,修改文件的properties属性,一律都选utf-8,同时修改PropertiesParse工具,改为inputstreamReader。
- ViewParameters类,为解析XML中para字符串(只是规则,不是真正的解析过程),para参数是不定的而且有顺序,用List,并且对外输出的方法是为了反射机制提供便利的得到类和值数组。
- viewFactory这个类的核心是viewControllerPool,用来存放XML中相应控件的,并且用到简单的单例模式(newInstance和currentInstance)
- viewFactoryBuilder才是真正的开始扫描文件。
- 对于无法修改的类(JFrame和Container)无法进行操作,进行巧妙地办法,继承个类。
后记
对框架的思考
一年过去了,回头再看写的easyswing博文,发现功能太单一和简陋了,同时现如今swing开发早都过时了,桌面应用级开发大部分是用C/C++来做的。Java用来网页开发。
但也正是此次回顾,让我意识到easyswing可能是我写的第一个框架。
事情是这样的。在一次项目工作中,浏览器下调试,找bug定位找到一个控件。
而我在相关的html中,没有找到相关的控件,html很短,很容易分辨出,浏览器调试模式下这个html却又很多。这是为什么呢?chrome这么智能吗?还能自己写代码?
肯定不会无缘无故的自动生成代码,还是那句话,计算机很笨,人工智能也是人在前,人为主导。我们看到控件id为layui,才明白它是使用的layui框架。
框架就是给一些配置,自动生成东西,实际底层的代码框架开发者一行都没有少写。和我的easyswing很像!从这里可以对框架认识更近一步了,同时easyswing一定要写,这就是一开始框架的雏形!只不过easyswing简单,而layui很大,发展次数多,考虑情况多。