过去两天我在网上搜索,我仍然找不到解决我这个奇怪问题的方法。
我有一个@SessionScoped bean,一个带有@Stateless注释的Restful Jersey服务和@Inject the bean以及@EJB my ejb。
当我尝试从浏览器的URL调用其余的Web服务时,该服务会注入我的@Inject userBean。 但是当我尝试从@SessionScoped UserBean中的代码调用Web服务时,它没有注入。
The Bean:
@SessionScoped
@Named("userBean")
public class UserBean implements Serializable {
@EJB
private UserEJB userEJB;
private User user = new User();
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
private String newStatusContent;
public String getNewStatusContent() {
return newStatusContent;
}
public void setNewStatusContent(String content) {
this.newStatusContent = content;
}
public void publishStatus() {
if (newStatusContent != null && newStatusContent.trim().isEmpty() == false) {
//Maybe a way to set manualy the user property of this bean here?
Client client = ClientBuilder.newClient();
Response response = client.target("http://localhost:8080/services/posts/status")
.queryParam("content", newStatusContent)
.request(MediaType.APPLICATION_JSON).get();
newStatusContent = "";
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request
= (HttpServletRequest) context.getExternalContext().getRequest();
if (request.getRequestURI().equals("/index/profile/profile.xhtml") && request.getParameter("prettyname") != null && request.getParameter("prettyname").equals(user.getPrettyname())) {
}
if (response.getStatus() == 200) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Δημοσίευση", "Η νέα κατάσταση δημοσιεύτηκε επιτυχώς."));
} else {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Δημοσίευση", "Συγγνώμη, κάτι πήγε στραβά με την νέα κατάσταση :("));
}
}
}
public String login() throws ServletException {
User authenticatedUser;
if (userEJB.mailExists(user)) {
if ((authenticatedUser = userEJB.authenticate(user)) != null) {
user = authenticatedUser;
//and store the api key for rest api
// userEJB.storeApiKey(user.getUserId());
} else {
FacesContext.getCurrentInstance().addMessage("mail", new FacesMessage("Wrong password for this e-mail"));
return "";
}
} else {
user = userEJB.create(user);
}
if (getPrevURI().isEmpty() == false) {
return getPrevURI();
} else {
return "/index/index.xhtml?faces-redirect=true";
}
}
}
RestApplication类(没有web.xml):
@ApplicationPath("/services/*")
public class RestApplication extends javax.ws.rs.core.Application{
@Override
public Set<Class<?>> getClasses() {
return new HashSet<Class<?>>(Arrays.asList(
UserService.class,
MessageService.class, PostService.class));
}
}
Restful Webservice类:
@Path("/posts")
@Stateless
public class PostService {
@EJB
private PostEJB postEJB;
@Inject
UserBean userBean;
@Path("/status")
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response publishStatus(@QueryParam("content") String content) {
if (content.isEmpty() || userBean.getUser().getUserId() == null) {
System.out.println("null user id or content but?" + content);
return Response.status(Response.Status.BAD_REQUEST).build();
}
Post postStatus = new Post();
postStatus.setContent(content.trim());
postStatus.setPublisher(userBean.getUser());
postStatus.setPublisherUserId(userBean.getUser().getUserId());
postEJB.create(postStatus);
System.out.println("post id : "+postStatus.getPostId());
PostStatus status = new PostStatus(postStatus.getPostId());
status.setReceiver(userBean.getUser());
status.setReceiverId(userBean.getUser().getUserId());
postEJB.postStatus(status);
return Response.status(Response.Status.OK).build();
}
//*this is not the problem I tried to remove and still doesn't worked*
@PreDestroy
public void destruct() {
postEJB.destruct();
}
}
Beans.xml(bean-discovery-mode to&#39; all&#39;)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all"
>
</beans>
我的步骤:
http://localhost:8080/services/posts/status?content=Test status
我使用的是Glassfish 4.1,JDK 8.20,Jersey 2.10(模块玻璃鱼)
我做错了什么?为什么要使用浏览器的网址而不是来自代码的客户端来电?
-----使用&#39; API KEY&#39;工作替代方式-----------
保存加密Cookie(用于浏览器&#39; s / js调用)和On Encrypted Session
我发现进行安全身份验证的另一种方式,但这不是我想要的:
ApiAuthenticator服务过滤器:
public class ApiAuthenticator implements ClientRequestFilter {
private static final StandardPBEStringEncryptor encryptor;
static {
encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword("jasypt");
}
public static String encrypt(String theString) {
return encryptor.encrypt(theString);
}
public static String decrypt(String encryptedString) {
return encryptor.decrypt(encryptedString);
}
private final String apikey;
public ApiAuthenticator(String apikey) {
this.apikey = apikey;
}
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
//requestContext.getCookies().putIfAbsent("apikey", new Cookie("apikey", apikey));
requestContext.getHeaders().add("apikey", encrypt(apikey));
}
}
在PostService中编辑了publishStatus方法:
@Path("/status")
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response publishStatus(@QueryParam("content") String content, @Context HttpHeaders headers) {
String apikey = headers.getHeaderString("apikey");
if (apikey == null) {
apikey = headers.getCookies().get("apikey").getValue();
}
if (content.isEmpty() || apikey == null) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
User user = userEJB.findByApiKey(ApiAuthenticator.decrypt(apikey));
Post postStatus = new Post();
postStatus.setContent(content.trim());
postStatus.setPublisher(user);
postStatus.setPublisherUserId(user.getUserId());
postEJB.create(postStatus);
PostStatus status = new PostStatus(postStatus.getPostId());
status.setReceiver(user);
status.setReceiverId(user.getUserId());
postEJB.postStatus(status);
return Response.status(Response.Status.OK).build();
}
编辑从bean到服务的客户端调用:
Client client = ClientBuilder.newClient();
Response response = client.target("http://localhost:8080/services/posts/status")
.queryParam("content", newStatusContent)
.register(new ApiAuthenticator(apiEJB.getApiKey(user.getUserId()).getApiKey()))
.request(MediaType.APPLICATION_JSON).get();
用户成功登录后,在UserBean上编辑了登录方法:
((HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse())
.addCookie(new Cookie("apikey", ApiAuthenticator.encrypt(apiEJB.storeApiKey(user.getUserId()).getApiKey())));
RestApplication.java:
@ApplicationPath("/services/*")
public class RestApplication extends javax.ws.rs.core.Application{
@Override
public Set<Class<?>> getClasses() {
return new HashSet<Class<?>>(Arrays.asList(
ApiAuthenticator.class ,
UserService.class,
MessageService.class, PostService.class));
}
}
解决方法是@Inject在UserBean中的PostService
:
@Inject PostService postService;
在UserBean.login方法上,删除所有其余客户端代码并替换为:
postService.publishStatus(newStatusContent);
但是,
引起:org.jboss.weld.context.ContextNotActiveException:WELD-001303:范围类型javax.enterprise.context.SessionScoped没有活动的上下文当我尝试在WebSocket ServerEndPoint中注入MessageService时
@ServerEndpoint(value = "/websocket", encoders = {MessageEncoder.class},
decoders = {MessageDecoder.class}, configurator = GetHttpSessionConfigurator.class)
public class ChatServerEndPoint {
@PersistenceContext(unitName = "CosmosDBPeristenceUnit")
private EntityManager em;
private static Set<Session> peers = Collections.synchronizedSet(new HashSet<Session>());
@Inject
private MessageService messageService;
//private Session session;
private int thisUserId;
private User thisUser;
@OnOpen
public void onOpen(Session thisSession, EndpointConfig config) throws IOException, EncodeException {
thisUserId = (int) config.getUserProperties().get("userId");
thisUser = em.find(User.class, thisUserId);
thisSession.getUserProperties().put("userId", thisUserId);
Iterator<Session> it = peers.iterator();
while (it.hasNext()) {
Session session = it.next();
int userId = (int) session.getUserProperties().get("userId");
thisSession.getBasicRemote().sendObject(new UserStatusMessage(em.find(User.class, userId), true));
session.getBasicRemote().sendObject(new UserStatusMessage(thisUser, true));
}
peers.add(thisSession);
}
/* we don't want @stateless problems with chat info except message, I will do it with rest public */
@OnMessage
public void onMessage(WebsocketMessage message) {
try {
Iterator<Session> it = peers.iterator();
if (message instanceof ChatMessage) {
ChatMessage msg = (ChatMessage) message;
msg.setSender(thisUser);
boolean otherUserIsOnline = false;
while (it.hasNext()) {
Session receiver = it.next();
if ((int) receiver.getUserProperties().get("userId") == msg.getReceiver_userId()) {
receiver.getBasicRemote().sendObject(msg);
otherUserIsOnline = true;
break;
}
}
if (otherUserIsOnline == false) {
//save the message to database via rest
messageService.addMessage(msg.getReceiver_userId(), msg.getMessage());
}
} else {
while (it.hasNext()) {
Session receiver = it.next();
receiver.getBasicRemote().sendObject(message);
}
}
} catch (IOException | EncodeException ex) {
System.out.println("Exception on endpoint: " + ex.getMessage());
}
}
@OnClose
public void onClose(Session session) throws IOException, EncodeException {
peers.remove(session);
Iterator<Session> it = peers.iterator();
while (it.hasNext()) {
Session otherSession = it.next();
otherSession.getBasicRemote().sendObject(new UserStatusMessage(thisUser, false));
}
}
@OnError
public void onError(Throwable t) {
t.printStackTrace();
}
}
GetHttpSessionConfigurator.class:
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
HttpSession ses = (HttpSession) request.getHttpSession();
config.getUserProperties().put("userId", ses.getAttribute("userId"));
}
}
MessageService.java:
@Stateless
@Path("/messages")
public class MessageService {
@EJB
UserEJB userEJB;
@EJB
ConversationEJB convEJB;
@Inject
UserBean userBean;
@Path("/{otherUserId}")
@GET
@Produces("application/json; charset=UTF-8")
public List<Message> findConversation(@PathParam("otherUserId") int otherUserId) {
User otherUser = userEJB.findById(otherUserId);
return convEJB.findConversation(userBean.getUser(), otherUser);
}
@Path("/{receiverUserId}")
@POST
@Produces("application/json; charset=UTF-8")
public Message addMessage(@PathParam("receiverUserId") int receiverUserId, @QueryParam("body") String body) {
return convEJB.addMessage(userBean.getUser(), userEJB.findById(receiverUserId), body);
}
@Path("/{messageId}")
@DELETE
public void removeMessage(@PathParam("messageId") int messageId) {
convEJB.removeMessage(messageId);
}
@Path("/{userId}/{otherUserId}")
@DELETE
public void clearConversation(@PathParam("userId") int userId, @PathParam("otherUserId") int otherUserId) {
convEJB.clearConversation(userEJB.findById(userId), userEJB.findById(otherUserId));
}
}
提前致谢!
答案 0 :(得分:0)
由于我不知道你的应用程序的确切流程,这可能是错误的,但这是我的假设:
您的userBean是会话范围的,它保存用户数据。在你的应用程序的某种程度上,它可能已初始化。
因此,一旦您从浏览器调用您的服务,它就会发送会话cookie以正确定位会话数据。
但是当你从代码中调用服务时,它没有传递那个会话cookie,所以在服务器端它创建了全新的会话,因此新的userBean带有空的用户数据。这就是为什么你有UserId null的原因。 Bean本身是注入的,否则你将在这里有空指针:
userBean.getUser().getUserId() // as userBean would be null
所以要在该调用中找到正确的用户bean,您需要将当前的会话cookie添加到该调用中。
PS。 但是为什么你要通过http向bean发出请求?注入服务bean并调用其方法而不是进行http调用会不会更合乎逻辑?