在有关Spring Roo的本系列的第1部分中 ,我们使用Spring Roo构建了一个小型企业会议应用程序。 在这里,我们将使用Spring Roo将简单的CRUD Web应用程序扩展为成熟的企业应用程序。 在开始之前,请确保您已安装Spring Roo并下载了SpringSource Tool Suite(更多信息,请参见第1部分)。
让我们开始吧
要扩展会议应用程序,我们需要从第1部分重新创建该应用程序。您可以按照前面的说明进行操作,也可以使用Roo的script
命令。 script
执行资源文件中指定的所有命令。 如果遵循第1部分,您会注意到Roo创建了一个名为log.roo的文件,其中包含在Roo Shell上触发的所有命令。 我们将执行该log.roo文件并重新创建应用程序。
- 此文件包含在示例代码中。 您可以将其重命名为Conference.roo。
- 创建一个名为会议的新目录,并在其中复制conference.roo。
- 打开您的操作系统命令行外壳。
- 进入刚刚创建的会议目录。
-
script --file conference.roo
命令。
如果您的Maven存储库中有必需的JAR,则script
将在几秒钟内重新创建应用程序。 否则,将需要更多时间,因为它必须下载所有JAR。 script
命令很有用,因为您可以将其用作模板来创建Spring管理的项目。
在继续之前,请在STS中导入Maven项目。 STS预先捆绑了Maven Eclipse插件 。 您应该通过选择文件>导入> Maven>现有Maven项目来导入项目 ,然后选择项目的目录。 我们将在STS中导入该项目,因为稍后将编写一些自定义代码。
到目前为止,我们已经创建了该Web应用程序,并且可以通过创建,读取,更新和删除Speaker and Talk实体来对其进行手动测试。 但是,如果我们可以自动化这个过程,那不是很好吗?
自动化的网络测试
这是Spring Roo的下一个功能:Selenium测试支持。 Selenium是一组功能强大的工具,可支持针对基于Web的应用程序快速开发测试自动化。 要将Selenium测试支持添加到您的应用程序,请执行以下命令:
selenium test --controller ~.web.SpeakerController
selenium test --controller ~.web.TalkController
selenium test
命令将为扬声器和Talk控制器创建Selenium测试。 该命令具有一个称为controller的必需属性,用于指定用于创建Selenium测试的控制器的名称。 此命令还有两个名为name
和serverUrl
可选属性,用于指定Selenium测试的名称以及Web应用程序可用的服务器。 当您启动selenium test
命令时,Spring Roo还将添加Selenium Maven插件。
上面,我们为控制器创建了Selenium测试用例,但是在运行它们之前,我们需要修复Spring Roo创建的Selenium测试套件中的一个小缺陷。 我们在说话者实体中添加了年龄应该在25到60之间的约束,但测试套件未考虑该约束。 它使用age值为1,因此测试将失败。 我们需要修改一个名为test-speaker.xhtml的文件并更新该部分,如清单1所示。
清单1.修改test-speaker.xhtml
<tr>
<td>type</td>
<td>_age_id</td>
<td>1</td>
</tr>
至
<tr>
<td>type</td>
<td>_age_id</td>
<td>26</td>
</tr>
此缺陷将在Spring Roo的将来版本中修复。
要运行Selenium测试用例,我们需要启动Tomcat服务器。 您可以使用Maven命令mvn tomcat:run
启动它。 默认情况下,所有使用Roo创建的Web应用程序都具有适用于Tomcat和Jetty Web服务器的Maven插件。 要运行selenium test
,请执行Maven命令mvn selenium:selenese
。
这将启动Firefox浏览器来运行Selenium测试用例。 在执行测试期间,您应该看到类似于图1的图像。
图1.Selenium测试
![执行Selenium Tests的工具的屏幕快照,左上角有脚步,右上角是用于运行测试的控制面板,下半部是正在运行的应用程序。](https://i-blog.csdnimg.cn/blog_migrate/04fdaaa2c67a717457b9fd3ae03f1207.png)
目前,任何人都可以访问我们的应用程序,并在Speaker and Talk上执行创建,更新和删除操作。 在实时应用程序中,存在谁可以执行哪个操作的安全性。
保护Web应用程序
Roo使用Spring Security在一行中为您的应用程序添加安全性。 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。 它是用于保护基于Spring的应用程序的实际标准。
增加Spring安全性
要添加Spring Security,请输入以下命令: security setup
。
该命令将添加所有必需的Spring Security JAR,并将为您的应用程序设置基本安全性。 该命令还会创建其他文件,但重要的是applicationContext-security.xml,其中包含与安全性相关的所有bean定义。 applicationContext-security.xml上下文看起来像清单2中的内容。我已经用点替换了散列的密码,以使其更具可读性。
清单2. applicationContext-security.xml的上下文
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans \
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security \
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<!-- HTTP security configurations -->
<http auto-config="true" use-expressions="true">
<form-login login-processing-url="/resources/j_spring_security_check" \
login-page="/login" authentication-failure-url="/login?login_error=t"/>
<logout logout-url="/resources/j_spring_security_logout"/>
<!-- Configure these elements to secure URIs in your application -->
<intercept-url pattern="/choices/**" access="hasRole('ROLE_ADMIN')"/>
<intercept-url pattern="/member/**" access="isAuthenticated()" />
<intercept-url pattern="/resources/**" access="permitAll" />
<intercept-url pattern="/**" access="permitAll" />
</http>
<!-- Configure Authentication mechanism -->
<authentication-manager alias="authenticationManager">
<!-- SHA-256 values can be produced using \
'echo -n your_desired_password | sha256sum' \
(using normal *nix environments) -->
<authentication-provider>
<password-encoder hash="sha-256"/>
<user-service>
<user name="admin" password="..." authorities="ROLE_ADMIN"/>
<user name="user" password="..." authorities="ROLE_USER"/>
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
Roo配置的安全性是通用的,没有引用我们的应用程序。 请记住,Roo帮助您快速设置或配置应用程序,但是定制最终产品是开发人员的责任。 在这种情况下,Roo仅提供了Spring Security的模板,我们有责任根据需要定制它。
自定义Spring Security
在我们的应用程序中,任何人都可以创建发言人,但是只有发言人可以创建演讲。 我们需要修改applicationContext-security.xml,如下所示。 清单3仅显示了需要修改的XML部分。
清单3.修改applicationContext-security.xml
<http auto-config="true" use-expressions="true">
<form-login login-processing-url="/resources/j_spring_security_check" \
login-page="/login" authentication-failure-url="/login?login_error=t"/>
<logout logout-url="/resources/j_spring_security_logout"/>
<!-- Configure these elements to secure URIs in your application -->
<intercept-url pattern="/talks/**" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/speakers/**" access="permitAll" />
<intercept-url pattern="/resources/**" access="permitAll" />
<intercept-url pattern="/**" access="permitAll" />
</http>
我已经更新了intercept-url
以便只有具有角色用户的用户才能创建“对话”,并且允许所有用户将自己注册为发言人。
上面显示的Roo生成的Spring Security使用在 <user-service>
标记下配置的内存中身份验证提供程序。 由于我们的应用程序管理发言人实体,因此我们应该构建一个使用发言人数据的自定义身份验证提供程序。 为了进行身份验证,我们将使用发言人电子邮件作为用户名,并向发言人实体添加密码字段,我们将其用作身份验证密码。
再次,我使用Roo shell将密码字段添加到Speaker实体:
field string --class ~.domain.Speaker --fieldName password --notNull --sizeMin 6 –sizeMax
10
我还添加了一个约束,即密码不应为null,密码长度应在6到10个字符之间。
由于我们使用电子邮件和密码作为身份验证参数,因此我们希望找到给定电子邮件和密码的发言人。 Spring Roo提供了一种使用finder add
命令为您的应用程序创建查找finder add
:
finder add --finderName findSpeakersByEmailAndPasswordEquals --class ~.domain.Speaker
您可以使用finder list
命令finder list
实体的所有finder list
。 finder add
将finder代码写入Speaker_Roo_Finder.aj文件,并写入一些与视图相关的文件。 这使您可以从GUI搜索发言人。
编写自定义AuthenticationProvider
我们将通过扩展名为AbstractUserDetailsAuthenticationProvider
的类来编写自定义身份验证提供程序,该类可与用户名/密码(如身份验证)一起使用。 扩展AbstractUserDetailsAuthenticationProvider
的类必须为其两种抽象方法提供实现: additionalAuthenticationChecks
和retrieveUser
。 提供者使用输入的电子邮件和密码来调用retrieveUser
方法以验证发言人。 发言人的数据库查找是使用我们在上面创建的查找器完成的。 如果找到发言人,则将GrantedAuthority ROLE_USER
分配给发言人。 如果登录成功,该方法最终将返回一个填充的UserDetails
对象,否则,将抛出BadCredentialsException
和相应的消息(请参见清单4)。
清单4.自定义身份验证
package com.dw.roo.conference.security;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityNotFoundException;
import javax.persistence.NonUniqueResultException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.\
dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import com.dw.roo.conference.domain.Speaker;
public class ConferenceAuthenticationProvider extends \
AbstractUserDetailsAuthenticationProvider {
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, \
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// TODO Auto-generated method stub
}
@Override
protected UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken \
authentication) throws AuthenticationException {
String password = (String) authentication.getCredentials();
if (!StringUtils.hasText(password)) {
throw new BadCredentialsException("Please enter password");
}
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
try {
Speaker speaker = Speaker.findSpeakersByEmailAndPasswordEquals(username, \
password).getSingleResult();
authorities.add(new GrantedAuthorityImpl("ROLE_USER"));
} catch (EmptyResultDataAccessException e) {
throw new BadCredentialsException("Invalid username or password");
} catch (EntityNotFoundException e) {
throw new BadCredentialsException("Invalid user");
} catch (NonUniqueResultException e) {
throw new BadCredentialsException("Non-unique user, contact administrator");
}
return new User(username, password, true, // enabled
true, // account not expired
true, // credentials not expired
true, // account not locked
authorities);
}
}
在applicationContext-security.xml中,我们必须定义conferenceAuthenticationProvider
bean,并将Roo生成的内存中身份验证提供程序替换为我们的conferenceAuthenticationProvider
如清单5所示。
清单5. conferenceAuthenticationProvider
<beans:bean name="conferenceAuthenticationProvider"
class="com.dw.roo.conference.security.ConferenceAuthenticationProvider">
</beans:bean>
<!-- Configure Authentication mechanism -->
<authentication-manager alias="authenticationManager">
<authentication-provider ref="conferenceAuthenticationProvider"/>
</authentication-manager>
现在,如果您启动服务器并尝试创建“对话”,将显示一个登录屏幕,您必须在其中输入创建的发言人的电子邮件和密码。 构建定制身份验证提供程序的目的不是要构建理想的身份验证提供程序,而是向您展示Roo的工作以及您自己必须做的事情。
电子邮件通知
在我们的应用程序中,演讲者在创建Talk时应该会收到一封电子邮件,因此让我们为我们的应用程序添加电子邮件支持。 我们将使用Gmail作为SMTP服务器,专注于使用Roo发送电子邮件。 使用以下命令向应用程序添加电子邮件支持:
email sender setup --hostServer smtp.gmail.com --username \
<Your email address> --password <Your email password> --port 587 --protocol SMTP
email sender命令在您的项目中安装Spring JavaMailSender
。 您可以在email.properties文件中更改与电子邮件相关的属性。
创建对话后,我们需要发送电子邮件。 为此,我们需要在TalkController
添加一个电子邮件字段。 要添加电子邮件字段,请输入:
field email template --class ~.web.TalkController
这会将MailSender
模板和sendMessage
方法添加到TalkController
。 现在,在将Talk sendMessage
在数据库中之后,我们需要触发sendMessage
方法。 由于所有的代码TalkController
存在TalkController_Roo_Controller.aj文件,完成这一任务的最简单的方法是创建一个encodeUrlPathSegment
从.aj文件的方法TalkController
类,并添加到通话sendMessage
后方法talk.persist()
行,如清单6所示。
清单6.创建从.aj文件到TalkController
的encodeUrlPathSegment
方法
public class TalkController {
@Autowired
private transient MailSender mailTemplate;
public void sendMessage(String mailFrom, String subject, String mailTo,
String message) {
org.springframework.mail.SimpleMailMessage \
simpleMailMessage = new org.springframework.mail.SimpleMailMessage();
simpleMailMessage.setFrom(mailFrom);
simpleMailMessage.setSubject(subject);
simpleMailMessage.setTo(mailTo);
simpleMailMessage.setText(message);
mailTemplate.send(simpleMailMessage);
}
@RequestMapping(method = RequestMethod.POST)
public String create(@Valid Talk talk, BindingResult result, Model model,
HttpServletRequest request) {
if (result.hasErrors()) {
model.addAttribute("talk", talk);
return "talks/create";
}
talk.persist();
sendMessage("spring.roo.playground@gmail.com", "Your talk is created",
talk.getSpeaker().getEmail(), \
"Congrats your talk is created");
return "redirect:/talks/"
+ encodeUrlPathSegment(talk.getId().toString(), request);
}
private String encodeUrlPathSegment(String pathSegment,
HttpServletRequest request) {
String enc = request.getCharacterEncoding();
if (enc == null) {
enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
}
try {
pathSegment = UriUtils.encodePathSegment(pathSegment, enc);
} catch (UnsupportedEncodingException uee) {
}
return pathSegment;
}
}
此后,演讲者在创建对话后将在其指定的帐户中收到一封电子邮件。
国际化支持
在构建基于Internet的Web应用程序时,重要的是要支持不同的语言,以便来自不同地区的用户都可以使用我们的应用程序。 Spring Roo通过使用web mvc install language
命令添加了国际化支持,该命令将在您的应用程序中安装新的语言。 例如,西班牙语和意大利语的命令为:
web mvc install language --code es
web mvc install language --code it
Roo当前支持六种语言,并且您可以为您选择的其他语言编写附加语言。 现在,当应用程序运行时,将显示两个标志(意大利和西班牙)以及英国标志。 如果单击这些标志中的任何一个,则将以与该标志相对应的语言查看Web应用程序。
社交化您的Web应用程序
这是社交媒体的时代,社交功能通常添加到当前应用程序中。 添加Talks视频是有意义的。 Roo提供对嵌入上传到YouTube,Vimeo,Viddler和Google Video等的视频的支持。要嵌入视频,请使用以下命令:
web mvc embed video --provider VIMEO --videoId 16069687
如果启动服务器并在浏览器中启动应用程序,则可以观看上面嵌入的视频。 同样,您可能已经添加了YouTube或Viddler视频。
Roo还为您提供了在应用程序中嵌入Twitter消息,文档,股票行情,地图,照片和视频流的选项。 清单7中显示了各种命令。
清单7.嵌入命令
web mvc embed document
web mvc embed finances
web mvc embed map
web mvc embed photos
web mvc embed stream video
web mvc embed twitter
web mvc embed video
数据库逆向工程
数据库反向工程(DBRE)允许您对现有数据库进行内省并将其公开为应用程序。 为了展示DBRE的工作原理,我将根据现有的反馈架构创建一个反馈应用程序。 我将使用MySQL作为数据库。
在启动Roo之前,我们必须在MySQL安装中创建一个架构。 运行清单8中SQL脚本,以在MySQL数据库中创建一个反馈模式。
清单8.用于创建反馈模式SQL脚本
create database feedback_schema;
use feedback_schema;
CREATE TABLE feedback (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
TalkTitle VARCHAR(45) NOT NULL,
SpeakerName VARCHAR(45) NOT NULL,
Feedback VARCHAR(4000) NOT NULL,
PRIMARY KEY (id)
)
ENGINE = InnoDB;
这是具有一个表且没有任何关系的简单模式,但是Roo可以成功地对具有多个表和关系的复杂模式进行反向工程。
在运行上述SQL脚本并生成了架构之后,请创建一个反馈应用程序:
- 创建一个名为feedback的目录。
- 在操作系统命令行上,转到反馈目录。
- 通过键入
roo
命令打开Roo shell。 - 键入
project --topLevelPackage com.dw.roo.feedback
创建一个新的Maven项目。 - 在此应用程序中,我们将使用MySQL作为数据库。 要为您的应用程序设置持久性,请使用:
persistence setup --provider HIBERNATE --database MYSQL --databaseName feedback_schema --userName root --password password
在以root用户创建架构时,我使用root作为用户名。 请输入用于创建架构的用户名和密码。 此命令还将添加所有必需的JAR以保持持久性。
- 您可以使用
database introspect --schema feedback_schema
来对数据库模式进行database introspect --schema feedback_schema
。database introspect
命令显示与数据库模式相关的元数据。 该命令将向您显示Roo Shell控制台上架构的元数据。 您也可以使用--file attribute
将元数据xml导出到文件中。 - 对数据库模式进行自省之后,可以使用
database reverse engineer --schema feedback_schema --package ~.domain
进行database reverse engineer --schema feedback_schema --package ~.domain
。database reverse engineer
命令具有两个必需的属性,模式和程序包,用于指定要反向工程的模式的名称以及Roo将在其中生成源的程序包。 这将在com.dw.roo.feedback.domain包中创建所有实体。 - 下一步是为您的应用程序生成控制器。 您可以通过触发
controller all --package ~.web
。 - 在运行应用程序之前,我们需要对persistence.xml中的属性进行一些小的更改。 hibernate.ejb.naming_strategy属性使用的是
ImprovedNamingStrategy
,不适用于MySQL数据库,如果执行mvn clean install tomcat:run
,则会出现异常。 为了使其工作,我们必须将hiberate.ejb.naming_strategy更改为DefaultNamingStrategy
,如下所示:<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.DefaultNamingStrategy"/>
- 现在,您可以使用Maven命令
mvn clean install tomcat:run
运行反馈应用程序。
您可以下载会议和反馈的源代码(请参阅下载)。
结论
至此,我们已经将简单的CRUD Web应用程序扩展到了成熟的企业应用程序。 我们展示了添加Selenium测试,Spring Security,国际化支持等功能非常简单。 我还展示了如何使用Spring Roo数据库反向工程功能从现有数据库创建应用程序。 Roo仍然可以轻松地将许多功能(如JMS,Solr和JSON支持等)添加到您的应用程序中。
在本系列的第3部分中,我将讨论如何将会议应用程序移植到Google App Engine。
翻译自: https://www.ibm.com/developerworks/java/library/os-springroo2/index.html