I am developing a simple web application which provides RestFul API for managing several resources.
I am using java standard edition - openjdk version "1.8.0_191 and the following javalite dependencies:
javalite-common-2.3-SNAPSHOT.jar
activejdbc-2.3-SNAPSHOT.jar
activeweb-2.3-SNAPSHOT.jar
activeweb-testing-2.3-SNAPSHOT.jar
freemarker-2.3.28.jar
I use embedded jetty v9.4.1 for web server and the whole project is packed as jar with dependencies and is run using the following command:
java -Dconfig=/etc/project/config.properties -jar jarname.jar
The web application is designed to serve only JSON and all FreeMarker templates construct json messages. Here is my FreeMarker config class:
public class FreeMarkerConfig extends AbstractFreeMarkerConfig {
#Override
public void init() {
// this is to override a strange FreeMarker default processing of numbers
Configuration config = this.getConfiguration();
config.setNumberFormat("0.##");
config.setClassicCompatible(true);
config.setClassForTemplateLoading(this.getClass(), "webapp/WEB-INF/views");
}
}
After the packaging the structure inside the jar is as follows (this is a simplified version with only one resource):
├── META-INF
│ ├── MANIFEST.MF
│ └── maven
│ └── project.control
│ └── panel
│ ├── pom.properties
│ └── pom.xml
├── activejdbc_models.properties
├── app
│ ├── config
│ │ ├── AppBootstrap.class
│ │ ├── AppControllerConfig.class
│ │ ├── DbConfig.class
│ │ ├── FreeMarkerConfig.class
│ │ └── RouteConfig.class
│ ├── controllers
│ │ ├── APIController.class
│ │ ├── CatchAllFilter.class
│ │ ├── OfficesController.class
│ └── models
│ ├── Office.class
├── config.properties
├── project
│ └── control
│ └── panel
│ ├── Launcher.class
│ ├── dao
│ │ ├── query
│ │ │ ├── Query.class
│ │ │ ├── QueryBuilder.class
│ │ │ ├── QueryStringBuilder.class
│ │ │ ├── StatsParamsHandler.class
│ │ └── validators
│ │ ├── OfficeValidator.class
│ ├── exceptions
│ │ └── ResourceNotFoundException.class
│ └── util
│ ├── Config.class
│ ├── JsonHelper.class
└── webapp
└── WEB-INF
├── views
│ ├── offices
│ │ ├── _comma.ftl
│ │ ├── _office.ftl
│ │ ├── _office_agency.ftl
│ │ ├── _office_agent.ftl
│ │ └── index.ftl
│ ├── layouts
│ │ └── default_layout.ftl
│ ├── shared
│ │ ├── _paging.ftl
│ │ └── message.ftl
│ ├── system
│ │ ├── 404.ftl
│ │ └── error.ftl
└── web.xml
Most of the time everything seems to operate normally and without any problems. But at some point something happens and FreeMarker is unable to locate the templates which were previously served multiple times.
I am not able to reproduce the behaviour on localhost so I am not able to debug it.
It happened several times while running on a server. The only observation I had is that it happens after several idle hours - i.e there were no requests for several hours, the next request fails because FreeMarker can't locate the needed template. Here is the exact exception which is thrown, when /offices is requested:
2019-06-24 15:22:50 - INFO LazyList:164 - {"sql":"SELECT * FROM offices ORDER BY id LIMIT 20 OFFSET 0 ","params":[],"duration_millis":2,"cache":"miss"}
2019-06-24 15:22:50 - INFO DB:164 - {"sql":"SELECT COUNT(*) FROM offices","params":[],"duration_millis":0}
2019-06-24 15:22:50 - INFO FreeMarkerTemplateManager:81 - Rendering template: '/offices/index.ftl' without layout.
2019-06-24 15:22:50 - INFO FreeMarkerTemplateManager:81 - Rendering template: '/shared/message.ftl' without layout.
2019-06-24 15:22:50 - INFO RequestDispatcher:360 - {"controller":"app.controllers.OfficesController","duration_millis":15,"remote_ip":"127.0.0.1","method":"GET","action":"index","error":"Failed to render template: '/shared/message.ftl' without layout. Template not found for name \\\"/shared/message.ftl\\\".\\nThe name was interpreted by this TemplateLoader: WebappTemplateLoader(subdirPath=\\\"/WEB-INF/views/\\\", servletContext={contextPath=\\\"\\\", displayName=\\\"activeweb\\\"}).","url":"http://127.0.0.1:5050/offices","status":404}
2019-06-24 15:22:50 - INFO FreeMarkerTemplateManager:81 - Rendering template: '/system/404.ftl' with layout: '/layouts/default_layout.ftl'.
2019-06-24 15:22:50 - ERROR RequestDispatcher:290 - ActiveWeb internal error:
org.javalite.activeweb.ViewMissingException: Failed to render template: '/system/404.ftl' with layout: '/layouts/default_layout.ftl'. Template not found for name "/system/404.ftl".
The name was interpreted by this TemplateLoader: WebappTemplateLoader(subdirPath="/WEB-INF/views/", servletContext={contextPath="", displayName="activeweb"}).
at org.javalite.activeweb.freemarker.FreeMarkerTemplateManager.merge(FreeMarkerTemplateManager.java:109)
at org.javalite.activeweb.RenderTemplateResponse.doProcess(RenderTemplateResponse.java:88)
at org.javalite.activeweb.ControllerResponse.process(ControllerResponse.java:67)
at org.javalite.activeweb.RequestDispatcher.renderSystemError(RequestDispatcher.java:283)
at org.javalite.activeweb.RequestDispatcher.doFilter(RequestDispatcher.java:219)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1613)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:541)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:190)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1584)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1228)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:481)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1553)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1130)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.Server.handle(Server.java:564)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:320)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:279)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:112)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:672)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:590)
at java.lang.Thread.run(Thread.java:748)
Additional details can be provided in case of need.
What may cause such problems - while everything seems operational, suddenly FreeMaker can't locate templates which were previously served without any problems.
Any suggestions that may help for debugging the issue will be appreciated.
Thanks!
After more detailed investigation it appeared that jetty extracts the FreeMarker templates from the jar and places them in /tmp folder. The folder has the following pattern
/tmp/jetty-<host>-<port>-<resourceBase>-<context>-<virtualhost>-<randomdigits>.dir
Example:
jetty-0.0.0.0-5050-webapp-_-any-35239075401795634.dir
Unix based operation systems have policy for cleaning /tmp folder and after the folder is deleted - obviously the templates can't be found.
The solutions is to configure jetty's WebAppContext to use another directory for this kind of data. This can be done with setTempDirectory method:
String webViewsPath = Launcher.class.getResource("/webapp").toString();
WebAppContext webapp = new WebAppContext(webViewsPath, "/");
webapp.setTempDirectory(new File("/data/templates"));
server.setHandler(webapp);
More information for jetty's temporary directories can be found here:
https://www.eclipse.org/jetty/documentation/9.4.x/ref-temporary-directories.html
Related
I'm building a web application in spring boot.
The development environment is Gradle and I am using visual studio code.
I'm using visual studio code's debug function, spring boot dashboard, and when I start the application, the page is displayed correctly, but when I make the web application a jar file and run it with java -jar, it doesn't work with some page transitions.
Please tell me what I should do to make it work and what is happening.
(I think that spring boot can't find the some path, but I don't know what is and how to fix it.)
Here's the page I was on when I accessed 「localhost:8080/gallery」
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Tue Sep 01 23:09:16 JST 2020
There was an unexpected error (type=Internal Server Error, status=500).
The following is the error I get when I use java -jar to access a specific gallery page.
2020-09-01 23:09:14.129 INFO 15464 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-09-01 23:09:14.143 INFO 15464 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 14 ms
Check image file path
2020-09-01 23:09:16.656 ERROR 15464 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException: null
at com.example.sweepea.GetImage.getImage(GetImage.java:23) ~[classes!/:na]
at com.example.sweepea.swepeaController.getGallery(swepeaController.java:21) ~[classes!/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
The methods in the controller class are as follows. 「index」and「about」is work.
#GetMapping("/index")
public String getIndex(){
return "index";
}
#GetMapping("/gallery")
public String getGallery(Model model){
List<String> imagePath = new GetImage().getImage(new File("src/main/resources/static/images"));
model.addAttribute("imagePath", imagePath);
return "gallery";
}
#GetMapping("/about")
public String getAbout(){
return "about";
}
}
GetImage class are as follw.
It is get .jpg images , add List and return.
#Service
public class GetImage {
String month = "202009";
List<String> pathname = new ArrayList<String>();
List<String> getImage(File file){
File[] filelist = file.listFiles();
if(filelist == null){
System.out.println("Check image file path");
}
for (File tmpFile : filelist){
if(tmpFile.isDirectory()){
month = tmpFile.getName();
//System.out.println(month);
//pathname.add(month);
getImage(tmpFile);
}else{
String imagePath = "images/" + month + "/" + tmpFile.getName();
if(imagePath.substring(imagePath.length() - 3).equals("jpg")){
pathname.add(imagePath);
//System.out.println(imagePath);
}
}
}
return pathname;
}
}
gallery.html
<body>
<div id="content">
<header class="page_header wrapper" >
<h1><a th:href="#{'/index'}"><img class="logo" th:src="#{images/logo.svg}" alt=""></a></h1>
<nav class="main_nav">
<ul>
<ii><a th:href="#{'/index'}">TOP</a></ii>
<ii><a th:href="#{'/gallery'}">GALLERY</a></ii>
<ii><a th:href="#{'/about'}">ABOUT</a></ii>
</ul>
</nav>
</header>
<main>
<div class="edition">
<p>sub title</p>
</div>
<h2 class="mounth">Gallery</h2>
<div class="grid item">
<a th:each="image : ${imagePath}" th:href="#{${image}}"><img th:src="#{${image}}" alt=""></a>
</div>
</main>
</body>
source tree is like that
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── sweepea
│ │ ├── DemoApplication.java
│ │ ├── GetImage.java
│ │ ├── Test.java
│ │ └── swepeaController.java
│ └── resources
│ ├── application.properties
│ ├── static
│ │ ├── logo.svg
│ │ ├── images
│ │ │ ├── 201910
│ │ │ │ ├── 1.jpg
│ │ │ ├── 201911
│ │ │ ├── 201912
│ │ │ │ ├── 1.jpg
│ │ │ ├── 202001
│ │ │ ├── 202002
│ │ │ │ ├── 1.jpg
│ │ │ ├── 202003
│ │ │ │ ├── 1.jpg
│ │ │ ├── 202004
│ │ │ │ ├── 1.jpg
│ │ │ ├── 202005
│ │ │ │ ├── 1.jpg
│ │ │ ├── 202006
│ │ │ ├── 202007
│ │ │ ├── 202008
│ │ │ │ ├── 1.jpg
│ │ │ ├── 202009
│ │ │ ├── logo.svg
│ │ │ └── logo_favicon.png
│ │ └── style.css
│ └── templates
│ ├── about.html
│ ├── gallery.html
│ ├── index.html
│ └── video.html
└── test
└── java
└── com
└── example
└── sweepea
└── DemoApplicationTests.java
This line:
List<String> imagePath = new GetImage().getImage(new File("src/main/resources/static/images"));
is the problem. The resources should always be read from within the Jar file, not by specifying path, either an absolute one or relative to whatever directory your run your app from.
This article shows how to properly read file from the resources folder.
If you package you application in a jar, it's no longer dependent on the source code.
If you start your application using java -jar ... in another folder or on another server, then new File("src/main/resources/static/images") will no longer work.
I think it will work when you execute java -jar ... in the root folder of your project, but that's probably not a long-term solution.
Maven considers the src/main/resources folder as an input folder for files that need to be included in the jar, so after packaging, those files will be available from java as getClass().getResource("/static/images/202008/1.jpg"), and that's how they will be served in Spring Boot (using the ResourceHttpRequestHandler, without the static/ prefix).
If you want to add images later without having to rebuild and repackage your application, it's better to use an absolute server path like "C:\gallery\images" or "/opt/gallery/images".
First Thing to say, I am a relatively inexperienced programmer.
My task is to import a given gradle project into Spring Boot Tool Suite (sts). I have already download springboot cli, java, gradle. & In the past, I was able to run spring perfectly on another project, unfortunately I had to get my hard drive replaced, so I lost a considerable amount of downloads.
Nonetheless, my issue is two-parted. Firstly, my tool suite (ide? It's basically eclipse) does not recognize my project as a a java project, I have tried many options & the only was to fix that portion is to hardcode org.eclipse.core.javabuilder within the buildCommand tag in my .project file, which I don't remember doing for my last project.
Here is the directory tree of the backend folder(don't concern with the frontend)
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── ca
│ │ └── mcgill
│ │ └── ecse321
│ │ └── eventregistration
│ │ ├── EventRegistrationApplication.java
│ │ ├── controller
│ │ │ ├── ApiError.java
│ │ │ ├── EventRegistrationRestController.java
│ │ │ └── RestExceptionHandler.java
│ │ ├── dao
│ │ │ ├── EventRegistrationRepository.java
│ │ │ ├── EventRepository.java
│ │ │ ├── PersonRepository.java
│ │ │ └── RegistrationRepository.java
│ │ ├── dto
│ │ │ ├── EventDto.java
│ │ │ ├── PersonDto.java
│ │ │ └── RegistrationDto.java
│ │ ├── model
│ │ │ ├── Event.java
│ │ │ ├── Person.java
│ │ │ ├── Registration.java
│ │ │ └── RegistrationManager.java
│ │ └── service
│ │ └── EventRegistrationService.java
│ └── resources
│ └── application.properties
└── test
└── java
└── ca
└── mcgill
└── ecse321
└── eventregistration
└── service
├── event
│ └── TestCinema.java
├── payment
│ ├── TestPaymentWithCreditCard.java
│ ├── TestPaymentWithCreditCardData.java
│ └── TestUtils.java
└── role
├── PromoterRoleTestData.java
└── TestPromoterRole.java
Additionally, Here it my build.gradle file
buildscript {
ext {
springBootVersion = '2.1.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
This fixes my java problem, but I still cannot get it running as a spring boot application.
Please help as I am not sure where to continue!!! I need to be able to run this project as a spring boot application for backend services, as I will then be tasked with connecting it to frontend services with vue.js. (Don't worry i got that part)!
Here is my EventRegistration.java file as well
package ca.mcgill.ecse321.eventregistration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.SpringApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
#RestController
#SpringBootApplication
public class EventRegistrationApplication {
public static void main(String[] args) {
SpringApplication.run(EventRegistrationApplication.class, args);
}
#RequestMapping("/")
public String greeting() {
return "ECSE321 Event Registration Application - Backend base URL.\n"
+ "Use the API methods to interact with the backend!";
}
}
Though I stopped using eclipse but following used to solve situations like this
1.
- Eclipse ->preferences-->java-->installed JRE --> select a standard vm
(can find here jdk1.8.0_51.jdk/Contents/Home)
2.
- add the JRE System Library to your project build path
- right click project > select build path > libraries >
- if JRE is listed then good otherwise > add Library
- select JRE library and finish
When it comes to uploading a file in Spring MVC, I found many articles suggesting that you should store it into the file-system folder rather than folder inside of a project.
But My web-app render HTML(view)page using a file from a folder inside webapp folder.
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com // Where my application's is.
│ │ ├── resources
│ │ │ ├── META-INF
│ │ │ ├── log4j.xml
│ │ │ ├── maildata.properties
│ │ │ └── persistence-mysql.properties
│ │ └── webapp
│ │ ├── WEB-INF
│ │ │ ├── classes
│ │ │ ├── spring
│ │ │ ├── templates // Where my view pages are. Thymeleaf template used.
│ │ │ │ ├── login.html
│ │ │ │ ├── index.html
│ │ │ │ └── ...html and More...
│ │ │ └── web.xml
│ │ └── resources // --> This is location that my app use for rendering.
│ │ ├── css
│ │ ├── fonts
│ │ ├── images
│ │ ├── js
│ │ └── vendors
│ └── test
│ ├── java
│ │ └── com
│ └── resources
│ └── log4j.xml
└── target
in my View part, I use files from resources folder to render the HTML page.
.
.
<link rel="stylesheet" th:href="#{'/resources/css/style.css'}" type="text/css">
.
.
.outbox {
background:
url([[#{/resources/images/header.jpg}]]) #000
55% 0 no-repeat;
background-size: 140% auto;
margin-top: -68px;
width: 100%;
padding-top: 42.25%;
position: relative;
display: block;
z-index: -1;
}
.
.
.
I want to make functionality that uploading a file into the resources folder and using it for rendering HTML page.
Is this a bad idea? Could you explain why?
If so, what's the best approach I can take for my task? (The Best location that for uploading files)
If you undeploy the application, what will happen to all files uploaded to the resources folder? I believe they will be deleted along with the rest of the application. If that doesn't matter to you, then you are fine to upload to that direct. If instead you want the files to persist between deployments, you should either:
Upload them to a different directory on the file system. One that isn't managed by a server.
Upload them to a database table as a BLOB.
In either case, you will need to make a controller that returns the image as a file (because they are no longer accessible as resources).
I am experimenting with SPI on JDK 9. Entire example works on JDK 9 without "module-info.java". After adding "module-info.java" ServiceLocator is not finding implementing class. I am confused and I cannot find working SPI example in modulerized JDK 9 project.
So my example project looks like this:
/spidemo
├── apiModule
│ ├── pom.xml
│ └── src
│ └── main
│ └── java
│ ├── eu
│ │ └── com
│ │ └── example
│ │ └── text
│ │ └── spi
│ │ └── TextAPI.java
│ └── module-info.java
├── applicationB
│ ├── pom.xml
│ └── src
│ └── main
│ ├── java
│ │ └── eu
│ │ └── com
│ │ └── example
│ │ └── spi
│ │ └── b
│ │ └── application
│ │ └── DemoB.java
│ └── module-info.java
├── applicationCommon
│ ├── pom.xml
│ └── src
│ └── main
│ └── java
│ ├── eu
│ │ └── com
│ │ └── example
│ │ └── spi
│ │ └── application
│ │ └── TextAPIProvider.java
│ └── module-info.java
├── implementationB
│ ├── pom.xml
│ └── src
│ └── main
│ ├── java
│ │ └── eu
│ │ └── com
│ │ └── example
│ │ └── implb
│ │ └── text
│ │ └── TextB.java
│ ├── module-info.java
│ └── resources
│ └── META-INF
│ └── services
│ └── eu.com.example.text.spi.TextAPI
I have introduced interface:
package eu.com.example.text.spi;
public interface TextAPI {
String getHelloWorldText();
}
This interface is implemented by:
package eu.com.example.implb.text;
import eu.com.example.text.spi.TextAPI;
public class TextB implements TextAPI {
public String getHelloWorldText() {
return "Text from B implementation";
}
}
The implementation is searched by code simmiliar to:
package eu.com.example.spi.application;
import eu.com.example.text.spi.DefaultTextAPI;
import eu.com.example.text.spi.TextAPI;
import java.util.ServiceLoader;
public class TextAPIProvider {
public static TextAPI getProvider(String providerName) {
ServiceLoader<TextAPI> serviceLoader = ServiceLoader.load(TextAPI.class);
for (TextAPI provider : serviceLoader) {
String className = provider.getClass().getName();
if (providerName.equals(className)) {
return provider;
}
}
throw new RuntimeException(providerName + " provider is not found!");
}
}
And now is the fun part. When I am executing class below without:
/implementationB/src/main/java/module-info.java
/applicationB/src/main/java/module-info.java
then implementation class is found and text is printed out.
package eu.com.example.spi.b.application;
import eu.com.example.spi.application.TextAPIProvider;
public class DemoB {
public static void main(String[] args) {
System.out.println("---> " + TextAPIProvider.getProvider("eu.com.example.implb.text.TextB").getHelloWorldText());
}
}
After introducing this two "module-info.java" files implementation class is not found by ServiceLocator.
Content of /applicationB/src/main/java/module-info.java:
module eu.com.example.applicationB {
requires eu.com.example.apiModule;
requires transitive eu.com.example.applicationCommon;
uses eu.com.example.text.spi.TextAPI;
}
Content of /implementationB/src/main/java/module-info.java:
module eu.com.example.implb.text {
requires eu.com.example.apiModule;
exports eu.com.example.implb.text;
// provides eu.com.example.implb.text.TextB with eu.com.example.text.spi.TextAPI;
}
When I uncomment:
provides eu.com.example.implb.text.TextB with eu.com.example.text.spi.TextAPI;
line then compilation error occurs:
.../implementationB/src/main/java/module-info.java:[7,74] the service implementation type must be a subtype of the service interface type, or have a public static no-args method named "provider" returning the service implementation
.../implementationB/src/main/java/module-info.java:[7,5] service implementation must be defined in the same module as the provides directive
I have tried to change package names as compilation error sugests, but then I have introduced "split package" issues.
What I should do to use ServiceLocator in fully modularized JDK 9? Is it possible? Have anyone seen working example?
Code can be also seen here: https://github.com/RadoslawOsinski/spidemo
You can change to using:-
provides eu.com.example.text.spi.TextAPI with eu.com.example.implb.text.TextB;
// you provide a service through its implementation
instead of
provides eu.com.example.implb.text.TextB with eu.com.example.text.spi.TextAPI;
Services in the document provides a sample around the implementation.
A module can specify that it provides a service with a specific type of service providers. It is declared using provides and with keyword.
syntax : provides serviceType with implementationTypes;
( Can specify multiple implementations types as a comma-separated list )
Therefore in your module eu.com.example.implb.text should add following statement.
provides eu.com.example.implb.text.TextB with eu.com.example.text.spi.TextAPI;
NOTE : provides does not equal exports. So any other modules that requires eu.com.example.implb.text will not access eu.com.example.implb.text.TextB if it doesn't exports.
I have the following directly structure:
mp1
├── lib
│ └── kryonet-2.21-all.jar
├── mp1.iml
├── out
│ ├── artifacts
│ │ └── UX
│ │ ├── META-INF
│ │ │ └── MANIFEST.MF
│ │ └── UX.jar
├── src
│ ├── [.class files]
│ └── cs425
│ └── mp1
│ ├── agent
│ │ ├── [handlers].java
│ ├── common
│ │ ├── [commons].java
│ ├── main
│ │ └── UX.java
│ └── network
│ ├── [networks].java
I'm completely new to mainfest files, and am just trying to get a .jar to run on a remote box. My manifest looks like this right now:
Manifest-Version: 1.0
Main-Class: cs425.mp1.main.UX
But I just keep getting,
~/mp1$ java -jar UX.jar
Error: Invalid or corrupt jarfile UX.jar
Any help? I'm not sure what options to add for my external library and how to define my classpath.
It appears as though the only thing I was missing was the classpath to my external .jar file. My manifest now looks like:
Manifest-Version: 1.0
Main-Class: cs425.mp1.main.UX
Class-Path: kryonet-2.21-all.jar
After making sure all of my required files are part of the artifact package (my manifest file, external jar, etc), the issue seems to have gone away.