目录
1.Book、DbOperation等这些JavaBean的实例化
问题2:在HttpServlet中使用 @Autowired注解的实例,报"NullPointerException"
在小白新手web开发简单总结(八)-数据库HSQLDB实例上面的例子的代码是我在反复解决各种问题之后可以成功运行项目的最终代码,这些代码并不是我想要总结的重点。因为本身就是Android开发,对写这些Java代码太简单,主要想总结下在这个过程中遇到的一些问题,也让我对web开发有了更深的印象,当然也产生一些疑问,需要自己后面去一一解决。本此主要就是总结了一些遇到的问题。
一 问题总结
1.Book、DbOperation等这些JavaBean的实例化
对于这些JavaBean之间有一些是有依赖关系的,那么就涉及到这些JavaBean的实例化、加载等管理过程,很容易的我就想到了可以借鉴在小白新手web开发简单总结(六)-Spring的IoC容器的提到的注解的方式来管理这些JavaBean。
由于有前面的一些积累,所以首先想到的就是Spring来管理JavaBean,就需要把AnnotationConfigApplicationContext实例化,并且还要在web应用启动的时候就实例化,很自然的想到实现一个 ServletContextListener类,如下:
@Configuration
@ComponentScan //扫描所在类的包以及子包,来创建@Component的类
public class SpringContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("SpringContextListener context Initialized!!! ");
// bookManagerService.createBookTable();
//初始化所有的@Component的类
new AnnotationConfigApplicationContext(SpringContextListener.class);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("SpringContextListener context Destroyed!!! ");
}
}
通过这个代码就实现了在web应用启动的时候,初始化AnnotationConfigApplicationContext,那么就可以使用@Component、 @Autowired等注解了,并且也想到需要在web.xml配置这个监听器:
<listener>
<listener-class>com.wj.hsqldb.SpringContextListener</listener-class>
</listener>
以为这样就万事具备,就可以完成成功的运行项目,但是万万没想到各种问题接踵而至。
问题1:@ComponentScan是有扫描范围的
我最初是从先写到Book、DbOperation、JdbcConfiguration以及BookManagerService这四个类,我通过增加的在SpringContextListener添加代码testDb()方法来验证验证下增删改查HSQLDB数据库这部分的正确性,详细的代码参见com.wj.hsqldb.listener.SpringContextListener里面的详细代码,没想到竟然报出以下错误:
rg.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.wj.hsqldb.db.JdbcConfiguration' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:354)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:345)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1126)
at com.wj.hsqldb.model.SpringContextListener.testDb(SpringContextListener.java:44)
at com.wj.hsqldb.model.SpringContextListener.contextInitialized(SpringContextListener.java:29)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4716)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5177)
- 问题分析
从上面的错误大体分析可能就是通过@Component、 @Autowired这些注解都是无效的,我突然间想起来@ComponentScan是有扫描范围的,是默认的扫描该类所在的包以及子包下的类,而我现在的项目结构如下:
而现在将SpringContextListener放到了com.wj.hsqldb.listener这个包下,怎么可能扫描到其他类呢?
- 解决方案:
将SpringContextListener类的@ComponentScan修改为@ComponentScan("com")。再次运行项目,成功出现了首页内容。
问题2:在HttpServlet中使用 @Autowired注解的实例,报"NullPointerException"
在验证完增删改查数据库部分没有错误之后,我陆续就开始添加HttpServlet代码,大体的思路就是实现代码的代码:
@WebServlet(name = "BookListServlet", value = "/booklist")
public class BookListController extends HttpServlet {
@Autowired
public BookManagerService bookManagerService;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<Book> books = bookManagerService.getBook();
System.out.println("book size = " + books.size());
req.setAttribute("book", books);
req.getRequestDispatcher("/book/booklist.jsp").forward(req, resp);
}
}
本来想着 现在都已经可以用Spring加载了所有的JavaBean,那么我就可以直接在HttpServlet中使用这些JavaBean了,没想到又报出下面的错误:
- 问题分析
这个问题很简单,就是bookManagerService没有实例化的,我就又在BookManagerService的初始化方法(用@PostConstruct标记)发现该JavaBean也被实例化了,但是为什么这HttpServlet就报空指针呢?
- 解决方案
后来又查各种资料,发现了原因本质:这些HttpServlet是在Servlet容器中实例化,而这些像BookManagerService等这些JavaBean是在Spring的IoC容器中实例化,所以在HttpServlet中是无法得到这个JavaBean实例的。我看到网上有些解决方案,就是复写HttpServlet的init(config)方法,在里面中添加如下代码:
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
ServletContext application = this.getServletContext();
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,application);
}
没想到这里又出现了让我意想不到的事情,竟然报出如下错误:
我这里马上想到了之前在总结小白新手web开发简单总结(二)-什么是web.xml里面的ContextLoaderListener的作用,这个ContextLoaderListener就是在web应用启动的时候,读取了contextConfigLocation指定的配置文件,自动装配了ApplicationContext,而这个ApplicationContext就是Spring的IoC,并且会将该ApplicationContext绑定到ServletContext中。所以特意还去研究了ContextLoaderListener的源码,见小白新手web开发简单总结(九)-ContextLoaderListener。
所以对于该问题2的终极解决方案就第一部分:在web.xml中引入ContextLoaderListener这个监听器。
<!--用来配置Spring的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/application-context.xml</param-value>
</context-param>
<!-- 用来创建Spring的IoC容器,并完成与ServletContext的绑定-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
单纯引入这个监听器并不能终极解决这个问题, 从源码中可以看到这些JavaBean默认的是通过配置文件进行配置的,我一开始通过配置文件增加的<bean>但是还是出现了这样那样的问题,而之前在最初接触Spring MVC的时候,就可以直接使用一些@Controller、@Autowired等注解,所以功夫不负有心人,终于让我找到了解决方案的第二部分:那就是还要在contextConfigLocation对应的配置文件中增加如下内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--可通过注解配置JavaBean-->
<context:annotation-config/>
<context:component-scan base-package="com.wj"/>
</beans>
此时就可以在非HttpServlet中使用 @Controller、@Autowired是没有问题了,但是还是没有解决在Serlvet容器中使用该JavaBean的问题,这就需要增加解决方案的第三部分:复写HttpServlet的init(config)方法,在里面中添加如下代码:
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
ServletContext application = this.getServletContext();
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this,application);
}
经过上面三部分终于解决了这个问题2:在HttpServlet中使用 @Autowired注解的实例,报"NullPointerException"。
2. EL表达式
问题1:c:foreach的使用
在使用c:foreach的时候一直无法实现预想的效果
- 问题原因
.jsp文件有个和Android开发不一样的东西,像在使用c:foreach,由于一开始没有引入tablib,所以在使用该标签的时候一直不起作用,但是也不报编译错误,但是项目就是运行不成功。
- 解决方案
后来又去查找各种资料,发现在.jsp文件中做list集合的循环就是使用c:foreach,后来才看到要使用这种c:foreach,需要引入tablib标签。
第一部分:在引入c标签,需要要在.jsp文件中添加如下代码:
<%--需要导入C标签该包--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
第二部分:要在pom.xml添加如下依赖:
<!-- 与jsp中的C标签相关的依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
通过这两部分的配置就可以在.jsp文件中使用 c:foreach。
问题2:使用${}一直无效
${} 在.jsp中可以轻松的获取到对应字段的内容,但是始终不能将对应的字段内容读取出来。例如之前在booklist.jsp有如下代码:
<%
List books = (List) request.getAttribute("book");
boolean isEmpty = books.isEmpty();
String display = isEmpty ? "none" : "block"; //display: ${display}
%>
<table>
<c:forEach items="${book}" var="it">
<tr style="color: red;alignment: center">
<td>${it.id}</td>
<td>${it.name}</td>
<td>${it.price}</td>
<td>${it.online}</td>
</tr>
</c:forEach>
</table>
- 问题分析
应该就是使用方式不对引起的。
- 解决方案
在BookListController跳转到该.jsp文件的时候,直接设置通过setAttribute设置到属性中。
req.setAttribute("book", books);
req.getRequestDispatcher("/book/booklist.jsp").forward(req, resp);
原来EL只能从request、session、page、application中获取属性,并且直接可以从写入四大域的key中直接读取内容。
(1)与域属性相关
requestScope:从request范围或者域属性空间中查找对应的key,如${requestScope.name }
sessionScope:从session范围或者域属性空间中查找对应的key,如${sessionScope.name }
pageScope:从pageScope范围或者域属性空间中查找对应的key,如${pageScope.name }
applicationScope:从application范围或者域属性空间中查处对应的key,如${applicationScope.name }
这些key对应的内容必须要通过setAttribute("key","value")的方式中写入对应的域中。
(2)其他的对象
pageContext:与jsp内置的pageContext是同一个对象。通过该对象,获取request、response、session、servletContext等对象,如可以直接通过${pageContext.request}获取reqeust对象;通过${pageContext.request.contextPath}获取的是web应用的根,这样就可以直接在后面添加跳转的路径即可,
<form action="${pageContext.request.contextPath}/addbook" method="get">
</form>
而在小白新手web开发简单总结(八)-数据库HSQLDB实例例子中获取的值为""。
param:获取的request的指定参数,等价于request.getParameter(),如${param.name}
paramValues:获取请求的指定参数的索引值,等价于request.getParameterValues()
EL表达式由于存在的局限性,所以经常可以直接通过在.jsp中添加java代码来实现这些逻辑。
3.JDBC
JDBC(Java DataBase Connectivity)。Java访问数据库的标准接口。我们的应用程序并不少直接访问数据库的,为了兼容不同的数据库厂商提供的数据库,都是通过JDBC接口来访问数据库。数据库的厂商提供JDBC驱动,这些JDBC驱动里面含有通过网络访问数据库的逻辑以及底层的网络通讯,这样web应用就可以直接通过JDBC接口来访问JDBC驱动,从而完成数据库的访问。
JDBC事务:数据库的事务都是有若干SQL语句构成一个操作序列。也就是说在某些业务要求,一系列的SQL语句必须全部执行,如果有一个SQL语句执行错误, 那么必须撤销所有的SQL,所以这些多条SQL语句作为一个整体进行操作被成为事务 。 数据库系统保证在一个事务中的所有SQL要么全部执行,要么全部都不执行。
二 总结
通过小白新手web开发简单总结(八)-数据库HSQLDB实例一个简单的实例,对一个web应用开发又有了一些新的认识:
1.一个web应用开发需要在web.xml中配置ContextLoaderListener用初始化Spring的IoC容器,并将该容器绑定到SerlvetContext中;
2.可以通过增加 <context:annotation-config/>的方式就可以通过注解的方式来加载和管理JavaBean,配置<context:component-scan base-package="com.wj"/>来设置扫描的包;
3.在HttpServlet中不能直接使用Spring实例化的JavaBean对象,因为是两个不同的东西,但可以通过SpringBeanAutowiringSupport.processInjectionBasedOnServletContext()将该注解与ServletContext绑定到一起;
4.EL表达式仅获取的是request、session、page、application设置的属性值;
5.@ComponentScan是有扫描范围的,可以直接通过@ComponentScan(“包名”)的方式来设置扫描包的范围
6.c标签不仅需要在jsp中引入,还需要添加依赖,才可以使用c标签
当然也遗留一些问题:
1. .jsp里面的文件有些还不是太规范需要以后在学习;
2.应该那些比较好的框架是来代替DbOperation、Book等这些类;
3.并没有关注数据库资源管理,像关闭链接等
4.在xml通过配置<bean>的方式加载和管理JavaBean的方式,还需要在验证下之前遇到的问题。