// . public static void main(String[] args) { // JUL SLF4J. SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install(); LOG.info("Start application"); // CDI http://weld.cdi-spec.org/ final Weld weld = new Weld(); // . weld.property(Weld.SHUTDOWN_HOOK_SYSTEM_PROPERTY, false); final WeldContainer container = weld.initialize(); // Netty JAX-RS, CDI . final CdiNettyJaxrsServer nettyServer = new CdiNettyJaxrsServer(); ............... // web . nettyServer.start(); .............. // TERM . try { final CountDownLatch shutdownSignal = new CountDownLatch(1); Runtime.getRuntime().addShutdownHook(new Thread(() -> { shutdownSignal.countDown(); })); try { shutdownSignal.await(); } catch (InterruptedException e) { } } finally { // CDI . nettyServer.stop(); container.shutdown(); LOG.info("Application shutdown"); SLF4JBridgeHandler.uninstall(); } } // , "" @ApplicationScoped public class IncrementService { .............. } // @NoCache @Path("/") @RequestScoped @Produces(MediaType.TEXT_HTML + ";charset=utf-8") public class RootResource { /** * {@link IncrementService}. */ @Inject private IncrementService incrementService; .............. }
/** * {@link IncrementResource}. */ @RunWith(Arquillian.class) public class IncrementResourceTest { @Inject private IncrementResource incrementResource; /** * @return , CDI. */ @Deployment public static JavaArchive createDeployment() { return ShrinkWrap.create(JavaArchive.class) .addClass(IncrementResource.class) .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); } @Test public void getATest() { final Map<String, Integer> response = incrementResource.getA(); assertNotNull(response.get("value")); assertEquals(Integer.valueOf(1), response.get("value")); } .............. /** * {@link IncrementService}. RequestScoped: * Arquillian . * @return {@link IncrementService}. */ @Produces @RequestScoped public IncrementService getIncrementService() { final IncrementService service = mock(IncrementService.class); when(service.getA()).thenReturn(1); when(service.incrementA()).thenReturn(2); when(service.getB()).thenReturn(2); when(service.incrementB()).thenReturn(3); return service; } }
public static void main(String[] args) { ............... // Netty JAX-RS, CDI . // JAX-RS Resteasy http://resteasy.jboss.org/ final CdiNettyJaxrsServer nettyServer = new CdiNettyJaxrsServer(); // Netty ( ). final String host = configuration.getString( AppConfiguration.WEBSERVER_HOST, AppConfiguration.WEBSERVER_HOST_DEFAULT); nettyServer.setHostname(host); final int port = configuration.getInt( AppConfiguration.WEBSERVER_PORT, AppConfiguration.WEBSERVER_PORT_DEFAULT); nettyServer.setPort(port); // JAX-RS. final ResteasyDeployment deployment = nettyServer.getDeployment(); // JAX-RS ( ). deployment.setInjectorFactoryClass(CdiInjectorFactory.class.getName()); // , JAX-RS . deployment.setApplicationClass(ReactReduxIsomorphicExampleApplication.class.getName()); // web . nettyServer.start(); ............... } /** * JAX-RS */ @ApplicationScoped @ApplicationPath("/") public class ReactReduxIsomorphicExampleApplication extends Application { /** * CDI Resteasy. */ @Inject private ResteasyCdiExtension extension; /** * @return JAX-RS. */ @Override @SuppressWarnings("unchecked") public Set<Class<?>> getClasses() { final Set<Class<?>> result = new HashSet<>(); // CDI Resteasy JAX-RS. result.addAll((Collection<? extends Class<?>>) (Object)extension.getResources()); // CDI Resteasy JAX-RS. result.addAll((Collection<? extends Class<?>>) (Object)extension.getProviders()); return result; } }
/** * . * <p> {filename}.{ext}</p> */ @Path("/") @RequestScoped public class StaticFilesResource { private final static Date START_DATE = DateUtils.setMilliseconds(new Date(), 0); @Inject private Configuration configuration; /** * . classpath. * @param fileName . * @param ext . * @param uriInfo URL , . * @param request . * @return 404 - . * @throws Exception . */ @GET @Path("{fileName:.*}.{ext}") public Response getAsset( @PathParam("fileName") String fileName, @PathParam("ext") String ext, @Context UriInfo uriInfo, @Context Request request) throws Exception { if(StringUtils.contains(fileName, "nomin") || StringUtils.contains(fileName, "server")) { // . return Response.status(Response.Status.NOT_FOUND) .build(); } // ifModifiedSince . classpath, // . final ResponseBuilder builder = request.evaluatePreconditions(START_DATE); if (builder != null) { // . return builder.build(); } // classpath. final String fileFullName = "webapp/static/" + fileName + "." + ext; // . final InputStream resourceStream = ResourceUtilities.getResourceStream(fileFullName); if(resourceStream != null) { // , . final String cacheControl = configuration.getString( AppConfiguration.WEBSERVER_HOST, AppConfiguration.WEBSERVER_HOST_DEFAULT); // . return Response.ok(resourceStream) .type(URLConnection.guessContentTypeFromName(fileFullName)) .cacheControl(CacheControl.valueOf(cacheControl)) .lastModified(START_DATE) .build(); } // . return Response.status(Response.Status.NOT_FOUND) .build(); } }
<plugin> <groupId>com.github.eirslett</groupId> <artifactId>frontend-maven-plugin</artifactId> <configuration> <nodeVersion>v${node.version}</nodeVersion> <npmVersion>${npm.version}</npmVersion> <installDirectory>${basedir}/src/main/frontend</installDirectory> <workingDirectory>${basedir}/src/main/frontend</workingDirectory> </configuration> <executions> <!-- nodejs npm . --> <execution> <id>nodeInstall</id> <goals> <goal>install-node-and-npm</goal> </goals> </execution> <!-- npm src/main/frontend/package.json. --> <execution> <id>npmInstall</id> <goals> <goal>npm</goal> </goals> </execution> <!-- webpack. --> <execution> <id>webpackBuild</id> <goals> <goal>webpack</goal> </goals> <configuration> <skip>${webpack.skip}</skip> <arguments>${webpack.arguments}</arguments> <srcdir>${basedir}/src/main/frontend/app</srcdir> <outputdir>${basedir}/src/main/resources/webapp/static/assets</outputdir> <triggerfiles> <triggerfile>${basedir}/src/main/frontend/webpack.config.js</triggerfile> <triggerfile>${basedir}/src/main/frontend/package.json</triggerfile> </triggerfiles> </configuration> </execution> </executions> </plugin>
/** * web-. */ public class ViewResult { private final String template; private final Map<String, Object> viewData = new HashMap<>(); private final Map<String, Object> reduxInitialState = new HashMap<>(); .............. } /** * , {@link ViewResult} HTML. * <p> * React HTML (React Isomorphic), * , React. * </p> */ @Provider @ApplicationScoped public class ViewResultBodyWriter implements MessageBodyWriter<ViewResult> { .............. private ObjectPool<AbstractScriptEngine> enginePool = null; @PostConstruct public void initialize() { // . final boolean useIsomorphicRender = configuration.getBoolean( AppConfiguration.WEBSERVER_ISOMORPHIC, AppConfiguration.WEBSERVER_ISOMORPHIC_DEFAULT); final int minIdleScriptEngines = configuration.getInt( AppConfiguration.WEBSERVER_MIN_IDLE_SCRIPT_ENGINES, AppConfiguration.WEBSERVER_MIN_IDLE_SCRIPT_ENGINES_DEFAULT); LOG.info("Isomorphic render: {}", useIsomorphicRender); if(useIsomorphicRender) { // React , // javascript . Javascript , // javascript. final GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMinIdle(minIdleScriptEngines); enginePool = new GenericObjectPool<AbstractScriptEngine>(new ScriptEngineFactory(), config); } } @PreDestroy public void destroy() { if(enginePool != null) { enginePool.close(); } } .............. @Override public void writeTo( ViewResult t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { .............. if(enginePool != null && t.getUseIsomorphic()) { // React . try { // javascript. final AbstractScriptEngine scriptEngine = enginePool.borrowObject(); try { // URL , react-router . final String uri = uriInfo.getPath() + (uriInfo.getRequestUri().getQuery() != null ? (String) ("?" + uriInfo.getRequestUri().getQuery()) : StringUtils.EMPTY); // React. final String htmlContent = (String)((Invocable)scriptEngine).invokeFunction( "renderHtml", uri, initialStateJson); // . enginePool.returnObject(scriptEngine); viewData.put(HTML_CONTENT_KEY, htmlContent); } catch (Throwable e) { enginePool.invalidateObject(scriptEngine); throw e; } } catch (Exception e) { throw new WebApplicationException(e); } } else { viewData.put(HTML_CONTENT_KEY, StringUtils.EMPTY); } // HTML . final String pageContent = StrSubstitutor.replace(templateContent, viewData); entityStream.write(pageContent.getBytes(StandardCharsets.UTF_8)); } /** * javascript. */ private static class ScriptEngineFactory extends BasePooledObjectFactory<AbstractScriptEngine> { @Override public AbstractScriptEngine create() throws Exception { LOG.info("Create new script engine"); // nashorn javascript . final AbstractScriptEngine scriptEngine = (AbstractScriptEngine) new ScriptEngineManager().getEngineByName("nashorn"); try(final InputStreamReader polyfillReader = ResourceUtilities.getResourceTextReader(WEBAPP_ROOT + "server-polyfill.js"); final InputStreamReader serverReader = ResourceUtilities.getResourceTextReader(WEBAPP_ROOT + "static/assets/server.js")) { // , nashorn, . scriptEngine.eval(polyfillReader); // , HTML React. scriptEngine.eval(serverReader); } // . ((Invocable)scriptEngine).invokeFunction( "initializeEngine", ResourceUtilities.class.getName()); return scriptEngine; } @Override public PooledObject<AbstractScriptEngine> wrap(AbstractScriptEngine obj) { return new DefaultPooledObject<AbstractScriptEngine>(obj); } } }
// global javascript . var global = this; // window javascript , , // . var window = this; // , Nashorn console. var console = { error: print, debug: print, warn: print, log: print }; // Nashorn setTimeout, callback - . function setTimeout(func, delay) { func(); return 0; }; function clearTimeout() { }; // Nashorn setInterval, callback - . function setInterval(func, delay) { func(); return 0; }; function clearInterval() { };
/** * HTML React. * @param {String} url URL . * @param {String} initialStateJson Redux JSON. * @return {String} HTML, React. */ renderHtml = function renderHtml(url, initialStateJson) { // JSON Redux. const initialState = JSON.parse(initialStateJson) // react-router ( ). const history = createMemoryHistory() // Redux , . const store = configureStore(initialState, history, true) // . const htmlContent = {} global.INITIAL_STATE = initialState // URL react-router. match({ routes: routes({history}), location: url }, (error, redirectLocation, renderProps) => { if (error) { throw error } // HTML React. htmlContent.result = ReactDOMServer.renderToString( <AppContainer> <Provider store={store}> <RouterContext {...renderProps}/> </Provider> </AppContainer> ) }) return htmlContent.result }
// Redux. const store = configureStore(initialState, history, false) // HTML, React. const contentElement = document.getElementById("content") // HTML React. ReactDOM.render(<App store={store} history={history}/>, contentElement)
{ "name": "java-react-redux-isomorphic-example", "version": "1.0.0", "private": true, "scripts": { "debug": "cross-env DEBUG=true APP_PORT=8080 PROXY_PORT=8081 webpack-dev-server --hot --colors --inline", "build": "webpack", "build:debug": "webpack -p" } }
devtool: isHot // "" . ? "module-source-map" // "module-eval-source-map" // production. : "source-map"
// . devServer: { // . hot: true, // . port: proxyPort, // . proxy: { "*": `http://localhost:${appPort}` } }
entry: { // . main: ["es6-promise", "babel-polyfill"] .concat(isHot // "" - . ? ["react-hot-loader/patch"] // . : []) .concat(["./src/main.jsx"]), // . [isProduction ? "server.min" : "server"]: ["es6-promise", "babel-polyfill", "./src/server.jsx"] }
output: { // . path: Path.join(__dirname, "../resources/webapp/static/assets/"), publicPath: isHot // "" ( ). ? `http://localhost:${proxyPort}/assets/` : "/assets/", filename: "[name].js", chunkFilename: "[name].js" }
{ // JavaScript (Babel). test: /\.(js|jsx)?$/, exclude: /(node_modules)/, use: [ { loader: isHot // "" babel. ? "babel-loader?plugins[]=syntax-dynamic-import,plugins[]=react-hot-loader/babel" : "babel-loader" } ] }
{ // CSS. test: /\.css$/, use: isHot // "" JavaScript . ? ["style-loader"].concat(cssStyles) // production - . : ExtractTextPlugin.extract({use: cssStyles, publicPath: "../assets/"}) }
// HTML React. ReactDOM.render(<App store={store} history={history}/>, contentElement) if (module.hot) { // "" . module.hot.accept("./containers/app", () => { const app = require("./containers/app").default ReactDOM.render(app({store, history}), contentElement) }) }
const store = createStore(reducers, initialState, applyMiddleware(...middleware)) if (module.hot) { // "" Redux-. module.hot.accept("./reducers", () => { const nextRootReducer = require("./reducers") store.replaceReducer(nextRootReducer) }) } return store
Source: https://habr.com/ru/post/327480/
All Articles