《精通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()进行转发.
标签:登录,Spring,企业应用,MVC,user,第二章,public,Spring4,smart 来源: https://www.cnblogs.com/hellostranger/p/13947360.html