这几个问题,干扰了我很长时间。
主要因为书本的例子,是通过controller层返回到jsp层。但是,最后一个SSM项目,它用的是controller返回信息给Service层,再由Service层返回Jsp层。
实训:编写一个模糊查询姓名的项目。在另一个show.jsp页面显示查询信息。
一步一步:
先通过controller层返回到JSP层。
第一个问题:(未找到迭代器,或者不是迭代器类型)
javax.servlet.ServletException: javax.servlet.jsp.JspTagException: Don't know how to iterate over supplied "items" in <forEach>
show.jsp对应的代码信息:
<%--querylist.account是有提示的。只有list.account没有提示。(说明list不是一个可迭代器对象)--%> <c:forEach items="${queryList}" var="user"> ${user.account}<br> ${user.password}<br> ${user.sex}<br> ${user.name}<br> </c:forEach>
这是show.jsp对应的代码,这个信息表示querylist的遍历器没有这个对象。说明,存在的问题是querylist不是一个可迭代的对象。我们返回到定义这个queryList的Controller文件,发现:
// 问题出在user这里,queryList是一个集合对象,但是user是一个类对象,是用不了c:foreach model.addAttribute("queryList",user);
解决方式:将user更换成一个迭代器对象。(例如,前面我们定义的service层的方法是一个list类型,这就是一个迭代器对象。)(额外:在使用 Service层作为与JSP返回的层级时,怀疑之前一直报错的原因,就是Service层的返回类型被我换成了String类型)
修改之后,可以在show.jsp显示用户查询信息。
第二个问题:未找到bean “query2”。明明定义了query2
我尝试将用户信息查询得到的信息放在query.jsp页面下面,也就是query.jsp提供查询功能,并将信息放在query.jsp的下面。
一开始我的尝试:就是将原本返回给show的方法,改为返回给query。
在query.jsp里面,将show.jsp的代码复制过来。但是,结果出现一个很大的问题,我找了很久才发现问题:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'query2' available as request attribute
这个错误信息表明在处理请求时,Spring无法找到名为query2
的bean。
JSP原本的代码是:(这里需要将query修改为input)
<form:form method="post" action="${pageContext.request.contextPath}/user/query" modelAttribute="query2">
因为,我们将查询和显示,在Controller分别用了两个方法表示,大概流程是这样的:
首先我们在浏览器输入/input。
/input里面返回POJO的信息给query.jsp 。通过query2的属性,将POJO和query.jsp的表单信息进行绑定。
接着在query.jsp里面,提交用户信息,action 为/query 。
/query里面返回从数据库获取的信息给query.jsp。但是,此时query.jsp里面,又会读取一次表单信息,而此时表单信息的属性query2并没有定义。于是,IDEA就提示报错,:找不到bean “query2”。
修改之后:
大概流程:
首先浏览器输入/input 。
它返回POJO的信息给query.jsp。注意此时POJO还没有和query.jsp的name进行绑定。所以数据库返回的数据此时为null。
它也返回数据库获取的数据给query.jsp。(同样是null)
在用户query.jsp,它提交action 为/input。
于是和上面的信息一样。此时user为{“null”,"null","null","李"}
最后返回给query.jsp。此时,jsp里面的querylist就存在信息了。此时querylist为{“2020”,“1234”,“男”,“李XX”}
第三个问题:测试 跟踪流程去发现问题 (挺重要的)
(学习这个方式,以后就不需要先对spring和Mybatis先进行整合并测试。)我想通过一些测试方法跟踪 这个流程的输出情况。通过查找资料,得出,可以通过日志方式输出。
方法:引入`org.slf4j.Logger`类
不是`org.apache.commons.logging.Log`类`。Log`类的`info`方法只接受一个参数,不能使用占位符`{}`来格式化消息。(在前面,我学过使用org.apache.commons.logging.Log`类,使用打印像logger.info("登录成功") 这样的信息)如果想要使用占位符来格式化消息,您可以将`Log`类更改为`org.slf4j.Logger`类,并使用`org.slf4j.LoggerFactory`来获取日志记录器(logger)对象。例如,您可以将代码修改为以下内容:
其实就是在controller类里面添加两句:
private static final Logger logger = LoggerFactory.getLogger(Query2Controller.class);
logger.info("User info: {}", user); //测试user对象是否获取到数据。
//如果还想测试queryList是否获取数据,也可以这样写。
package controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import po.User;
import service.Query2;
@Controller
@RequestMapping("/user")
public class Query2Controller {
private static final Logger logger = LoggerFactory.getLogger(Query2Controller.class);
@Autowired
private Query2 query2;
@RequestMapping("/input")
public String input(Model model, User user){
logger.info("User info: {}", user);
model.addAttribute("query2",user);
model.addAttribute("queryList",query2.query2list(user));
return "query";
}
}
在添加方法之前,需要把有关依赖包导入进来。
又因为我们的项目是通过log4j的那个配置,文件输出日志信息,所以,需要对SLF4J和log4j进行绑定。
就两个依赖包:一个SLF4j的依赖包,和前面的输出日志信息搭配。
一个是绑定log4j的依赖包。实现在log4j的配置文件里面输出日志信息。
使用`log4j`作为日志记录实现,您需要在应用程序的类路径中添加`SLF4J`到`log4j`的绑定库。您可以通过添加以下依赖项来实现这一点:
<!-- for Maven -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.32</version>
</dependency>
此外,您还需要在应用程序的类路径中添加`log4j`库。您可以通过添加以下依赖项来实现这一点:
<!-- for Maven -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
最后,配置log4j的配置文件,
不过,这个文件,其实在创建项目的时候,在mybatis的学习里面,我们已经添加过了。
添加完依赖项后,您需要配置`log4j`以指定日志记录行为。您可以在应用程序的类路径中创建一个名为`log4j.properties`的文件,并在其中指定日志记录配置。例如,您可以在配置文件中添加以下内容:
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
在上面的示例中,我们配置了一个名为`stdout`的控制台日志记录器(appender),它将日志信息输出到控制台。您可以根据需要更改配置。
结果如下:
user info 这个的信息,就是我们获取的user对象信息。
通过控制台信息,我们再来分析:
这整个流程:
首先浏览器输入:/user/input 。进入input界面。
此时运行两个model.addribute,第一个user存放的信息是null.第一个querylist存放的信息也是null。此时显示querylist的信息是null.即空。
再将这些数据交给query.jsp
在query.jsp里面,它提交action为/user/input。它继续回到这个input界面。
此时再次运行两个model.addribute,不过,第一个存放的信息里面多了name的信息。第一个querylist存放的信息是一条完整的数据。
最后,显示querylist的信息。
这是第一次,浏览器访问input的信息:
这是第二次,在input界面,点击按钮之后的信息:(action:/input)
最后,回到一开始的问题。通过controller 提交给service层 然后再交给JSP层。
由service层返回JSP信息:(最开始的困惑)
在书本最后一个比较大的SSM项目里面,都是通过controller层返回service.方法。然后在service里面,返回return ".jsp" 。
这个和我们一开始学的通过 controller层返回数据有点不同。return "。jsp"
第一个问题:错误是说,线程问题,其实真正问题不是这个,
05-Jul-2023 16:42:54.580 淇℃伅 [mysql-cj-abandoned-connection-cleanup] org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading Illegal access: this web application instance has been stopped already. Could not load []. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
java.lang.IllegalStateException: Illegal access: this web application instance has been stopped already. Could not load []. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
找到文件所在,原来是我的mybatis-context.xml文件部署,给部署到dao.userDao里面去了,应该部署指定mybatis/UserMapper.xml
第二个问题:未解之谜,从service层传递的model属性,在JSP里面,并没有提示到获取了model的属性,但是,仍然可以得到最后的结果。
我们来比较一下,通过controller层返回给JSP的数据:
原因出在哪呢?尝试通过日志跟踪输出,查看信息是怎么从service层到View层的。
在log4j文件下,输出如下信息。
最后,在service层输入:
在控制台能够得到 数据。说明,数据传输是没有问题。
那么为什么JSP的model属性 提示没有获取到呢???没有获取到,它又是怎么得到最后的结果的???
未解之谜