《精通Spring4.x企业应用开发实战》第二章
昨天联系了一下学长,学长说这个项目因为种种原因代码比较混乱,感觉最坏的打算是从头开始写。
大概询问了一下学长和xianhua学姐的建议,又看了看网上的资料,这个项目开发的技术栈基本就是SpringBoot + vue + D3,SpringBoot做后端的东西,vue写个前端的东西,D3用来做知识图谱那个图比较好。
数据库的话应该要用Neo4j,应该还要加一个关系数据库。
先花几天时间突击一下Spring,知乎上推荐这个书的比较多,源码先跑起来看看。废话不多说,从第二章开始看。
第二章主要要求做一个Spring的登录模块,首先要建立相应的数据库和表相关。
drop database if exists sampledb;
create database sampledb default character set utf8;
use sampledb; #创建用户表
create table t_user (
user_id int auto_increment primary key,
user_name varchar(30),
credits int,
password varchar(32),
last_visit datetime,
last_ip varchar(23)
)engine = InnoDB; #创建用户登录日志表
create table t_login_log (
login_log_id int auto_increment primary key,
user_id int,
ip varchar(23),
login_datatime datetime
)engine=InnoDB; #插入初始化数据
insert into t_user (user_name, password) values ('admin', '123456');
commit;
为了编码的统一,将IDEA设置成统一的utf8编码。
之后就是配置maven和创建项目的详细内容,由于这个博客的主要目的不在于详细记录每一个步骤(具体步骤可以参考书籍中内容),所以只对我觉得关键的步骤和内容进行笔记。
类包以分层的方式进行组织,共划分为4个包,分别是dao(持久层)、 domain(领域对象)、 service(业务层)和 web(展现层)。领域对象从严格意义上讲属于业务层,但由于领域对象可能同时被持久层和展现层共享,所以一般将其单独划分到一个包中
spring-context.xml是配置文件。
2.3持久层
2.3.1 建立领域对象
持久层负责数据的访问和操作,DAO类被上层的业务类调用。Spring 本身支持多种流行的ORM框架(第14章对此进行专门的讲解),这里使用Spring JDBC作为持久层的实现技术(关于Spring JDBC的详细内容,请参见第13章)。为了方便阅读,我们会对本章涉及的相关知识点进行必要的介绍,所以在不了解Spring JDBC的情况下,相信读者也可以轻松阅读本章的内容。
领域对象(Domain Object)也被称为实体类,它代表了业务的状态,且贯穿展现层、业务层和持久层,并最终被持久化到数据库中。领域对象使数据库表操作以面向对象的方式进行,为程序扩展带来了更大的灵活性。领域对象不一定等同于数据库表,不过对于简单的应用来说,领域对象往往拥有对应的数据库表。
持久层的主要工作就是从数据库表中加载数据并实例化领域对象,或将领域对象持久化到数据库表中。论坛登录模块需要涉及两个领域对象: User 和 LoginLog,前者代表用户信息,后者代表日志信息,分别对应t_user和t_login_log 数据库表,领域对象类的包为com.smart.domain。
2.3.2 UserDao
首先来定义访问User的 DAO,它包括3个方法。
getMatchCount():根据用户名和密码获取匹配的用户数。等于1表示用户名/密码正确;等于0表示用户名或密码错误(这是最简单的用户身份认证方法,在实际应用中需要采用诸如密码加密等安全策略)。
findUserByUserName():根据用户名获取User对象。
updateLoginInfo():更新用户积分、最后登录IP及最后登录时间。下面通过Spring JDBC技术实现这个DAO类,如代码所示。
@Repository
public class UserDao {
private JdbcTemplate jdbcTemplate; private final static String MATCH_COUNT_SQL = " SELECT count(*) FROM t_user " +
" WHERE user_name =? and password=? "; public int getMatchCount(String userName, String password) { return jdbcTemplate.queryForObject(MATCH_COUNT_SQL, new Object[]{userName, password}, Integer.class);
} ... @Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
这里说明了两个注释的作用@Repository //通过Spring 注解定义一个DAO、@Autowired//自动注入 JdbcTemplate的 Bean .
传统的JDBC API太底层,即使用户执行一条最简单的数据查询操作,都必须执行如下过程:获取连接→创建Statement→执行数据操作→获取结果→关闭Statement→关闭结果集→关闭连接,除此之外还需要进行异常处理的操作。如果使用传统的JDBC API进行数据访问操作,则可能会产生1/3以上单调乏味的重复性代码。
Spring JDBC对传统的JDBC API进行了薄层封装,将样板式的代码和那些必不可少的代码进行了分离,用户仅需要编写那些必不可少的代码,剩余的那些单调乏味的重复性工作则交由Spring JDBC框架处理。简单来说,Spring JDBC通过一个模板类org.springframework.jdbc.core.JdbcTemplate封装了样板式的代码,用户通过模板类就可以轻松地完成大部分数据访问操作。
如getMatchCount()方法,我们仅提供了一个查询SQL语句,直接调用模板的queryForInt()方法就可以获取查询,用户不用担心获取连接、关闭连接、异常处理等烦琐的事务。
@Repository
public class UserDao {
private JdbcTemplate jdbcTemplate;
private final static String UPDATE_LOGIN_INFO_SQL = " UPDATE t_user SET " +
" last_visit=?,last_ip=?,credits=? WHERE user_id =?"; public User findUserByUserName(final String userName) {
String sqlStr = " SELECT user_id,user_name,credits "
+ " FROM t_user WHERE user_name =? ";
final User user = new User();
jdbcTemplate.query(sqlStr, new Object[] { userName },
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
user.setUserId(rs.getInt("user_id"));
user.setUserName(userName);
user.setCredits(rs.getInt("credits"));
}
});
return user;
} public void updateLoginInfo(User user) {
jdbcTemplate.update(UPDATE_LOGIN_INFO_SQL, new Object[] { user.getLastVisit(),
user.getLastIp(),user.getCredits(),user.getUserId()});
} @Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
findUserByUserName()方法稍微复杂一些。这里,我们使用了JdbcTemplate#query()方法,该方法的签名为query(String sql, Object[]args, RowCallbackHandler rch),它有3个入参。
sqlStr:查询的SQL语句,允许使用带“?”的参数占位符。
args: SQL语句中占位符对应的参数数组。
RowCallbackHandler:查询结果的处理回调接口,该回调接口有一个方法
processRow(ResultSet rs),负责将查询的结果从ResultSet装载到类似于领域对象的对象实例中。
findUserByUserName()通过匿名内部类的方式定义了一个RowCallbackHandler回调接口实例,将ResultSet 转换为User对象。
updateLoginInfo()方法比较简单,主要通过JdbcTemplatc#update(String sql,Object[])进行数据的更新操作。
2.3.4在 Spring 中装配DAO
在编写DAO接口的实现类时,大家也许会有一个问题:在以上两个DAO 实现类中都没有打开/释放Connection的代码,DAO类究竟如何访问数据库呢﹖前面说过,样板式的操作都被JdbcTemplate封装起来了,JdbcTemplate本身需要一个DataSource,这样它就可以根据需要从DataSource中获取或返回连接。UserDao和 LoginLog 都提供了一个带@Autowired注解的JdbcTemplate变量,所以我们必须事先声明一个数据源,然后定义一个JdbcTemplate Bean,通过Spring 的容器上下文自动绑定机制进行Bean的注入。
在项目工程的src\resources(在Maven工程中,资源文件统一放置在resources文件夹中)目录下创建一个名为smart-context.xml的Spring配置文件,该配置文件的基本结构如下:
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
...
</beans>
<?xml version="1.0" encoding="UTF-8" ?>
<beans ...> <!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
<context:component-scan base-package="com.smart.dao"/>
<context:component-scan base-package="com.smart.service"/> <!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/sampledb"
p:username="root"
p:password="123456" /> <!-- 配置Jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource" /> </beans>
我们使用Spring 的<context:component-scan>扫描指定类包下的所有类,这样在类中定义的Spring注解(如@Repository、@Autowired等)才能产生作用。
我们使用Jakarta 的 DBCP开源数据源实现方案定义了一个数据源,数据库驱动器类为com.mysql.jdbc.Driver。
配置了JdbcTemplate Bean,将声明的dataSource注入JdbcTemplate 中,而这个JdbcTemplate Bean将通过@Autowired自动注入LoginLog和UserDao 的Bean中,可见Spring可以很好地将注解配置和XML配置统一起来。
这样就完成了登录模块持久层所有的开发工作,接下来将着手业务层的开发和配置工作。我们将对业务层的业务类方法进行单元测试,到时就可以看到DAO的实际运行效果了,现在暂时把这两个DAO放在一边。
总结一下,访问User的 DAO,它包括3个方法:getMatchCount(),findUserByUserName(),updateLoginInfo(),LoginLogDao负责记录用户的登录日志,它仅有一个insertLoginLog()接口方法。
2.4业务层
在论坛登录实例中,业务层仅有一个业务类,即 UserService。UserService负责将持久层的UserDao和LoginLoginDao组织起来,完成用户/密码认证、登录日志记录等操作。
2.4.1 UserService
UserService 业务接口有3个业务方法,其中,hasMatchUser()方法用于检查用户名/密码的正确性;findUserByUserName()方法以用户名为条件加载User对象;loginSuccess(方法在用户登录成功后调用,更新用户最后登录时间和P信息,同时记录用户登录日志。
下面我们来实现这个业务类。UserService的实现类需要调用DAO层的两个DAO完成业务逻辑操作,如代码所示。
@Service
public class UserService {
private UserDao userDao;
private LoginLogDao loginLogDao; public boolean hasMatchUser(String userName, String password) {
int matchCount =userDao.getMatchCount(userName, password);
return matchCount > 0;
} public User findUserByUserName(String userName) {
return userDao.findUserByUserName(userName);
} @Transactional
public void loginSuccess(User user) {
user.setCredits( 5 + user.getCredits());
LoginLog loginLog = new LoginLog();
loginLog.setUserId(user.getUserId());
loginLog.setIp(user.getLastIp());
loginLog.setLoginDate(user.getLastVisit());
userDao.updateLoginInfo(user);
loginLogDao.insertLoginLog(loginLog);
} @Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
} @Autowired
public void setLoginLogDao(LoginLogDao loginLogDao) {
this.loginLogDao = loginLogDao;
}
}
首先通过@Service注解将UserService标注为一个服务层的Bean;然后通过@Autowired注入userDao和 loginLogDao 这两个DAO层的Bean;接着通过 hasMatchUser()和findUserByUserName()业务方法简单地调用DAO 完成对应的功能;最后为loginSuccess()方法标注@Transactional事务注解,让该方法运行在事务环境中(因为我们在 Spring事务管理器拦截切入表达式上加入了@Transactional过滤),否则该方法将在无事务方法中运行。loginSuccess()方法根据入参user对象构造出 LoginLog 对象并将user.credits递增5,即用户每登录一次赚取5个积分,然后调用userDao更新到t_user中,再调用loginLogDao向t_login_log表中添加一条记录。
loginSuccess()方法将两个DAO组织起来,共同完成一个事务性的数据操作:更新t_user表记录并添加t_login_log表记录。但我们从UserService中却看不出任何事务操作的影子,这正是Spring的高明之处,它让我们从事务操作单调、机械的代码中解脱出来,专注完成那些不可或缺的业务工作。通过Spring声明式事务配置即可让业务类享受EJB声明式事务的好处,下一节我们将了解如何赋予业务类事务管理的能力。
2.4.2 在 Spring 中装配Service
事务管理的代码虽然无须出现在程序代码中,但我们必须以某种方式告诉Spring哪些业务类需要工作在事务环境下及事务的规则等内容,以便Spring根据这些信息自动为目标业务类添加事务管理的功能。
打开原来的smart-context.xml 文件,进行如代码所示的更改。
<?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:p="http://www.springframework.org/schema/p"
<!-- 1 -- >
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
<context:component-scan base-package="com.smart.dao"/>
<!-- 2 -->
<context:component-scan base-package="com.smart.service"/>
...
<!-- 配置事务管理器 -->
<!-- 3 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource" />
<!-- 4 -->
<!-- 通过AOP配置提供事务增强,让service包下所有Bean的所有方法拥有事务 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceMethod"
expression="(execution(* com.smart.service..*(..))) and (@annotation(org.springframework.transaction.annotation.Transactional))" />
<aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>
</beans>
①处在<beans>的声明处添加 aop和 tx命名空间的Schema定义文件的说明,这样,在配置文件中就可以使用这两个空间下的配置标签了。
②处将com.smart.service添加到上下文扫描路径中,以便使service包中类的Spring注解生效。
③处定义了一个基于数据源的DataSourceTransactionManager事务管理器,该事务管理器负责声明式事务的管理。该管理器需要引用dataSource Bean。
④处通过aop及tx命名空间的语法,以AOP的方式为com.smart.service包下所有类的所有标注@Transactional注解的方法都添加了事务增强,即它们都将工作在事务环境中(关于Spring事务的配置,详见第11章)。
这样就完成了业务层的程序开发和配置工作,接下来需要对该业务类进行简单的单元测试,以便检验业务方法的正确性。
2.4.3单元测试
TestNG和JUnit 相比有了重大的改进,本书示例所有的单元测试统一采用TestNG框架。请确保已经将TestNG依赖包添加到根模块pom.xml文件中。在 chapter2\srcltest测试目录下创建与UserService一致的包结构,即com.smart.service,并创建UserService对应的测试类UserServiceTest,编写如代码所示的测试代码。
@ContextConfiguration("classpath*:/smart-context.xml")
public class UserServiceTest extends AbstractTransactionalTestNGSpringContextTests { @Autowired
private UserService userService; @Test
public void testHasMatchUser() {
boolean b1 = userService.hasMatchUser("admin", "123456");
boolean b2 = userService.hasMatchUser("admin", "1111");
assertTrue(b1);
assertTrue(!b2);
} @Test
public void testFindUserByUserName()throws Exception{
for(int i =0; i< 100;i++) {
User user = userService.findUserByUserName("admin");
assertEquals(user.getUserName(), "admin");
} } @Test
public void testAddLoginLog() {
User user = userService.findUserByUserName("admin");
user.setUserId(1);
user.setUserName("admin");
user.setLastIp("192.168.12.7");
user.setLastVisit(new Date());
userService.loginSuccess(user);
}
}
Spring 4.0的测试框架很好地整合了TestNG单元测试框架,示例UserServiceTest通过扩展Spring测试框架提供测试基类 AbstractTransactionalTestNGSpringContextTests来启动测试运行器。 @ContextConfiguration也是Spring 提供的注解,用于指定Spring的配置文件。
可以使用Spring 的@Autowired 将Spring容器中的Bean注入测试类中。在测试方法前通过TestNG的@Test注解即可将方法标注为测试方法
在 IDEA 中执行当前测试类,通过右键菜单Run ‘UserServiceTest'来运行该测试用例,以检验业务类方法的正确性。
2.5展现层
业务层和持久层的开发任务已经完成,该是为程序提供界面的时候了。Spring4.0对MVC进行了全面增强,支持跨域注解@CrossOrigin 配置,Groovy Web集成,Gson、Jackson、Protobuf的 HttpMessageConverter消息转换器等,Spring MVC的功能更加丰富、强大(读者将在第18章学习到Spring MVC的详细内容)。
2.5.1配置Spring MVC框架
首先需要对web.xml文件进行配置,以便Web容器启动时能够自动启动Spring容器,如代码所示。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- 1-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:smart-context.xml</param-value>
</context-param>
<!-- 2-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- 3 -->
<servlet>
<servlet-name>smart</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
<!-- 4 -->
<servlet-mapping>
<servlet-name>smart</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
然后通过Web容器上下文参数指定Spring 配置文件的地址,如①所示。多个配置文件可用逗号或空格分隔,建议采用逗号分隔的方式。然后在②处指定Spring所提供的ContextLoaderListener的Web容器监听器,该监听器在 Web容器启动时自动运行,它会根据contextConfigLocation Web容器参数获取 Spring配置文件,并启动Spring容器。注意,需要将log4J.propertis日志配置文件放置在类路径下,以便日志引擎自动生效。
最后需要配置Spring MVC 相关的信息。Spring MVC像 Struts一样,也通过一个Servlet来截获URL 请求,然后再进行相关的处理.
在3处声明了一个 Servlet, Spring MVC 也拥有一个Spring配置文件(稍后将涉及),该配置文件的文件名和此处定义的Servlet名有一个契约,即使用<Servlet 名>-servlet.xml的形式。在这里,Servlet名为smart,则在/WEB-INF目录下必须提供一个名为smart-servlet.xml 的Spring MVC配置文件,但这个配置文件无须通过web.xml的contextConfigLocation上下文参数进行声明,因为Spring MVC 的 Servlet会自动将smart-servlet.xml文件和Spring 的其他配置文件(smart-dao.xml、smart-service.xml)进行拼装。
在4处对这个Servlet的URL路径映射进行定义,在这里让所有以.html为后缀的URL都能被smart Servlet截获,进而转由Spring MVC框架进行处理。我们知道,在Struts框架中一般将URL后缀配置为*.do,而在 WebWork 中一般配置为*.action。其实,框架本身和URL模式没有任何关系,用户大可使用喜欢的任何后缀。使用.html后缀,一方面,用户不能通过URL直接知道我们采用了何种服务器端技术;另一方面,.html是静态网页的后缀,可以骗过搜索引擎,增加被收录的概率,所以我们推荐采用这种后缀。对于那些真正无须任何动态处理的静态网页,则可以使用.htm后缀加以区分,以避免被框架截获。
请求被Spring MVC截获后,首先根据请求的URL查找到目标的处理控制器,并将请求参数封装“命令”对象一起传给控制器处理;然后,控制器调用Spring容器中的业务Bean完成业务处理工作并返回结果视图。
2.5.2处理登录请求
1. POJO控制器类。
首先需要编写的是LoginController,它负责处理登录请求,完成登录业务,并根据登录成功与否转向欢迎页面或失败页面。
//1
@Controller
public class LoginController{
private UserService userService;
//2
@RequestMapping(value = "/index.html")
public String loginPage(){
return "login";
}
//3
@RequestMapping(value = "/loginCheck.html")
public ModelAndView loginCheck(HttpServletRequest request,LoginCommand loginCommand){
boolean isValidUser = userService.hasMatchUser(loginCommand.getUserName(),
loginCommand.getPassword());
if (!isValidUser) {
return new ModelAndView("login", "error", "用户名或密码错误。");
} else {
User user = userService.findUserByUserName(loginCommand
.getUserName());
user.setLastIp(request.getLocalAddr());
user.setLastVisit(new Date());
userService.loginSuccess(user);
request.getSession().setAttribute("user", user);
return new ModelAndView("main");
}
} @Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
在①处通过Spring MVC的@Controller注解可以将任何一个POJO的类标注为Spring MVC的控制器,处理HTTP的请求。当然,标注了@Controller 的类首先会是一个Bean,所以可以使用@Autowired进行 Bean的注入。
一个控制器可以拥有多个处理映射不同HTTP请求路径的方法,通过@RequestMapping指定方法如何映射请求路径,如②和③处所示。
请求参数会根据参数名称默认契约自动绑定到相应方法的入参中。例如,在③处的loginCheck(HttpServletRequest request,LoginCommand loginCommand)方法中,请求参数会按名称匹配绑定到loginCommand 的入参中。
请求响应方法可以返回一个ModelAndView,或直接返回一个字符串,Spring MVC会解析之并转向目标响应页面。ModelAndView对象既包括视图信息,又包括视图渲染所需的模型数据信息。在这里用户仅需要了解它代表一张视图即可,在后面的内容中,读者将了解到Spring MVC如何根据这个对象转向真正的页面。前面用到的LoginCommand对象是一个POJO,没有继承特定的父类或实现特定的接口。LoginCommand 类仅包括用户/密码这两个属性(和请求的用户/密码参数名称一样)。
在代码的②和③处,控制器根据登录处理结果分别返回 ModelAndView("login" , " error", "用户名或密码错误。")和 ModelAndView("main")。ModelAndView的第一个参数代表视图的逻辑名,第二、第三个参数分别为数据模型名称和数据模型对象,数据模型对象将以数据模型名称为参数名放置到request的属性中。
编写好LoginCommand后,需要在smart-servlet.xml中声明该控制器,扫描Web路径,指定Spring MVC的视图解析器。
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/p ">
<!-- 扫描web包,应用Spring的注解 -->
<context:component-scan base-package="com.smart.web"/> <!-- 配置视图解析器,将ModelAndView及字符串解析为具体的页面 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp"/> </beans>
Spring MVC为视图名到具体视图的映射提供了许多可供选择的方法。在这里,我们使用InternalResourceViewResolver,它通过为视图逻辑名添加前、后缀的方式进行解析。如视图逻辑名为“login”,将解析为/WEB-INFjsp/login.jsp;视图逻辑名为“main”,将解析为/WEB-INF/jsp/main.jsp。
2.5.3JSP视图页面
论坛登录模块共包括两个JSP页面,分别是登录页面login.jsp和欢迎页面main.jsp,我们将在这节完成这两个页面的开发工作。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>小春论坛登录</title>
</head>
<body>
<!-- 1 -->
<c:if test="${!empty error}">
<font color="red"><c:out value="${error}" /></font>
</c:if>
<!-- 2 -->
<form action="<c:url value="loginCheck.html"/>" method="post">
用户名:
<input type="text" name="userName">
<br>
密 码:
<input type="password" name="password">
<br>
<input type="submit" value="登录" />
<input type="reset" value="重置" />
</form>
</body>
</html>
login.jsp页面有两个用处,既作为登录页面,又作为登录失败后的响应页面。所以在①处,使用JSTL标签对登录错误返回的信息进行处理。在JSTL标签中引用了error变量,这个变量正是LoginController中返回的ModelAndView("login" , " error", "用户名或密码错误。")对象所声明的error参数。
login.jsp的登录表单提交到/loginController.html,如②所示。<c:url value="/loginController.html"/>的JSTL标签会在URL 前自动加上应用部署根目录。假设应用部署在网站的 bbt目录下,则<c:url>标签将输出/bbt/loginController.html。通过<c:url>标签很好地解决了开发和应用部署目录不一致的问题。
由于login.jsp放置在 WEB-INF/jsp目录下,无法直接通过URL进行调用,所以它由LoginController控制类中标注了@RequestMapping(value= "/index.html")的 loginPage()进行转发.
《精通Spring4.x企业应用开发实战》第二章的更多相关文章
- 02.第二章_C++ Primer学习笔记_变量和基本类型
2.1 基本内置类型 2.1.1 算术类型 算术类型包括两类:整型和浮点型 2.2 变量 2.3 复合类型 2.4 const限定符 2.5 处理类型 2.6 自定义数据结构
- C++ Primer 笔记 第二章
C++ Primer 第二章 变量和基本类型 2.1基本内置类型 有算数类型和void类型:算数类型储存空间大小依及其而定. 算数类型表: 类型 含义 最小储存空间 bool 布尔型 - char 字 ...
- 《C++ Primer》读书笔记—第二章 变量和基本类型
声明: 文中内容收集整理自<C++ Primer 中文版 (第5版)>,版权归原书所有. 学习一门程序设计语言最好的方法就是练习编程. 1.8比特的char类型计算机表示的实际范围是-12 ...
- C++primer拾遗(第二章:变量和基本类型)
这是我对c++primer第二章的一个整理总结,算是比较适用于我自己吧,一小部分感觉不用提及的就省略了,只提了一下平时不注意,或者不好记住的内容. 排版太费劲了,直接放了图片格式.从自己的oneNot ...
- 《C++Primer》第五版习题答案--第二章【学习笔记】
C++Primer第五版习题解答---第二章 ps:答案是个人在学习过程中书写,可能存在错漏之处,仅作参考. 作者:cosefy Date: 2020/1/9 第二章:变量和基本类型 练习2.1: 类 ...
- 《C Primer Plus》- 第二章 C语言概述
本笔记写于2020年1月27日. 本系列文章参考的是<C Primer Plus>(第六版),其中里面会有笔者自己的相关补充. 以下示例均运行于macOS Catalina 10.15.2 ...
- 逆向基础 C++ Primer Plus 第二章 开始学习C++
C++ Primer Plus 第二章 开始学习C++ 知识点梳理 本章从一个简单的C++例子出发,主要介绍了创建C++程序的步骤,以及其所包含的预处理器编译指令.函数头.编译指令.函数体.注释等组成 ...
- C++PRIMER第二章前半部分答案
C++PRIMER第二章前半部分答案 哈哈哈,为什么是前半部分呢,后半部分还在学习中,重新系统性的学习c++,共同进步嘛,不多说,跟我一起来看看吧,第三章开始才是新手收割的时候,慢慢来~~ 2.1&a ...
- C++ Primer 笔记(2)第二章 变量与基本类型
第二章 变量与基本类型 1.基本内置类型包括算术类型和空类型,算术类型分为两类:整型(包括字符和布尔类型)和浮点型: 2.布尔类型(bool)的取值是真(true)或者假(false): 3.字面值常 ...
- C++ primer的第二章的主要内容
这第二章主要是介绍了C++中基本的内置数据类型:整型与浮点型.介绍了什么是变量的过程中了解到了左值与右值的概念.左值是可以出现在赋值语句的左边或者右边,也就是说可以放在等号的左右两边,而右值只能是出现 ...
随机推荐
- Morris遍历
Morris遍历 一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1) 通过利用原树中大量空闲指针的方式,达到节省空间的目的 Morris遍历可以改前中后序的树遍历 思路: 创建一个当 ...
- linux下的echo
echo命令用于在shell中打印shell变量的值,或者直接输出指定的字符串.linux的echo命令,在shell编程中极为常用, 在终端下打印变量value的时候也是常常用到的,因此有必要了解下 ...
- 在阿里云上搭建私有GIT仓库
在阿里云上搭建私有GIT仓库 年轻人就得好好学习,不能这么颓废 最近做项目练练手,用到了github, 但是github访问速度是真的慢啊,下载项目,下载一天了.所以呢,我是个成熟的人了,只好自己搭建 ...
- Vue:Vue-Cli 实现的交互式的项目脚手架
一.这份文档是对应 @vue/cli.老版本的 vue-cli 文档请移步https://github.com/vuejs/vue-cli/tree/v2#vue-cli-- Vue CLI 是一个基 ...
- shell-变量的数值运算let内置命令
1. let命令的用法 格式: let 赋值表达式 [注]let赋值表达式功能等同于:((赋值表达式)) 范例1:给自变量i加8 [root@1-241 scripts]# i=2 [root@1- ...
- 《Android逆向反编译代码注入》 - 逆向安全入门必看视频教程
适合人群: Android开发人员.逆向反编译开发人员.以及对Android逆向安全感兴趣的朋友. 视频地址: 51CTO学院:https://edu.51cto.com/course/24485 ...
- kali linux 换国内源
输入命令 vim /etc/apt/sources.list 添加国内源 #中科大deb http://mirrors.ustc.edu.cn/kali kali-rolling main non-f ...
- 如何把C++的源代码改写成C代码?而C改C++只需一步!
★ 如何把C++的源代码改写成C代码? C++解释器比C语言解释器占用的存储空间要大,想要在某些特定场合兼容C++代码,同时为了节省有限的存储空间,降低成本,也为了提高效率,将用C++语言写的源程序用 ...
- JAVA 基于Jusup爬虫
java爬虫核心:httpclient slf4j jsoup slf4j 配置文件log4j.properties log4j.rootlogger=DEBUG,A1log4j.logger.cn. ...
- thinkphp数组给js赋值 tp模板把数组赋值给js变量
var arr=transArr({$array|json_encode=true}); function transArr(obj) { var tem=[]; $.each(obj, functi ...