Java9 Module - Maven: ServiceLoader.load( HelloInterface.class) cannot find Interface class - java

In IntelliJ/Maven I want to work with Java9 / modules and the ServiceLoader. I built a simple 2 module project. Using maven the ServiceLoader.load( ...) cannot find the implementation of the HelloInterface.class. Why?
I created the project via IntelliJ by first adding the 2 modules. Rebuilding the project works fine. Clean/installing via Maven also works fine - well, the unit test fails ;-(
UPDATE 1: Running with IntelliJ is OK due to putting the module-info.java right under the sources root. I compile using the "Rebuild project".
How can I use this 2 module project with the ServiceLoader with Maven?
My simple project struture is:
pom.xml:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>14</source>
<target>14</target>
</configuration>
</plugin>
</plugins>
</build>
<modules>
<module>java9.com.hello</module>
<module>java9.com.hello.client</module>
</modules>
File: module java9.com.hello / src / main / java / module-info.jar
module java9.com.hello {
exports com.hello;
exports com.hello.services;
provides com.hello.services.HelloInterface with com.hello.services.HelloWorldIntefaceImpl;
}
File: module java9.com.hello / com.hello.services.HelloInterface.java
public interface HelloInterface {
String sayHello();
}
File: module java9.com.hello / com.hello.services.HelloWorldIntefaceImpl.java
public class HelloWorldIntefaceImpl implements HelloInterface {
public String sayHello() {
String helloString = "Hello world by Inteface!";
System.out.println( helloString);
return helloString;
}
}
File: module java9.com.hello / pom.xml (without the boilerplate)
<parent>
<artifactId>jdk-new-features</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>java9.com.hello</artifactId>
File: module java9.com.hello.client / src / main / java / module-info.jar.
This file is giving the compiler error.
module java9.com.hello.client {
requires java9.com.hello; // <==== gives the ERROR
uses com.hello.services.HelloInterface;
}
File: module java9.com.hello.client / com.hello.client.HelloWorldClient.java
public class HelloWorldClient {
public static void main (String arg[]) {
HelloWorld hello = new HelloWorld();
System.out.println(hello.sayHelloWorld());
Iterable<HelloInterface> services = ServiceLoader.load(HelloInterface.class);
HelloInterface service = services.iterator().next();
service.sayHello();
}
public String callService() {
Iterable<HelloInterface> services = ServiceLoader.load(HelloInterface.class);
HelloInterface service = services.iterator().next();
return service.sayHello();
}
}
The pom.xml is: module java9.com.hello.client / pom.xml:
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>java9.com.hello</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
Unit test file: module java9.com.hello.client / com.hello.client.HelloWorldClientTest.java
public class HelloWorldClientTest {
#Test
public void testModuleInterfaceImplementation() {
HelloWorldClient helloWorldClient = new HelloWorldClient();
assertEquals( "Hello world by Inteface!", helloWorldClient.callService());
}
}

Related

Changing Autowired bean type unexpectedly changes value?

I've tried to create an Application and ran into some funky behavior. First, I'll run through my setup. Here's my configuration class:
ProblemApp.java
#SpringBootApplication
public class ProblemApp
{
public static void main(String[] args)
{
var context = SpringApplication.run(ProblemApp.class);
var tblController = context.getBean(TableController.class);
tblController.printTable();
}
#Bean
public TableController getTableController()
{
return new TableController();
}
#Bean("componentTable")
public String[] getComponentTable() //weird
{
return new String[] { "application.components" };
}
}
Here's my component:
TableController.java
#Controller
public class TableController
{
private static final Logger log = LoggerFactory.getLogger(TableController.class);
#Autowired
private String[] componentTable; //weird
public void printTable()
{
log.info("Component table: " + Arrays.deepToString(componentTable));
}
}
This is my module-info.java and directory structure:
module problem.application
{
exports application;
exports controller;
opens controller to spring.core;
opens application to spring.core;
requires spring.context;
requires spring.boot;
requires spring.boot.autoconfigure;
requires spring.beans;
requires spring.core;
requires java.sql;
requires org.slf4j;
}
src/main/java/
application
ProblemApp.java
controller
TableController.java
module-info.java
src/test/java/ is empty
src/main/resources/ is empty
This is the pom.xml I use to retrieve dependencies (using Maven in Spring Tool Suite 4):
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>problem.application</groupId>
<artifactId>problem-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Problematic application</name>
<description>An application with some problems</description>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>12</release>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
This code, as it is, works as I expected it to. The relevant part of the output is:
2019-09-11 10:55:21.351 INFO 21060 --- [ main] controller.TableController :
Component table: [application.components]
Now, I tried to change the type of Bean componentTable to Object[]. In ProblemApp:
#Bean("componentTable")
public Object[] getComponentTable() //weird
{
return new Object[] { "application.components" };
}
In TableController:
#Autowired
private Object[] componentTable; //weird
Suddenly, the output of the program changes drastically:
2019-09-11 10:57:05.630 INFO 10156 --- [ main] controller.TableController :
Component table: [org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#b40bb6e,
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#3f28bd56,
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#3276732,
org.springframework.context.annotation.ConfigurationClassPostProcessor#f74e835,
org.springframework.context.support.PropertySourcesPlaceholderConfigurer#48c35007,
org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar#4b3c354a,
org.springframework.context.event.EventListenerMethodProcessor#31e3250d,
org.springframework.context.event.DefaultEventListenerFactory#19fe4644,
application.ProblemApp$$EnhancerBySpringCGLIB$$7e1f363f#21d8bcbe,
...
// more Spring-looking things and then environment information
What is going on here?
When auto-wring on an array or a list of type T , it will first try to inject all beans which the type is T into it. So for :
#Autowired
private String[] componentTable;
It will first try to inject all beans which the type is String . However since there are no such beans, it will then try to inject a bean which the type is String[] . As you define componentTable bean as String[] , it will be injected.
Follow the same logic on :
#Autowired
private Object[] componentTable;
It will first try to inject all beans which the type is Object. As every bean must be an Object type . It means all spring beans will be injected. Hence it prints out all the spring bean information.

How to solve unable to drive module description for ../selenium-server-standalone-3.141.59.jar [duplicate]

This question already has answers here:
Unable to derive module descriptor: Provider {class X} not in module
(2 answers)
Closed 1 year ago.
Error occurred during initialization of boot layer
java.lang.module.FindException: Unable to derive module descriptor for C:\Users\admin\eclipse-workspace\Testing\lib\selenium-server-standalone.jar
Caused by: java.lang.module.InvalidModuleDescriptorException: Provider class org.eclipse.jetty.http.Http1FieldPreEncoder not in module
package Testing;
import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
public class Testing {
public static void main(String[] args) throws InterruptedException
{
Selenium selenium= new DefaultSelenium("localhost",4444,"firefox","http://www.calculator.net");
selenium.start();
selenium.open("/");
selenium.windowMaximize();
selenium.click("xpath=.//*[#id=''hl3']/li[3]/a");
Thread.sleep(4000);
selenium.focus("id=cpar1");
selenium.type("css=input[id=\"cpar1\"]", "10");
selenium.focus("id=cpar2");
selenium.type("css=input[id=\"cpar2\"]", "50");
(selenium).click("xpath=.//*[#id='content']/table[1]/tbody/tr[2]/td/input[2]");
// verify if the result is 5
Thread.sleep(4000);
String result = selenium.getText("xpath=.//*[#id='content']/p[2]/font/b");
//String result = selenium.getValue("xpath=.//*[#id='cpar3']");
System.out.println("Result:"+result);
if (result.equals("5")/*== "5"*/){
System.out.println("Pass");
}
else{
System.out.println("Fail");
}
}
}
I would recommend reconsidering using Selenium Remote Control as it is quite outdated approach which is no longer supported, current stable version of Selenium Java client is 3.141.59 and it provides WebDriver API which is a W3C Standard as of now.
Once you implement option 1 get rid of those Thread.sleep() as it's a some form of a performance anti-pattern, go for Explicit Wait instead, check out How to use Selenium to test web applications using AJAX technology for comprehensive explanation and code examples.
It's better to use a dependency management solution like Apache Maven which will automatically detect and download your project transitive dependencies. A relevant pom.xml file would be something like:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>selenium</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
</dependency>
</dependencies>
</project>

multimodule project: reuse static method between test classes from different modules

I have a multimodule java maven project. I added a static method to junit class of module A. Now I would like to reuse this static method from the juinit test of module B.
module 1:
public class AccountDAOTest {
private static Faker faker = new Faker();
public static Account getRandomAccount() {
Account account = new Account();
account.set...(faker.idNumber().valid());
...
return account;
}
#Test
public void getByName() {
Account expected = getRandomAccount();
accountDAO.persist(expected);
assertNotEquals(expected.getId(), null);
Account actual = accountDAO.getByName(expected.getName());
assertNotNull(actual);
}
...
}
module 2 (trying to reuse the static method):
public class BusinessBeanTest {
#Test
public void testSomething() {
Account account = AccountDAOTest.getRandomAccount();
...
}
}
The problem is that the AccountDAOTest.java is not in the class path of module 2 test either I add module dependency in the pom.xml of module 2 with test scope.
I can see only two solutions:
duplicate this metod and copy it from module 1 test class to module 2 test class
remove getRandomAccount() method from test class and add it to a common module as a real code.
None of the above two solitions looks good.
Any idea hot to do this on a correct way?
How about creating jar only with tests and then include it to the other module?
For Module 1:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
For Module 2:
<dependency>
<groupId>groupId</groupId>
<artifactId>artifactId</artifactId>
<classifier>tests</classifier>
<type>test-jar</type>
<version>version</version>
<scope>test</scope>
</dependency>

IncompatibleClassChangeError when instrumenting running SpringBoot application with Byte Buddy

I would like to introduce Byte Buddy to my company and I have prepared a demo for my colleagues. Since we use Spring a lot, I thought the best example would be instrumentation of SpringBoot application. I have decided to add logs to RestController methods.
Instrumented application is a simple SpringBoot Hello World example:
#RestController
public class HelloController {
private static final String template = "Hello, %s!";
#RequestMapping("/hello")
public String greeting(
#RequestParam(value = "name", defaultValue = "World") String name) {
return String.format(template, name);
}
#RequestMapping("/browser")
public String showUserAgent(HttpServletRequest request) {
return request.getHeader("user-agent");
}
}
And here is my Byte Buddy agent:
public class LoggingAgent {
public static void premain(String agentArguments,
Instrumentation instrumentation) {
install(instrumentation);
}
public static void agentmain(String agentArguments,
Instrumentation instrumentation) {
install(instrumentation);
}
private static void install(Instrumentation instrumentation) {
createAgent(RestController.class, "greeting")
.installOn(instrumentation);
}
private static AgentBuilder createAgent(
Class<? extends Annotation> annotationType, String methodName) {
return new AgentBuilder.Default().type(
ElementMatchers.isAnnotatedWith(annotationType)).transform(
new AgentBuilder.Transformer() {
#Override
public DynamicType.Builder<?> transform(
DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassLoader classLoader) {
return builder
.method(ElementMatchers.named(methodName))
.intercept(
MethodDelegation
.to(LoggingInterceptor.class)
.andThen(
SuperMethodCall.INSTANCE));
}
});
}
}
Interceptor logs method execution:
public static void intercept(#AllArguments Object[] allArguments,
#Origin Method method) {
Logger logger = LoggerFactory.getLogger(method.getDeclaringClass());
logger.info("Method {} of class {} called", method.getName(), method
.getDeclaringClass().getSimpleName());
for (Object argument : allArguments) {
logger.info("Method {}, parameter type {}, value={}",
method.getName(), argument.getClass().getSimpleName(),
argument.toString());
}
}
When executed with -javaagent parameter this example works well. When however I try to load the agent on the running JVM with Attach API:
VirtualMachine vm = VirtualMachine.attach(args[0]);
vm.loadAgent(args[1]);
vm.detach();
I've got the following exception on the first logging attempt:
Exception in thread "ContainerBackgroundProcessor[StandardEngine[Tomcat]]" java.lang.IncompatibleClassChangeError: Class ch.qos.logback.classic.spi.ThrowableProxy does not implement the requested interface ch.qos.logback.classic.spi.IThrowableProxy
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.subjoinExceptionMessage(ThrowableProxyConverter.java:180)
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.subjoinFirstLine(ThrowableProxyConverter.java:176)
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.recursiveAppend(ThrowableProxyConverter.java:159)
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.throwableProxyToString(ThrowableProxyConverter.java:151)
at org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter.throwableProxyToString(ExtendedWhitespaceThrowableProxyConverter.java:35)
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.convert(ThrowableProxyConverter.java:145)
at ch.qos.logback.classic.pattern.ThrowableProxyConverter.convert(ThrowableProxyConverter.java:1)
at ch.qos.logback.core.pattern.FormattingConverter.write(FormattingConverter.java:36)
at ch.qos.logback.core.pattern.PatternLayoutBase.writeLoopOnConverters(PatternLayoutBase.java:114)
at ch.qos.logback.classic.PatternLayout.doLayout(PatternLayout.java:141)
at ch.qos.logback.classic.PatternLayout.doLayout(PatternLayout.java:1)
at ch.qos.logback.core.encoder.LayoutWrappingEncoder.doEncode(LayoutWrappingEncoder.java:130)
at ch.qos.logback.core.OutputStreamAppender.writeOut(OutputStreamAppender.java:187)
at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:212)
at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:100)
at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:84)
at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:48)
at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:270)
at ch.qos.logback.classic.Logger.callAppenders(Logger.java:257)
at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:421)
at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:383)
at ch.qos.logback.classic.Logger.log(Logger.java:765)
at org.slf4j.bridge.SLF4JBridgeHandler.callLocationAwareLogger(SLF4JBridgeHandler.java:221)
at org.slf4j.bridge.SLF4JBridgeHandler.publish(SLF4JBridgeHandler.java:303)
at java.util.logging.Logger.log(Unknown Source)
at java.util.logging.Logger.doLog(Unknown Source)
at java.util.logging.Logger.logp(Unknown Source)
at org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:181)
at org.apache.juli.logging.DirectJDKLog.error(DirectJDKLog.java:147)
at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1352)
at java.lang.Thread.run(Unknown Source)
I run the example on 64-bit HotSpot with Java8:
java version "1.8.0_112"
Java(TM) SE Runtime Environment (build 1.8.0_112-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)
Byte Buddy version is 1.4.32. Here is agent maven configuration:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.halun.demo.bytebuddy</groupId>
<artifactId>byte-buddy-agent-demo</artifactId>
<version>1.0</version>
<properties>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.4.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>${project.artifactId}-${project.version}-full</finalName>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifestEntries>
<Premain-Class>pl.halun.demo.bytebuddy.logging.LoggingAgent</Premain-Class>
<Agent-Class>pl.halun.demo.bytebuddy.logging.LoggingAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
And here is pom file for the instrumented application:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.halun.demo.bytebuddy.instrumented.app</groupId>
<artifactId>byte-buddy-agent-demo-instrumented-app</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
From my point of view it is very valuable option to add logs on the running server and I hate to loose this part of demo. I tried to experiment with different redefinition strategies, but until now nothing seems to work.
What you observe is a classical version conflict, I think. Spring Boot most likely comes with a version of ThrowableProxy that is not compatible to the version that is added with the Java agent. When loading a Java agent at runtime, Spring's version is already loaded whereas the startup attachment prepends the agent-bundled version on the class path where the version of your agent is loaded.
Java agents are typically added to the class path. This is also where your Spring boot application is living. You need to make sure that a Java agent does not contain dependencies that are incompatible with your application's dependencies or you need to shade all dependencies to avoid such conflicts.
There is however another problem: When writing a Java agent that is attached at runtime, you meet additional constraints on most JVMs where on HotSpot, you are not allowed to change the class file format of any class that is already loaded. There is also a chance that your class is already loaded where currently, no effect would be visible as you do not enable retransformation.
A runtime-capable agent would need to use the Advice component which inlines code into target code rather then using the classical delegation model:
class MyAdvice {
#Advice.OnMethodEnter
static void intercept(#Advice.BoxedArguments Object[] allArguments,
#Advice.Origin Method method) {
Logger logger = LoggerFactory.getLogger(method.getDeclaringClass());
logger.info("Method {} of class {} called", method.getName(), method
.getDeclaringClass().getSimpleName());
for (Object argument : allArguments) {
logger.info("Method {}, parameter type {}, value={}",
method.getName(), argument.getClass().getSimpleName(),
argument.toString());
}
}
}
You can use the above advice class by registering it as a visitor. Such visitors only apply to declared methods, i.e. not to inherited methods and inline their code into existing methods. This way, the logging will not be visible on the call stack and it also becomes legal to retransform already loaded classes:
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.type(isAnnotatedWith(annotationType))
.transform(new AgentBuilder.Transformer() {
#Override
public DynamicType.Builder<?> transform(
DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassLoader classLoader) {
return builder.visit(Advice.to(MyAdvice.class).on(named(methodName)));
}
});
As for the attachment, look into the byte-buddy-agent project which allows you to call:
ByteBuddyAgent.attach(agentJar, processId);
The above helper supports other VMs where the attachment API often lives in a different namespace.
Update: Here is the problem with Spring Boot. Spring Boot creates custom class loaders that have the system class loader (class path) as their parent. These class loaders consider classes from the system class loader first. When you add the agent, the entire Spring boot app is both on the class loader and in these child class loaders. A class like IThrowableProxy now exists twice in two class loaders but is not considered to be equal by the JVM. Depending on the state of the VM, some classes might already be linked to the original IThrowableProxy whereas other classes are loaded after the agent was attached and get linked to the new IThrowableProxy from the agent. Both classes are not equal and the error that you see is thrown where the VM complains that the class does not implement the correct IThrowableProxy (but the previous one). If the agent is attached at start up, this problem does not exist as the class path's IThrowableProxy is always loaded.
This is not an easy error to fix, in the end, Byte Buddy cannot help you with such class path issues and Spring Boot is quite free in its interpretation of the class loader contract. The easiest way would be to not use Spring Boot types in your agent. You can still match the annotation with for example
isAnnotatedWith(named("org.springframework.web.bind.annotation.RestController"))
The question is how you can communicate with Spring Boot. One work-arround would be to add all shared classes to the class path on start up. Typically, I do avoid the usage of shared classes altogether but only use them in the Advice classes where the code is inlined in the class loader of the target application. Simply set the Spring Boot dependency in provided scope, the advice code itself is never executed.

Deploy a Java EE project as jar with Maven and perform the DI

I've just developed a sample Java EE 7 application.
The code is as follows:
#Stateless
#LocalBean
public class Foo {
#Inject
private Boo boo; // Internal resource
#Asynchronous
public void doFoo(Collection<Object> c) {
boo.doSomething(c);
}
}
With the aim to deploy the project as jar file, I'm using the following Maven configuration:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sample</groupId>
<artifactId>ejb-foo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>ejb-foo</finalName>
</build>
<properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
</project>
Unfortunately, Maven returns me this warning:
Classpath entry org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER will not be exported or published. Runtime ClassNotFoundExceptions may result. ejb-foo P/ejb-foo Classpath Dependency Validator Message
How can I fix this error?
Note:
The idea is to import that jar into another Java project and then to instance the Foo class as EJB:
import myjavaeeproject.Foo;
public OtherClass {
#EJB
private Foo foo;
public void doMagic(List<String> list) {
foo.doFoo(list);
}
}
Update:
I've fixed the error as shown here.
When I deploy (as war) the target project (that implements OtherClass, annotated as WebServlet) on JBoss, I've an error:
POST_MODULE: JBAS018733: Failed to process phase POST_MODULE of deployment
It depends on the EJB injection.
What am I doing wrong?
As per my examples in comment, it is because eclise think "the library exists at the server and it is not right to export this with your projects"
I don't have much idea about your code, but seems to be ok.
If this is a J2EE application, I would expect the target to be a war or ear. I don't think that a J2EE container will understand a jar deployment.

Categories