Blog of Samperson

J2EE第5次作业的坑,我帮你踩过了

2018-01-05

在写J2EE第5次作业时,遇到了很多坑,无论是老师上课的PPT、给的示例代码还是网上的教程都有很多不完善的地方。因此想在这篇博客里对我遇到的这些坑做一个总结,供同学们参考,也欢迎大家在评论区批评指正,或者提出一些新的问题。
在这里要对薛老师表示感谢,因为他花费一个半小时陪我一起解决了这些问题。

👉可以继续参考:J2EE第6次作业的坑 (JPA技术)

项目要求

整体要求

[1] 客户登录, 跟据客户的ID/PW, 查询订单情况, 并根据具体情况, 返回不同结果:有缺货的订单项,警示页面;正常结果: 标准页面, 每一项订单包括多项属性(时间、名称、数量、价格), 分页显示;未知的客户ID: 错误页面
[2] 使用过滤器解决表单中的中文请求后的乱码问题
[3] 统计在线人数:总人数、已登录人数、游客人数
[4] 用户通过URL直接访问XXXServlet或XXX.JSP时,已登录用户可访问,未登录用户转向登录页面

第4次作业要求

基于MVC、DAO、Service等设计
[1] Model: JavaBean(ServiceFactory, XXXService; DAOFactory,XXXDAO)
[2] View: JSP
[3] Controller: Servlet

第5次作业要求

[1] Service层使用EJB技术
[2] DAO层使用EJB技术

项目构成

新建项目

[1] 首先到WildFly官网上下载最新版本。

[2] 我完成作业用IDE的是intellij idea,因此这篇博客不适用于eclipse等其他IDE。
[3] 因为之后的作业还要基于第4次作业进行修改,所以这次我新建了两个项目,分别是client和server。其中client是Web Application,应用Tomcat;server是EJB,应用Wildfly。
创建项目时,client和之前的项目类似;server则在创建时左侧栏选择 Java Enterprise, 右侧找到 EJB: Enterprise Java Beans, 同时设置好 JDK 版本,应用服务器 WildFly 注意下方勾选 Create ejb-jar.xml 并且 Libraries 选择 Download ,这样 IDEA 会自动下载所需的 javax.ejb-api.jar 并且设置好 classpath。

osx系统升级到10.13之后,idea新建EJB项目下面没有Download选项,这样不能按照接下来的步骤进行。按照其他网上攻略的办法,周围的同学屡经尝试均无果,欢迎其他同学踊跃尝试。
如果多勾选了Web Application,可能会导致在configuration中提示:Error: Artifact has invalid extension。

“转移”代码

[1] client中包含之前作业中的servlets, tag, service, model, listener, filter等包;server中包含之前作业中的service, serviceImpl, dao, daoImpl, model和factory。

model需要implements Serializable,否则后期可能会报错javax.ejb.EJBException: Failed to read response。

[2] 对于server的serviceImpl包中每一个Service,需要使用 @Stateless 标注,表明这是一个无状态的会话 Bean。 这个实现类对客户端是透明的,客户端仅需要拥有刚刚建立的接口即可,不需要关心具体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package serviceImpl;

import dao.UserDao;
import factory.DaoFactory;
import service.UserService;

import javax.ejb.Stateless;

@Stateless
public class UserServiceBean implements UserService {

private UserDao userDao = DaoFactory.getUserDao();

@Override
public String getPassword(String id) {
return userDao.getPassword(id);
}
}

[3] 之后就可以点击工程右上角的Run按钮部署工程

开始踩坑

继续写client

[1] 首先把下载的wildfly目录下bin/client/jboss-client.jar 拷贝进client的web/WEB-INF/lib 文件夹中,并打开 Project Structure 设置依赖添加这个 lib 文件夹。

注意一定是在项目内部依赖,而不是外部依赖到External Libraries中。这个问题的解决话费了大约1小时😭😭😭
如果这边配置出现问题,一般会报错javax.naming.NameNotFoundException: Name [ejb:…] is not bound in this Context. Unable to find [ejb:].

[2] 对于client和server的service包中的每一个Service,需要使用 @Remote 标注,表明这是一个远程接口。如:

1
2
3
4
5
6
7
8
package service;

import javax.ejb.Remote;

@Remote
public interface UserService {
String getPassword(String id);
}

一定要保持client和server的service包完全一致。

[3] 在 src 下新建一个 jboss-ejb-client.properties 文件,内容如下。注意端口不要冲突:我这里设置server端口是8080,因此配置文件中port=8080;client端口为8000。

1
2
3
4
5
6
endpoint.name=client-endpoint
remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
remote.connections=default
remote.connection.default.host=localhost
remote.connection.default.port=8080
remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

[4] client中新建factory包,按照PPT创建EJBFactory。但是PPT的代码只有JBoss6和7,对于WildFly11,可以使用如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package factory;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Hashtable;

public class EJBFactory {
public static Object getEJB(String beanName, String interfaceName) {
Hashtable<String, String> jndiProperties = new Hashtable<>();
jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
try {
Context context = new InitialContext(jndiProperties);
final String fullName = "ejb://J2EE_server_ejb exploded//" + beanName + "!" + interfaceName;
return context.lookup(fullName);
} catch (NamingException e) {
e.printStackTrace();
}

return null;
}
}

其中stateless beans的路径ejb:/<app-name>/<module-name>/<distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>:
module-name为server build得到的.jar文件名字(不包含”.jar”);
app-name和distinct-name均为空;
bean-name为bean名字,只有名字;
fully-qualified-classname-of-the-remote-interface为interface路径+名称。

[5] 在Servlet中,对于EJBFactory的调用是这样的:

1
UserService userService = (UserService) EJBFactory.getEJB("UserServiceBean", "service.UserService");

配置数据源

注意除非使用JBoss6或者7(老师不推荐),否则PPT上的内容和WildFly版本一定是不兼容的,不要按照PPT做!这些坑是薛老师踩过的。

[1] 在WildFly安装目录/modules/com/或/modules/system/layers/base/com下新建mysql/main
[2] 下载驱动程序mysql-connector-java-5.1.45.zip,解压,将其中的mysql-connector-java-5.1.45-bin.jar驱动文件复制到main下面
[3] 在main下建立module.xml如下:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>  
<module xmlns="urn:jboss:module:1.5" name="com.mysql">
<resources>
<resource-root path="mysql-connector-java-5.1.45-bin.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
<module name="javax.transaction.api"/>
</dependencies>
</module>

一定要注意path的名字和jar名字完全一样,这边我当时少写了“-bin”,然后debug大约1个小时😊

[4] 修改standalone.xml:
修改WildFly安装目录/standalone/configuration/standalone.xml,在datasources节点增加:

1
2
3
4
5
6
7
8
<datasource jndi-name="java:jboss/datasources/mysqlDS" pool-name="mysqlDSPool">
<connection-url>jdbc:mysql://localhost:3306/数据库名</connection-url>
<driver>mysql</driver>
<security>
<user-name>用户名</user-name>
<password>密码</password>
</security>
</datasource>

在drivers节点增加:

1
2
3
<driver name="mysql" module="com.mysql">
<xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
</driver>

修改配置文件之前,需要先停止运行、关闭项目(或者直接关闭IDE),修改保存后再打开项目、运行。否则配置文件会自动恢复。

[5] 注意修改server连接数据库部分的代码,其余不变:

1
2
3
4
5
6
try {
InitialContext jndiContext = new InitialContext();
datasource = (DataSource) jndiContext.lookup("java:jboss/datasources/mysqlDS");
} catch (NamingException e) {
e.printStackTrace();
}

其他注意点

[1] 昨晚成功之后,今早打开项目发现又一次失败,运行client报错找不到EJB。这种情况的解决方案有两种:一是关闭IDE重新打开(……),二是新建一个项目,把之前的配置和代码复制过来,亲测有效。
[2] 运行的时候要先开server,然后开client,否则会找不到destination。
[3] 👉可以继续参考:J2EE第6次作业的坑 (JPA技术)

参考资料

[1] https://youthlin.com/20161265.html
[2] http://blog.csdn.net/kylinsoong/article/details/17042245
[3] https://stackoverflow.com/questions/45053857/name-ejb-is-not-bound-in-this-context-unable-to-find-ejb-with-root-c