重生之渣受归来书包网:struts2学习总结
来源:百度文库 编辑:九乡新闻网 时间:2024/04/29 13:46:31
1:准备工作,首先我们的把相关的jar考到lib目录下 2:我们的配置strts2在web.xml中 struts-cleanup org.apache.struts2.dispatcher.ActionContextCleanUp struts-cleanup /* struts2 org.apache.struts2.dispatcher.FilterDispatcher struts2 /*
说明在struts2中所有的请求都会通过这个FilterDispatcher过滤器3:然后我们写自己的业务逻辑和action类,这里说明一下,在struts2中不想struts1中那样非得继承action或者aciton的子类,在struts2很灵活,这个是不强制的,但是我通常情况下还是继承ActionSupport的在ActionSupport中的excute方法就想struts1中的dispatchAction的unspecified方法一样它会在不指定方法的时候默认执行的方法。4:做完这些了我们的在struts.xml中配置我们的Action呀 /struts2jdbc/stu_input.jsp
说明:第一句代码很重要,就设置我们的编码方式,通常我们都设置为中文编码,第二句就是当我们在修改struts.xml的时候我们不需要重新启动服务下面的代码就很容易理解了5:现在我们就可以随心所欲的做我们想要做的工作了,但是在做我们工作的同时我们的了解一下struts2的工作原理5.1:首先我们知道struts2所用到的技术就是webwork的技术,它所用到的表达式语言是ognl语言,这是种非常强大的对象导航语言5.2:我们应该知道在struts2中剔除来在struts1中的actionform来管理表单的现象,在struts2中我们用的action,他整合了strtus中的action和actionform为一体5.3:在struts2中是通过标签来对表单和实体bean进行管理的,在struts2中就提供一套标签非常灵活,不想在strtus1那样一大堆一大堆的6:现在我们可以做一下自己事了(CRUD)6.1:R:当我们在数据库中把一个列表拿出来啦,我们该如何把它传递到页面上呢?,然后如何显示呢?首先我们的拿到这个list,把它放到struts2提供的map中,具体代码public String getStus() { List stus = StuManager.getInstance().findStus(); ActionContext.getContext().put("stus", stus);//这就是struts2提供的map return Action.LOGIN; }
调用stu!getStu.action就可以调用这个方法了,他就会把这个list放放到ActionContext中。于是我们就可以利用标签在页面上得到这个list 编辑 删除 上传图片
这样我们就可以从ActionContext中用得到这个list了,这里的stus就是一个list数组他里面就是stu对象,本人个人认为这样做的不好之处就是,在iterator中应该有个var作为以个单独的对象用,这也是说明ognl的功能太牛逼了6.2:C:就是向库里面插入一条记录,这个相当的简单就是把name的值名称设定和实体bean中的值名称一样它会帮我们自动转型的 我们不需要考虑
就这样它就会把这些值传给stu对象,然后我们拿到这个对象就可以插入了6.3:U:更新就是把当前对象的id拿来然后找到这个对象,做修改在更新数据库表就搞定了首先我们的在列表界面上有一个链接把id传给它,它查找对象,代码如下:编辑
我们利用 来得到一个对象的id值把它传给url,最终传给action处理,这样我们在action中就可以得到这个id值了,查找这个对象了我们在后面是这样做的public String updateInput() { stu = StuManager.getInstance().findStuById(stu.getId()); return "updateinput"; }
然后我们显示这个stu的数据就可以了
注意我们还的把id隐藏起来,在下面的工作就和插入一样了,这里就不多说了6.4:D:删除就是和更新差不多,也是传递一个id值给它,删除
这里我用了一个js技巧,然后我传递到action中删掉就ok了7:用strtus2来做上传工作那是相当的简单,这里我直说一种方法上传7.1:首先我们就是写一个图片上传的页面和普通的上传一样的表单
无论我们用什么来上传文件我们都的在form中加入enctype="multipart/form-data" 7.2:这里我为了代码的好看我用了两个action,一个用上面所做的CRUD,一个用于上传图片,这个也没有办法,这要是他妈的参数比较多,stuImageAction的代码如下:// 学生 private Stu stu; //文件标题 private String title; //要上传的文件 private File upload; //上传文件类型 private String uploadContentType; //上传文件名 private String uploadFileName; public void setTitle(String title) { this.title = title; } public void setUpload(File upload) { this.upload = upload; } public void setUploadContentType(String uploadContentType) { this.uploadContentType = uploadContentType; } public void setUploadFileName(String uploadFileName) { this.uploadFileName = uploadFileName; } public String getTitle() { return (this.title); } public File getUpload() { return (this.upload); } public String getUploadContentType() { return (this.uploadContentType); } public String getUploadFileName() { return (this.uploadFileName); } //找学生 public String input() { stu = StuManager.getInstance().findStuById(stu.getId()); return ActionSupport.INPUT; } @Override public String execute() throws Exception { System.out.println("开始上传单个文件-----------------------"); System.out.println("==========" + getUploadFileName()); System.out.println("==========" + getUploadContentType()); System.out.println("==========" + getUpload()); // 以服务器的文件保存地址和原文件名建立上传文件输出流 if(this.getUpload() != null) { FileInputStream fis = new FileInputStream(this.upload); byte[] buffer = new byte[fis.available()]; fis.read(buffer); stu.setImage(buffer); StuManager.getInstance().uploadImage(stu); } return ActionSupport.SUCCESS; } public Stu getStu() { return stu; } public void setStu(Stu stu) { this.stu = stu; }
7.3:现在我们就得把这个action配置一下啦 image/bmp,image/png,image/gif,image/jpg,text/plain,application/msword 2000 /stu!getStus.action /struts2jdbc/upload_image.jsp
说明:上面我们为了限定类型和大小我设置一下struts2自身提供的拦截器,这样可以简化了代码,但是我管我们怎么样设我们都不要忘记要设定默认的拦截器 这里还有一个知识点就是当我们再转向到另一个action的时候我们一定要设置type为redirect或者我们如果想得到上一页的参数的情况下我们可以设定为chain也是没有问题/stu!getStus.action 这样当大于我们设定的大小和不我们设定的类型的时候,它就会报错7.4:上面我们不是得到是报错吗?那么我们该如何处理这些错误呢?在struts2中是用英文显示的我们可以通过在输入界面上这样一句就可以了
当然这是显示英文的错误,为了增强用户体验我们应该把这些英文的错误改成中文,那我们就要用国际话资源文件了,这里我们不要把汉字直接写到国际话资源文件中,这样是会除向乱码的,为来显示中文我们的把汉字首先专化unick编码写到资源文件中,那我们就可以显示这些中文了,但是我们找到这个文件呢?比方说我们现在的文件是globalMessages.properties,现在我们的配置一下struts.xml让struts可以找到这个文件,就是在struts.xml的头部写上这样一句代码就可以 了
8:表单验证在struts2中提供了一套表单验证的的方法,但是尽管这样我们还是喜欢用js来验证,不过能利用struts2的框架做表单验证的话我们为什么不学习一下呢?8.1:首先在struts2中为了做表单验证,我们的写一个关于表单验证的文件,就像在写js文件一样。这样的文件是以action的名字开头的,如User-validation.xml,记得后面必须是validation.xml,而且这个文件要房主和action同一级目录下,这样才可以找到这个文件加以解释。具体代码如下: userName 6 10 true userName [A-Za-z0-9]+ name [a-zA-Z]+ 1 120 email mainPage 1980-01-01 2007-07-29
8.2:当然我也可以配置这些出错的信息,在国际化资源文件中把上面的对应的message的key值对用到国际化资源文件中的key值这样我们就可以用汉字的编码来显示一些汉字了,代码如下:requiredstring=\u4f60\u597d\u4f60\u6240\u8f93\u5165\u7684\u5185\u5bb9\u4e0d\u80fd\u4e3a\u7a7astringlengthmessage=userName must be between ${minLength}and${maxLength} needs to be 6-8 characters long !regexmessage=The userName must be character and number!nameregexmessage=name must be all character!agemessage=Age must be in range ${min} and ${max}emailmessage=Must provide a valid email!mainpagemessage=Invalid homepage urlbirthdaymessage=Birthday must be within ${min} and ${max}
注意不论我们这么写这些国际化文件我们一定记得在struts.xml 中配置这个国际化资源文件
8.3:除了我们struts2自动的错误处理我们也可以写自己的错误信息如:public String execute() throws Exception { System.out.println("Age=" + getAge()); System.out.println("Birthday" + birthday); sports = getSports(); for (String sport : sports) { System.out.println(sport); } if ((sex == null) || sex.equals("")) { addFieldError("sex", "sex is not null"); return INPUT; } return SUCCESS; }
然后我同样用struts2中的标签就可以显示我们的错误信息
9:拦截器来实现用户登录的验证9.1:我们登录之后让session中保存这个user对象,让一次会话中得到这个user对象所以我们首先用一个登录页面来记录这user对象,代码如下:public String execute() throws Exception { Admin admin2 = AdminManager.getInstance().checkAdmin(admin.getUsername()); if(admin2 != null && admin2.getPassword().equals(admin.getPassword())) { ActionContext.getContext().getSession().put("admin", admin2); return ActionSupport.SUCCESS; } return ActionSupport.INPUT; }
9.2:然后我们的配置这个action /index.jsp /stu!getStus.action
需要注意:的是我们在配置AdminAction的时候我们不需要让他来拦截admin对象,因为我们是通过它得到的Admin对象的(如果配上了就永远都不能调整过去了),所我们这个是写到另一个包中9.3:然后我的一个拦截器public class AdminIntercepter extends AbstractInterceptor { @Override public String intercept(ActionInvocation invocation) throws Exception { ActionContext ctx=invocation.getInvocationContext(); Map session=ctx.getSession(); Admin admin = (Admin)session.get("admin"); if(admin!=null) { return invocation.invoke();[Y1] } return "index"; }
9.4:接下来我们在我们需要验证的所有的action包中统统的加上这个拦截器就可以了 /index.jsp
注意这个我们写到包里面了,是为了不与json产生冲突,当然我们在不用json的情况下我们可以用继承的方法实现也是一样的,这个我们还定义一个全局的results这样能使它跳转到index.jsp页面10:统一异常处理在struts2中的异常处理机制非常方便,但是我还是折磨了好久,唉,人笨也没有办法10.1:首先我们的写一个我们自己处理的异常类来处理我们想要的异常,SystemException.java,由于这个方法太长我就不下了,就是继承RuntimeException后做一下自己的处理就可以了10.2:然后我们的配置这个个性化异常,我们可以配置成为全局的也可以配置成为局部的 (局部)
全局的
问题:但是这里我测试了,他妈的全局的异常和拦截器不能定义在一起但是无论我们怎么写都的写一个全局的result /index.jsp
这样当我们抛出异常的时候就可以定位到这个页面上了10.3:然后我们在jsp中用struts2中标签就可以得到这个错误信息代码如下:输出异常对象本身: //这个是出去给客户看的基本异常信息 输出异常堆栈信息: //这个是输入堆栈的信息是给开发人员看的
11:我们如何从库里面把byte[]数组拿出来正常显示成图片呢11.1:首先我们的在这个把这个图片和对象对应,也就说谁的属性对应谁的图片 "/>
这样的化我们就得到这个对象的id了11.2:然后我们对这个showView方法做一下相应/** * 显示图片 * @return * @throws Exception */ public String showView() throws Exception{ HttpServletResponse res = ServletActionContext.getResponse(); stu = StuManager.getInstance().findStuById(stu.getId()); byte[] im = stu.getImage(); OutputStream out = null; byte[] bytes = null; if(im!=null){ java.io.ByteArrayInputStream bi = new java.io.ByteArrayInputStream(im); //二进制输出流 res.setContentType( "multipart/form-data" ); //得到输出流 out = res.getOutputStream(); //从输入流读取数据到输出流 bytes = new byte [ 1024 ]; while ( -1 != bi.read( bytes ) ) { out.write( bytes ); } //强制刷新输出流 out.flush(); } return null; }
这个我都测试了两三天真实郁闷终于成功12:利用ajax来实现单选按钮的相应(即选择哪个就出现那个内容)12.1:首先我们的写一个ajax的js然后我们才能做一些事情
在页面我们上我们这样写
我们可以用div也可以用span,但是我们只能用这个两个才可以的,如果用别的话就会出现问题12.2:然后我们来对用httpxmlrequest发出的请求做一下处理我们这个可以写一个jsp也可以用servlet来处理这里我们来我们用servlet来处理这个问题public class SelectAjax extends HttpServlet { private static final String CONTENT_TYPE= "text/html; charset=GB18030"; protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType(CONTENT_TYPE); String itemNo = req.getParameter("itemNo"); List list = new ArrayList(); list.add("张三"); list.add("李四"); list.add("王五"); if(itemNo != null) { StringBuffer sbf = new StringBuffer(); sbf.append(""); resp.getWriter().print(sbf.toString()); } }}
就这样我们就可以做好了
-------------------------------------------------------------------------------- [Y1]这句的意思这个拦截器执行完毕后继续执行其他的拦截和action,也就相当与struts1中的super.execute(mapping, form, request, response); [番茄花园2]把迭代的id传给action中stu对象中id [番茄花园3]迭代的id值
说明在struts2中所有的请求都会通过这个FilterDispatcher过滤器3:然后我们写自己的业务逻辑和action类,这里说明一下,在struts2中不想struts1中那样非得继承action或者aciton的子类,在struts2很灵活,这个是不强制的,但是我通常情况下还是继承ActionSupport的在ActionSupport中的excute方法就想struts1中的dispatchAction的unspecified方法一样它会在不指定方法的时候默认执行的方法。4:做完这些了我们的在struts.xml中配置我们的Action呀
说明:第一句代码很重要,就设置我们的编码方式,通常我们都设置为中文编码,第二句就是当我们在修改struts.xml的时候我们不需要重新启动服务下面的代码就很容易理解了5:现在我们就可以随心所欲的做我们想要做的工作了,但是在做我们工作的同时我们的了解一下struts2的工作原理5.1:首先我们知道struts2所用到的技术就是webwork的技术,它所用到的表达式语言是ognl语言,这是种非常强大的对象导航语言5.2:我们应该知道在struts2中剔除来在struts1中的actionform来管理表单的现象,在struts2中我们用的action,他整合了strtus中的action和actionform为一体5.3:在struts2中是通过标签来对表单和实体bean进行管理的,在struts2中就提供一套标签非常灵活,不想在strtus1那样一大堆一大堆的6:现在我们可以做一下自己事了(CRUD)6.1:R:当我们在数据库中把一个列表拿出来啦,我们该如何把它传递到页面上呢?,然后如何显示呢?首先我们的拿到这个list,把它放到struts2提供的map中,具体代码public String getStus() { List
调用stu!getStu.action就可以调用这个方法了,他就会把这个list放放到ActionContext中。于是我们就可以利用标签在页面上得到这个list
这样我们就可以从ActionContext中用
就这样它就会把这些值传给stu对象,然后我们拿到这个对象就可以插入了6.3:U:更新就是把当前对象的id拿来然后找到这个对象,做修改在更新数据库表就搞定了首先我们的在列表界面上有一个链接把id传给它,它查找对象,代码如下:编辑
我们利用
然后我们显示这个stu的数据就可以了
注意我们还的把id隐藏起来,在下面的工作就和插入一样了,这里就不多说了6.4:D:删除就是和更新差不多,也是传递一个id值给它,删除
这里我用了一个js技巧,然后我传递到action中删掉就ok了7:用strtus2来做上传工作那是相当的简单,这里我直说一种方法上传7.1:首先我们就是写一个图片上传的页面和普通的上传一样的表单
无论我们用什么来上传文件我们都的在form中加入enctype="multipart/form-data" 7.2:这里我为了代码的好看我用了两个action,一个用上面所做的CRUD,一个用于上传图片,这个也没有办法,这要是他妈的参数比较多,stuImageAction的代码如下:// 学生 private Stu stu; //文件标题 private String title; //要上传的文件 private File upload; //上传文件类型 private String uploadContentType; //上传文件名 private String uploadFileName; public void setTitle(String title) { this.title = title; } public void setUpload(File upload) { this.upload = upload; } public void setUploadContentType(String uploadContentType) { this.uploadContentType = uploadContentType; } public void setUploadFileName(String uploadFileName) { this.uploadFileName = uploadFileName; } public String getTitle() { return (this.title); } public File getUpload() { return (this.upload); } public String getUploadContentType() { return (this.uploadContentType); } public String getUploadFileName() { return (this.uploadFileName); } //找学生 public String input() { stu = StuManager.getInstance().findStuById(stu.getId()); return ActionSupport.INPUT; } @Override public String execute() throws Exception { System.out.println("开始上传单个文件-----------------------"); System.out.println("==========" + getUploadFileName()); System.out.println("==========" + getUploadContentType()); System.out.println("==========" + getUpload()); // 以服务器的文件保存地址和原文件名建立上传文件输出流 if(this.getUpload() != null) { FileInputStream fis = new FileInputStream(this.upload); byte[] buffer = new byte[fis.available()]; fis.read(buffer); stu.setImage(buffer); StuManager.getInstance().uploadImage(stu); } return ActionSupport.SUCCESS; } public Stu getStu() { return stu; } public void setStu(Stu stu) { this.stu = stu; }
7.3:现在我们就得把这个action配置一下啦
说明:上面我们为了限定类型和大小我设置一下struts2自身提供的拦截器,这样可以简化了代码,但是我管我们怎么样设我们都不要忘记要设定默认的拦截器
当然这是显示英文的错误,为了增强用户体验我们应该把这些英文的错误改成中文,那我们就要用国际话资源文件了,这里我们不要把汉字直接写到国际话资源文件中,这样是会除向乱码的,为来显示中文我们的把汉字首先专化unick编码写到资源文件中,那我们就可以显示这些中文了,但是我们找到这个文件呢?比方说我们现在的文件是globalMessages.properties,现在我们的配置一下struts.xml让struts可以找到这个文件,就是在struts.xml的头部写上这样一句代码就可以 了
8:表单验证在struts2中提供了一套表单验证的的方法,但是尽管这样我们还是喜欢用js来验证,不过能利用struts2的框架做表单验证的话我们为什么不学习一下呢?8.1:首先在struts2中为了做表单验证,我们的写一个关于表单验证的文件,就像在写js文件一样。这样的文件是以action的名字开头的,如User-validation.xml,记得后面必须是validation.xml,而且这个文件要房主和action同一级目录下,这样才可以找到这个文件加以解释。具体代码如下:
8.2:当然我也可以配置这些出错的信息,在国际化资源文件中把上面的对应的message的key值对用到国际化资源文件中的key值这样我们就可以用汉字的编码来显示一些汉字了,代码如下:requiredstring=\u4f60\u597d\u4f60\u6240\u8f93\u5165\u7684\u5185\u5bb9\u4e0d\u80fd\u4e3a\u7a7astringlengthmessage=userName must be between ${minLength}and${maxLength} needs to be 6-8 characters long !regexmessage=The userName must be character and number!nameregexmessage=name must be all character!agemessage=Age must be in range ${min} and ${max}emailmessage=Must provide a valid email!mainpagemessage=Invalid homepage urlbirthdaymessage=Birthday must be within ${min} and ${max}
注意不论我们这么写这些国际化文件我们一定记得在struts.xml 中配置这个国际化资源文件
8.3:除了我们struts2自动的错误处理我们也可以写自己的错误信息如:public String execute() throws Exception { System.out.println("Age=" + getAge()); System.out.println("Birthday" + birthday); sports = getSports(); for (String sport : sports) { System.out.println(sport); } if ((sex == null) || sex.equals("")) { addFieldError("sex", "sex is not null"); return INPUT; } return SUCCESS; }
然后我同样用struts2中的标签就可以显示我们的错误信息
9:拦截器来实现用户登录的验证9.1:我们登录之后让session中保存这个user对象,让一次会话中得到这个user对象所以我们首先用一个登录页面来记录这user对象,代码如下:public String execute() throws Exception { Admin admin2 = AdminManager.getInstance().checkAdmin(admin.getUsername()); if(admin2 != null && admin2.getPassword().equals(admin.getPassword())) { ActionContext.getContext().getSession().put("admin", admin2); return ActionSupport.SUCCESS; } return ActionSupport.INPUT; }
9.2:然后我们的配置这个action
需要注意:的是我们在配置AdminAction的时候我们不需要让他来拦截admin对象,因为我们是通过它得到的Admin对象的(如果配上了就永远都不能调整过去了),所我们这个是写到另一个包中9.3:然后我的一个拦截器public class AdminIntercepter extends AbstractInterceptor { @Override public String intercept(ActionInvocation invocation) throws Exception { ActionContext ctx=invocation.getInvocationContext(); Map session=ctx.getSession(); Admin admin = (Admin)session.get("admin"); if(admin!=null) { return invocation.invoke();[Y1] } return "index"; }
9.4:接下来我们在我们需要验证的所有的action包中统统的加上这个拦截器就可以了
注意这个我们写到包里面了,是为了不与json产生冲突,当然我们在不用json的情况下我们可以用继承的方法实现也是一样的,这个我们还定义一个全局的results这样能使它跳转到index.jsp页面10:统一异常处理在struts2中的异常处理机制非常方便,但是我还是折磨了好久,唉,人笨也没有办法10.1:首先我们的写一个我们自己处理的异常类来处理我们想要的异常,SystemException.java,由于这个方法太长我就不下了,就是继承RuntimeException后做一下自己的处理就可以了10.2:然后我们的配置这个个性化异常,我们可以配置成为全局的也可以配置成为局部的
全局的
问题:但是这里我测试了,他妈的全局的异常和拦截器不能定义在一起但是无论我们怎么写都的写一个全局的result
这样当我们抛出异常的时候就可以定位到这个页面上了10.3:然后我们在jsp中用struts2中标签就可以得到这个错误信息代码如下:输出异常对象本身:
11:我们如何从库里面把byte[]数组拿出来正常显示成图片呢11.1:首先我们的在这个把这个图片和对象对应,也就说谁的属性对应谁的图片
这样的化我们就得到这个对象的id了11.2:然后我们对这个showView方法做一下相应/** * 显示图片 * @return * @throws Exception */ public String showView() throws Exception{ HttpServletResponse res = ServletActionContext.getResponse(); stu = StuManager.getInstance().findStuById(stu.getId()); byte[] im = stu.getImage(); OutputStream out = null; byte[] bytes = null; if(im!=null){ java.io.ByteArrayInputStream bi = new java.io.ByteArrayInputStream(im); //二进制输出流 res.setContentType( "multipart/form-data" ); //得到输出流 out = res.getOutputStream(); //从输入流读取数据到输出流 bytes = new byte [ 1024 ]; while ( -1 != bi.read( bytes ) ) { out.write( bytes ); } //强制刷新输出流 out.flush(); } return null; }
这个我都测试了两三天真实郁闷终于成功12:利用ajax来实现单选按钮的相应(即选择哪个就出现那个内容)12.1:首先我们的写一个ajax的js然后我们才能做一些事情
在页面我们上我们这样写
我们可以用div也可以用span,但是我们只能用这个两个才可以的,如果用别的话就会出现问题12.2:然后我们来对用httpxmlrequest发出的请求做一下处理我们这个可以写一个jsp也可以用servlet来处理这里我们来我们用servlet来处理这个问题public class SelectAjax extends HttpServlet { private static final String CONTENT_TYPE= "text/html; charset=GB18030"; protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType(CONTENT_TYPE); String itemNo = req.getParameter("itemNo"); List list = new ArrayList(); list.add("张三"); list.add("李四"); list.add("王五"); if(itemNo != null) { StringBuffer sbf = new StringBuffer(); sbf.append(""); resp.getWriter().print(sbf.toString()); } }}
就这样我们就可以做好了
-------------------------------------------------------------------------------- [Y1]这句的意思这个拦截器执行完毕后继续执行其他的拦截和action,也就相当与struts1中的super.execute(mapping, form, request, response); [番茄花园2]把迭代的id传给action中stu对象中id [番茄花园3]迭代的id值
struts2学习总结
struts2.x学习总结
struts2 跳转类型 result 总结大全
Struts2.1 OGNL 表达式 学习笔记
Struts2中OGNL,valueStack,stackContext的学习
struts2示例学习2-快速起步 - Struts - New - ITeye论坛
大学学习总结范文
“国培学习总结”
小继教 学习 总结
Jetty学习总结
STL学习总结
党课学习个人总结
Struts2实例教程
struts2 ajaxf
struts2 doubleselect
Struts2标签
交换生学习生活总结
关于MSP430的学习总结
[转载]缠论学习总结
教师政治理论学习v 总结
PhotoShop 学习方法论简单总结
个人学习总结探讨方法
Struts2教程1:第一个Struts2程序
Struts2 XML配置详解