Struts2_CRUD操作实例
来源:互联网 发布:json弹幕 编辑:程序博客网 时间:2024/06/05 06:52
Struts2_CRUD操作实例
Struts2运行流程:
浏览器先发了个请求,
先会到StrutsPrepareAndExecuteFilter的doFilter方法,
然后创建了一个StrutsActionProxy(代理),调用了这个代理的execute方法,
StrutsActionProxy里有一个DefaultActionInvocation的引用,调用了DefaultActionInvocation里的invoke()方法,
接着DefaultActionInvocation去调拦截器的intercept()方法,
拦截器ExceptionMappingInterceptor接着又回调DefaultActionInvocation的invoke()方法,
DefaultActionInvocation再调下一个拦截器的intercept()方法,然后再回调,
如此往复吗,到调完最后一个拦截器再回调后,将调用自己的invokeAction(),
最终调用Action的目标方法。
先会到StrutsPrepareAndExecuteFilter的doFilter方法,
然后创建了一个StrutsActionProxy(代理),调用了这个代理的execute方法,
StrutsActionProxy里有一个DefaultActionInvocation的引用,调用了DefaultActionInvocation里的invoke()方法,
接着DefaultActionInvocation去调拦截器的intercept()方法,
拦截器ExceptionMappingInterceptor接着又回调DefaultActionInvocation的invoke()方法,
DefaultActionInvocation再调下一个拦截器的intercept()方法,然后再回调,
如此往复吗,到调完最后一个拦截器再回调后,将调用自己的invokeAction(),
最终调用Action的目标方法。
1.ActionProxy:是Action的一个代理类,也就是说Action的调用是通过ActionProxy实现的,其实就是调用了ActionProxy.execute方法,而该方法又调用了ActionInvocation.invoke()方法。负责调用目标Action方法之前的拦截器的调用。
2.ActionInvocation:就是Action的调用者。ActionInvocation在Action的执行过程中,负责Interceptor,Action和Result等一系列元素的调度。
Params拦截器
Parameters拦截器将把表单字段映射到ValueStack栈的栈顶对象的各个属性中,如果某个字段在模型里没有匹配的属性,Param拦截器将尝试ValueStack栈中的下一个对象。
默认情况下,栈顶对象就是Action。
Parameters拦截器将把表单字段映射到ValueStack栈的栈顶对象的各个属性中,如果某个字段在模型里没有匹配的属性,Param拦截器将尝试ValueStack栈中的下一个对象。
默认情况下,栈顶对象就是Action。
把Action和Model隔开
在使用Struts作为前端的企业级应用程序时把Action和Model清晰的隔离开是有必要的,有些Action类不代表任何Model对象,他们的功能仅限于提供显示服务。
如果Action类实现了ModelDriven接口,该拦截器将把ModelDriven接口的getModel()方法返回的对象置于栈顶
1.Action实现ModelDriven接口后的运行流程
1).先会执行ModelDrivenInterceptor的interceptor方法
public String intercept(ActionInvocation invocation) throws Exception { //获取Action对象:EmployeeAction对象,此时该Action已经实现了ModelDriven接口 Object action = invocation.getAction(); //判断action是否是ModelDriven的实例 if (action instanceof ModelDriven) { //强制转换为ModelDriven类型 ModelDriven modelDriven = (ModelDriven) action; //获取值栈 ValueStack stack = invocation.getStack(); //调用ModelDriven接口的getModel()方法 //即调用EmployeeAction的getModel()方法 Object model = modelDriven.getModel(); if (model != null) {//把getModel()方法的返回值压入到值栈的栈顶,实际压入的是//EmployeeAction的employee成员变量 stack.push(model); } if (refreshModelBeforeResult) { invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model)); } } return invocation.invoke(); }
3).注意:getModel方法
public Employee getModel() {// TODO 自动生成的方法存根 employee = new Employee(); return employee;}不能写成return new Employee();,这样与成员变量employee 就没有了联系,当前Action的employee成员变量是null;如下图:
ModelDriven拦截器
当用户触发add请求时,ModelDriven拦截器将调用EmployeeAction对象的getModel()方法,并把返回的模型(Employee实例)压入到ValueStack栈。
接下来Parameters拦截器将把表单字段映射到ValueStack栈的栈顶对象的各个属性中,因为此时ValueStack栈的栈顶元素则是刚被压入的模型(Employee)对象,所以该模型将被填充。如果某个字段在模型里没有匹配的属性,Param拦截器将尝试ValueStack栈中的下一个对象。
当用户触发add请求时,ModelDriven拦截器将调用EmployeeAction对象的getModel()方法,并把返回的模型(Employee实例)压入到ValueStack栈。
接下来Parameters拦截器将把表单字段映射到ValueStack栈的栈顶对象的各个属性中,因为此时ValueStack栈的栈顶元素则是刚被压入的模型(Employee)对象,所以该模型将被填充。如果某个字段在模型里没有匹配的属性,Param拦截器将尝试ValueStack栈中的下一个对象。
回显:
1.客户端发送请求 employee.edit
2.getModel()把employee对象置于栈顶,
此时栈顶对象为employee,所以把请求参数赋给employee的对应属性
3.public String edit()方法
1. 从数据库中获取了employeeId对应的employee对象
2.把数据库中获取的属性放入值栈属性中
使用paramsPrepareParamsStack拦截器栈后的运行流程
1.paramsPrepareParamsStack和defaultStack一样都是拦截器栈 ,
而struts-default包默认使用的是defaultStack。
2.可以在struts配置文件中通过以下方式修改默认的拦截器栈
<default-interceptor-ref name="paramsPrepareParamsStack"></default-interceptor-ref>
3.paramsPrepareParamsStack拦截器拦截器在于:
params --> modelDriven -->params
所以可以先把请求参数赋给Action对应的属性,再根据赋给Action的那个属性值决定压到值栈栈顶的对象,最后再为栈顶对象的属性赋值。
对于edit操作而言
I.先为EmployeeAction的employeeId赋值
II.根据employeeId从数据库中加载对应的对象,并放入到值栈的栈顶
III.再为栈顶对象的employeeId赋值(实际上此时employeeId属性值已经存在)
IV.把栈顶对象的属性回显在表单中。
1.客户端发送请求 employee.edit
2.getModel()把employee对象置于栈顶,
此时栈顶对象为employee,所以把请求参数赋给employee的对应属性
3.public String edit()方法
1. 从数据库中获取了employeeId对应的employee对象
2.把数据库中获取的属性放入值栈属性中
使用paramsPrepareParamsStack拦截器栈后的运行流程
1.paramsPrepareParamsStack和defaultStack一样都是拦截器栈 ,
而struts-default包默认使用的是defaultStack。
2.可以在struts配置文件中通过以下方式修改默认的拦截器栈
<default-interceptor-ref name="paramsPrepareParamsStack"></default-interceptor-ref>
3.paramsPrepareParamsStack拦截器拦截器在于:
params --> modelDriven -->params
所以可以先把请求参数赋给Action对应的属性,再根据赋给Action的那个属性值决定压到值栈栈顶的对象,最后再为栈顶对象的属性赋值。
对于edit操作而言
I.先为EmployeeAction的employeeId赋值
II.根据employeeId从数据库中加载对应的对象,并放入到值栈的栈顶
III.再为栈顶对象的employeeId赋值(实际上此时employeeId属性值已经存在)
IV.把栈顶对象的属性回显在表单中。
4.关于回显:struts2表单标签会从值栈中获取对应的属性进行回显
该实例中的问题:
I.在执行删除的时候,employeeId不为空,但getModel()方法(employee = dao.get(employeeId);)却从数据库中加载了一个对象,没有必要。
II.在执行list()时,会employee = new Employee(); new了个 Employee()对象,没有必要。
解决方案:使用ParamPrepareInterceptor 的 intercept方法
该实例中的问题:
I.在执行删除的时候,employeeId不为空,但getModel()方法(employee = dao.get(employeeId);)却从数据库中加载了一个对象,没有必要。
II.在执行list()时,会employee = new Employee(); new了个 Employee()对象,没有必要。
解决方案:使用ParamPrepareInterceptor 的 intercept方法
关于PrepareInterceptor,源代码解析:
public String doIntercept(ActionInvocation invocation) throws Exception { //获取Action实例 Object action = invocation.getAction(); //判断Action是否实现了Preparable接口 if (action instanceof Preparable) { try { String[] prefixes;//根据当前拦截器的firstCallPrepareDo(默认为false)属性确定prefixes if (firstCallPrepareDo) { prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX}; } else { prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX}; }//若为false,则prefixes:prepare ,prepareDo//调用前缀方法, PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof Exception) { throw (Exception) cause; } else if(cause instanceof Error) { throw (Error) cause; } else { throw e; } } //根据当前拦截器的alwaysInvokePrepare(默认为true)决定是否调用Action的prepare方法 if (alwaysInvokePrepare) { ((Preparable) action).prepare(); } } return invocation.invoke(); }PrefixMethodInvocationUtil.invokePrefixMethod方法:
public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException { //获取Action实例Object action = actionInvocation.getAction(); //获取要调用的Action方法的名字(update)String methodName = actionInvocation.getProxy().getMethod(); //如果方法是空就调用executeif (methodName == null) {// if null returns (possible according to the docs), use the default execute methodName = DEFAULT_INVOCATION_METHODNAME;}//获取前缀方法Method method = getPrefixedMethod(prefixes, methodName, action);//若方法不为空,则通过反射调用前缀方法if (method != null) {method.invoke(action, new Object[0]);}}
PrefixMethodInvocationUtil.getPrefixedMethod方法:
public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) {assert(prefixes != null);//把方法的首字母变为大写String capitalizedMethodName = capitalizeMethodName(methodName); //遍历前缀数组 for (String prefixe : prefixes) { //通过拼接的方式,得到前缀方法名:第一次prepareUpdate,第二次prepareDoUpdate String prefixedMethodName = prefixe + capitalizedMethodName; try {//利用反射从action中获取对应的方法,若有直接返回,结束循环 return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY); } catch (NoSuchMethodException e) { // hmm -- OK, try next prefix if (LOG.isDebugEnabled()) { LOG.debug("cannot find method [#0] in action [#1]", prefixedMethodName, action.toString()); } } }return null;}
分析后得到的结论:
若Action实现了Preparable接口,则struts将会尝试执行prepare[ActionMethodName]方法,
若prepare[ActionMethodName]不存在,则将尝试执行prepareDo[ActionMethodName]方法,
若都不存在,就都不执行。
若ParamPrepareInterceptor的alwaysInvokePrepare属性为false,则Struts2将不会调用实现了Preparable接口的Action的prepare()方法
解决方案:
为每一个ActionMethod准备prepare[ActionMethodName]方法,而抛弃掉原来的prepare()方法
将ParamPrepareInterceptor的alwaysInvokePrepare属性置为false,以避免Struts2框架再调用prepare()方法
如何在配置文件中把拦截器栈的属性赋值:参看文档
使用paramsPrepareParamsStack
paramsPrepareStack从字面上理解来说,这个stack的拦截器调用的顺序为:首先params,然后prepare,接下来modelDriven,最后params。
struts2的设计上要求modelDriven在params之前调用,而业务中prepare要负责准备model,准备model又需要参数,这就需要在prepare之前运行params拦截器设置相关参数,这个也就是创建paramsPrepareParamsStack的原因。
流程如下:
1.params拦截器首先给action中的相关参数赋值,如id
2.prepare拦截器执行prepare方法,prepare方法会根据参数,如id,去调用业务逻辑,设置model对象
若prepare[ActionMethodName]不存在,则将尝试执行prepareDo[ActionMethodName]方法,
若都不存在,就都不执行。
若ParamPrepareInterceptor的alwaysInvokePrepare属性为false,则Struts2将不会调用实现了Preparable接口的Action的prepare()方法
解决方案:
为每一个ActionMethod准备prepare[ActionMethodName]方法,而抛弃掉原来的prepare()方法
将ParamPrepareInterceptor的alwaysInvokePrepare属性置为false,以避免Struts2框架再调用prepare()方法
如何在配置文件中把拦截器栈的属性赋值:参看文档
使用paramsPrepareParamsStack
paramsPrepareStack从字面上理解来说,这个stack的拦截器调用的顺序为:首先params,然后prepare,接下来modelDriven,最后params。
struts2的设计上要求modelDriven在params之前调用,而业务中prepare要负责准备model,准备model又需要参数,这就需要在prepare之前运行params拦截器设置相关参数,这个也就是创建paramsPrepareParamsStack的原因。
流程如下:
1.params拦截器首先给action中的相关参数赋值,如id
2.prepare拦截器执行prepare方法,prepare方法会根据参数,如id,去调用业务逻辑,设置model对象
3.modelDriven拦截器将model对象压入ValueStack,这里的model对象就是在prepare中创建的
4.params拦截器再次将参数赋值给model对象
5.action的业务逻辑执行
下面给出完整范例:
struts.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE struts PUBLIC"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN""http://struts.apache.org/dtds/struts-2.3.dtd"><struts> <package name="wul" namespace="/" extends="struts-default"> <!--配置使用paramsPrepareParamsStack作为默认的拦截器栈 <default-interceptor-ref name="paramsPrepareParamsStack"></default-interceptor-ref>--><!--修改ParamPrepareInterceptor中alwaysInvokePrepare的属性值为false --><interceptors><interceptor-stack name="wulstack"><interceptor-ref name="paramsPrepareParamsStack"><param name="prepare.alwaysInvokePrepare">false</param></interceptor-ref></interceptor-stack></interceptors><default-interceptor-ref name="wulstack"></default-interceptor-ref><action name="emp-*" class="com.wul.app.EmployeeAction"method="{1}"><result name="{1}">/emp-{1}.jsp</result><result name="success" type="redirectAction">emp-list</result></action> </package></struts>注意:
<interceptors>
<interceptor-stack name="wulstack">
<interceptor-ref name="paramsPrepareParamsStack">
<param name="prepare.alwaysInvokePrepare">false</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
要在<default-interceptor-ref name="wulstack"></default-interceptor-ref>前
index.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%><%@ taglib prefix="s" uri="/struts-tags"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Insert title here</title></head><body><a href="emp-list.action">List All Employees</a></body></html>emp-list.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%><%@ taglib prefix="s" uri="/struts-tags"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Insert title here</title></head><body><s:form action="emp-save"><s:textfield name="firstName" label="FirstName"></s:textfield><s:textfield name="lastName" label="LastName"></s:textfield><s:textfield name="email" label="Email"></s:textfield><s:submit></s:submit></s:form><table cellpadding="10" cellspacing="0" border="1"><thead><tr><td>ID</td><td>FirstName</td><td>LastName</td><td>Email</td><td>Edit</td><td>Delete</td></tr></thead><tbody><s:iterator value="#request.emps"><tr><td>${employeeId }</td><td>${firstName }</td><td>${lastName }</td><td>${email }</td><td><a href="emp-edit?employeeId=${employeeId}">Edit</a></td><td><a href="emp-delete?employeeId=${employeeId}">Delete</a></td></tr></s:iterator></tbody></table></body></html>emp-edit.jsp
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%><%@ taglib prefix="s" uri="/struts-tags"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Insert title here</title></head><body><s:debug></s:debug><s:form action="emp-update"><s:hidden name="employeeId"></s:hidden><s:textfield name="firstName" label="FirstName"></s:textfield><s:textfield name="lastName" label="LastName"></s:textfield><s:textfield name="email" label="Email"></s:textfield><s:submit></s:submit></s:form></body></html>Employee.java
package com.wul.app;public class Employee {private Integer employeeId;private String firstName;private String lastName;private String email;public Integer getEmployeeId() {return employeeId;}public void setEmployeeId(Integer employeeId) {this.employeeId = employeeId;}public String getFirstName() {return firstName;}public void setFirstName(String firstName) {this.firstName = firstName;}public String getLastName() {return lastName;}public void setLastName(String lastName) {this.lastName = lastName;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public Employee() {super();}public Employee(Integer employeeId, String firstName, String lastName,String email) {super();this.employeeId = employeeId;this.firstName = firstName;this.lastName = lastName;this.email = email;}}
Dao.java
package com.wul.app;import java.util.ArrayList;import java.util.HashMap;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;public class Dao {private static Map<Integer,Employee> emps = new LinkedHashMap<Integer,Employee>();static{emps.put(1001, new Employee(1001,"AA","aa","aa@xinlin.com"));emps.put(1002, new Employee(1002,"BB","bb","bb@xinlin.com"));emps.put(1003, new Employee(1003,"CC","cc","cc@xinlin.com"));emps.put(1004, new Employee(1004,"DD","dd","dd@xinlin.com"));emps.put(1005, new Employee(1005,"EE","ee","ee@xinlin.com"));}public List<Employee> getEmployee(){return new ArrayList<>(emps.values());}public void delete(Integer empId){emps.remove(empId);}public void save(Employee emp){long time =System.currentTimeMillis();emp.setEmployeeId((int)time);emps.put(emp.getEmployeeId(), emp);}public Employee get(Integer empId){return emps.get(empId);}public void update(Employee emp){emps.put(emp.getEmployeeId(), emp);}}
EmployeeAction.java
package com.wul.app;import java.sql.PreparedStatement;import java.util.Map;import org.apache.struts2.interceptor.RequestAware;import com.opensymphony.xwork2.ActionContext;import com.opensymphony.xwork2.ModelDriven;import com.opensymphony.xwork2.Preparable;public class EmployeeAction implements RequestAware,ModelDriven<Employee>,Preparable{private Dao dao = new Dao();private Employee employee;public String save(){dao.save(employee);return "success";}public void prepareSave(){employee = new Employee();}public String delete(){dao.delete(employeeId);return "success";}public void prepareEdit(){employee = dao.get(employeeId);}public String edit(){//1.获取传入的employeeId:employee.getEmployeeId()//2.根据employee获取Employee对象//Employee emp = dao.get(employee.getEmployeeId());//3.把栈顶对象的属性装配好//目前的employee对象只有employeeId属性,其他属性为null/*Struts2表单回显时:从值栈栈顶开始查找匹配的属性,若找到就添加到value属性中。*///employee.setEmail(emp.getEmail());//employee.setFirstName(emp.getFirstName());//employee.setLastName(emp.getLastName());//employee = dao.get(employee.getEmployeeId());不行,经过重写赋值的employee对象已经不再是栈顶对象了//手动的把从数据库中获取的Employee对象放到值栈的栈顶//但此时值栈栈顶及第二个对象均为Employee对象,有浪费//ActionContext.getContext().getValueStack().push(dao.get(employee.getEmployeeId()));return "edit";}public void prepareUpdate(){employee = new Employee();}public String update(){dao.update(employee);return "success";}////需要在当前的Employee中定义employeeId属性。////以接受请求参数//private Integer employeeId;////public void setEmployeeId(Integer employeeId) {//this.employeeId = employeeId;//}////public String delete(){//dao.delete(employeeId);////返回结果的类型应为:redirectAction////也可以是chain:实际上chain是没有必要的,因为不需要在下一个Action中////保留当前Action的状态////还有,若使用chain,则达到目标页面后,地址栏显示的依然是删除的那个连接,刷屏时会有重复提交////return "success";//}//public String list(){request.put("emps", dao.getEmployee());return "list";}////private String firstName;//private String lastName;//private String email;////public String getFirstName() {//return firstName;//}////public void setFirstName(String firstName) {//this.firstName = firstName;//}////public String getLastName() {//return lastName;//}////public void setLastName(String lastName) {//this.lastName = lastName;//}////public String getEmail() {//return email;//}////public void setEmail(String email) {//this.email = email;//}////public String save(){////1.获取请求参数:通过定义对应属性的方式//Employee employee = new Employee(null,firstName,lastName,email);////2.调用Dao的save方法//dao.save(employee);//////3.通过redirectAction的方式反应结果给emp-list//return "success";//}//private Map<String,Object> request = null;@Overridepublic void setRequest(Map<String, Object> arg0) {// TODO 自动生成的方法存根this.request = arg0; }private Integer employeeId;public void setEmployeeId(Integer employeeId) {this.employeeId = employeeId;}@Overridepublic Employee getModel() {// TODO 自动生成的方法存根//判读是Create还是Edit。//若为Create,则employee = new Employee();//若为Edit,则employee = dao.get(employeeId);//判定标准为是否有employeeId这个请求参数,若有该参数,则视为Edit,若没有该参数,则视为Create//若通过employeeId来判断,则需要在ModelDriven拦截器之前先执行一个params拦截器//而这可以通过使用paramsPrepareParams拦截器栈来实现//需要在struts.xml文件中配置使用paramsPrepareParams作为默认的拦截器栈。//if(employeeId==null)// employee = new Employee();//else//employee = dao.get(employeeId);return employee;}//prepare方法的主要作用:为getModel()方法准备model的。@Overridepublic void prepare() throws Exception {//// TODO 自动生成的方法存根//if(employeeId==null)// employee = new Employee();//else//employee = dao.get(employeeId);System.out.println("prepare...");}}web.xml
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>struts2_2</display-name> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
`
1 0
- Struts2_CRUD操作实例
- Struts2_CRUD
- 26-28-29-31.struts2_CRUD操作
- 21.Struts2_CRUD操作(1)查询和删除
- 22.Struts2_CRUD操作(2)添加和修改
- 25.Struts2_CRUD操作(3)使用拦截器之后的效果
- 24(续)Struts2_CRUD总结下(paramsPrepareParamsStack 拦截器)
- VI操作实例
- 注册表操作实例
- Hibernate操作视图实例
- java日期操作实例
- java File操作实例
- VC 时间操作实例
- Java文件操作实例
- mysql操作实例
- XML操作实例
- Blob操作实例
- apdcomport操作串口实例
- 谁说Source Insight只能看C盘的文件?我有妙招!
- C++中名字空间介绍(新手)
- 基于opencv的身份证识别系统
- cordova二维码扫描插件phonegap-plugin-barcodescanner使用及坑点
- Python之时间处理模块time
- Struts2_CRUD操作实例
- 根据“+”号,获取数字图片,数值变大变小,温度上升,降低
- iquery的事件触发问题
- 谈什么情况下C++编译器会自动生成default constructor
- AlertDialog.Builder的setCancelable
- javascript之DOM编程改变CSS样式(简易验证码显示)
- (4.6.12.2)uses-permission权限列表
- Javascript之入门篇(一)
- 并查集模板