I am trying to enable loadtimeweaving without javaagent jar files of aspectweaver and spring-instrument. This what I have implemented to achieve the same But it's not working.
#ComponentScan("com.myapplication")
#EnableAspectJAutoProxy
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT)
public class AopConfig implements LoadTimeWeavingConfigurer {
#Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
/**
* Makes the aspect a Spring bean, eligible for receiving autowired components.
*/
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
}
A workaround I found was to hot-attach InstrumentationSavingAgent from spring-instrument instead of starting the agent via -javaagent command line parameter. But for that you need an Instrumentation instance. I just used the tiny helper library byte-buddy-helper (works independently of ByteBuddy, don't worry) which can do just that. Make sure that in Java 9+ JVMs the Attach API is activated if for some reason this is not working.
So get rid of implements LoadTimeWeavingConfigurer and the two factory methods in your configuration class and just do it like this:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.14</version>
</dependency>
#SpringBootApplication
public class Application {
public static void main(String[] args) {
Instrumentation instrumentation = ByteBuddyAgent.install();
InstrumentationSavingAgent.premain("", instrumentation);
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// ...
}
}
Feel free to ask follow-up questions if there is anything you do not understand.
Update: One more thing I noticed is that this only works for me with aspectjWeaving = ENABLED, not with AUTODETECT. And for one sample Spring bean I noticed that #Component did not work, probably because of some bootstrapping issue between Spring vs AspectJ. Hence, I replaced it by an explicit #Bean configuration, then it worked. Something like this:
#Configuration
#ComponentScan("com.spring.aspect.dynamicflow")
#EnableLoadTimeWeaving(aspectjWeaving = ENABLED)
public class ApplicationConfig {
#Bean
public JobProcess jobProcess() {
return new JobProcessImpl();
}
}
Related
First I have looked through the other answers and tried everything I could find nothing seems to work.
I have the following simple JPARepository
#Repository
public interface ResultsRepository extends JpaRepository<User, String> {}
I have a service like...
#Service
public class Service {
#Autowired
ResultsRepository repo;
....
}
this is called from a CommandLineRunner...
#Component
public class OTMRunner implements CommandLineRunner {
private static final Logger logger = LogManager.getLogger(OTMRunner.class);
#Autowired
private Service service;
#Override
public void run(String... args) throws Exception {
...
}
}
And an Application where I am setting the scan package manually just to be sure...
// Also tried without scan base package.
#SpringBootApplication(
scanBasePackages = {"my.package.*"}
)
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.web(WebApplicationType.NONE)
.run(args);
}
}
I have the following in my pom...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Everything seems to run fine when I use a JUnit IT but I need to run it from the cmd line to add ars and properties to the classpath....
java -cp "JAR_LOCATION;PROPERTIES_LOCATION" my.package.Application PARAM
A couple of important points...
I Cannot use mvn spring-boot:run or java -jar
This does not work because I can't dynamically add to the classpath. Also I don't seem to be able to pass in Param.
I also already tried
#EnableJpaRepositories("my.package.repository")
#EntityScan(
basePackages = {"my.package"}
)
On the application but that did not help either. Can anyone see what I am missing?
UPDATE
I can confirm the only change needed to get it to "work" is to comment out the #Repository which means it is something related to the way Spring is wiring the interfaces.
I'm implementing micrometer to spring web project. While trying to add #Timed annotation. Prior to add #Timed i'm supposed to create TimedSpect bean. But it says could not autowire no bean of MeterRegistry type found
#Configuration
public class MetricsCofiguration {
#Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
Not sure if you still need this answer but this is what i tried and its working fine.
#Configuration
#EnableAspectJAutoProxy
public class TimedConfiguration {
#Autowired
MeterRegistry registry;
#Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
Make sure you have below starter dependency in your pom.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
I'm doing my fist steps with Springs AOP and wanted to start with a simple logging advice. My project is a multi module maven project with the following structure:
parentProject
|__aop
|__data
|__web
The web module has a user service class in the package de.my.awsome.project.web.service with a saveNewUser Method:
#Service
public class UserService {
...
public MdUser saveNewUser(UserModel user) {
MdUser newUser = this.save(user);
createGroupMembership(user, newUser);
return newUser;
}
}
The method works as expected so don't bother with details about that.
What I've done now is to create the following class in the aop module:
#Component
#Aspect
public class LoggingAspect {
Logger logger = Logger.getLogger(getClass());
#Before("execution(public * de.my.awsome.project.web.service.UserService.saveNewUser(..))")
public void newUserLog(JoinPoint joinpoint) {
logger.info(joinpoint.getSignature() + " with user " + joinpoint.getArgs()[0]);
}
}
I added a dependency for the web module in the pom of the aop module:
<dependency>
<groupId>de.my.awsome.project</groupId>
<artifactId>web</artifactId>
<version>${project.version}</version>
</dependency>
I even wrote a ConfigurationClasse even though I thought this would not be necessary with SpringBoot:
#Configuration
#ComponentScan(basePackages="de.fraport.bvd.mobisl.aop")
public class AspectsConfig {
}
The expected result is a log-message like "saveNewUser with user xyz". But the logging method is never called. What have I missed to do?
#Configuration - Indicates that this file contains Spring Bean Configuration for an Aspect.
Replace #Component with #Configuration for LoggingAspect.
Well, the answer that #sankar posted didn't work either but I found the solution by myself.
I had to add a dependency to my aop module in the web modules pom, not vice versa. Then I added an Import of my AspectsConfig to the web modules SpringBootApplication class and it worked.
#SpringBootApplication
#Import(value= {JPAConfig.class, AspectsConfig.class})
#EnableAspectJAutoProxy
public class WebApplication {
#Autowired
private JPAConfig config;
#Autowired
private AspectsConfig aspectConfig;
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
The following steps work for me - see below if anyone is looking for a solution
add the aop dependency in to the main parent pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
add your Aspect implementation in he aop-sub-module
#Aspect
#Component
public class SampleAspect {
#After("execution(* de.my.awsome.project.testMethod())")
public void logAuditActivity(JoinPoint jp) {
System.out.println("TESTING ****************");
System.out.println("The method is called");
}
In your other submodule (web-submodule) add the dependancy of the aspect-submodule
<dependency>
<groupId>de.my.awsome.project</groupId>
<artifactId>aop</artifactId>
</dependency>
In your web Project include the aop package for component scan
eg. if SampleAspect is under a package de.my.awsome.project you will need to add in as follow
#ComponentScan(basePackages = {"de.my.awsome.project", "de.my.awsome.aop" }
clean build and run the app
I'm using Spring Boot 1.5.2 with Logback, which is configured using a logback-spring.xml. There, I define an appender of a custom type (subclass of RollingFileAppender) and would like to get a pair of beans injected.
Is this possible? I naively tried annotating the appender #Component etc. but as it is created by Logback/Joran, it of course doesn't work. Is there a trick I can apply?
If not possible, what would be the canonical way of achieving my goal (inserting beans from the application context into an appender)?
As mentioned also in the question, by default, Logback instantiates and manages the lifecycle of different logging components (appenders, etc) itself. It knows nothing of Spring. And, Logback typically configures itself way before Spring is started (as Spring also uses it for logging).
So, you cannot really use Spring to configure an instance of FileAppender (or some other rather fundamental appender) and then inject that into Logback.
However, in case your appender is not truly fundamental (or you are happy to ignore logging events during Spring Boot startup), you can follow the "simple" approach below. In case you would like to capture all events (including the ones during startup), keep on reading.
Simple approach (loses events during startup)
Create your appender as a Spring component:
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
#Component
public class LogbackCustomAppender extends UnsynchronizedAppenderBase<ILoggingEvent> implements SmartLifecycle {
#Override
protected void append(ILoggingEvent event) {
// TODO handle event here
}
#Override
public boolean isRunning() {
return isStarted();
}
}
As you can see, it is annotated with #Component so that Spring will pick it up during classpath scanning. Also, it implements SmartLifecycle so that Spring will call Logback Lifecycle interface methods (luckily, start() and stop() methods have exactly the same signature, so we only need to implement isRunning() which will delegate to Logback isStarted()).
Now, by the end of Spring application context startup, we can retrieve the fully initialized LogbackCustomAppender instance. But Logback is blissfully unaware of this appender, so we need to register it with Logback.
One way of doing this is within your Spring Boot Application class:
#SpringBootApplication
#ComponentScan(basePackages = {"net.my.app"})
public class CustomApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(CustomApplication.class, args);
context.start();
addCustomAppender(context, (LoggerContext) LoggerFactory.getILoggerFactory());
}
private static void addCustomAppender(ConfigurableApplicationContext context, LoggerContext loggerContext) {
LogbackErrorCollector customAppender = context.getBean(LogbackCustomAppender.class);
Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.addAppender(customAppender);
}
}
No need to change anything in your Logback configuration file.
More complicated approach (captures all events)
As mentioned above, you might be interested in not losing the events logged during Spring Boot startup.
For this, you could implement a placeholder appender (that would buffer startup events internally):
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import java.util.ArrayList;
public class BufferingAppenderWrapper<E> extends UnsynchronizedAppenderBase<E> {
private final ArrayList<E> eventBuffer = new ArrayList<>(1024);
private Appender<E> delegate;
#Override
protected void append(E event) {
synchronized (eventBuffer) {
if (delegate != null) {
delegate.doAppend(event);
}
else {
eventBuffer.add(event);
}
}
}
public void setDelegateAndReplayBuffer(Appender<E> delegate) {
synchronized (eventBuffer) {
this.delegate = delegate;
for (E event : this.eventBuffer) {
delegate.doAppend(event);
}
this.eventBuffer.clear();
}
}
}
We register that appender with Logback the usual way (e.g. logback.xml):
<appender name="DELEGATE" class="my.app.BufferingAppenderWrapper" />
<root level="INFO">
<appender-ref ref="DELEGATE" />
</root>
After Spring has started, look that appender up by name and register your Spring-configured appender with the placeholder (flushing the buffered events in the process):
#SpringBootApplication
#ComponentScan(basePackages = {"net.my.app"})
public class CustomApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(CustomApplication.class, args);
context.start();
addCustomAppender(context, (LoggerContext) LoggerFactory.getILoggerFactory());
}
private static void addCustomAppender(ConfigurableApplicationContext context, LoggerContext loggerContext) {
LogbackErrorCollector customAppender = context.getBean(LogbackCustomAppender.class);
Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
BufferingAppenderWrapper<ILoggingEvent> delegate = (BufferingAppenderWrapper<ILoggingEvent>) rootLogger.getAppender("DELEGATE");
delegate.setDelegateAndReplayBuffer(customAppender);
}
}
LogbackCustomAppender stays the same.
It isn't possible to do what you are trying to do. Logback is initialised before the application context is created so there's nothing to perform the dependency injection.
Perhaps you could ask another question describing what you'd like your appender to be able to do? There may be a solution that doesn't involve injecting Spring-managed beans into it.
Using logback-extensions you can create your appenders in a spring application context file or in a spring config factory.
Try defining a bean like this and calling the static getBean method on it, instead of using dependency injection:
#Component
public class BeanFinderGeneral implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
#Override
public void setApplicationContext(ApplicationContext pApplicationContext) throws BeansException {
applicationContext = pApplicationContext;
}
}
In Spring boot you can write a configuration class and create a Bean of your logback class as below:
#Component
#Configuration
public class LogBackObjectBuilder {
#Bean
public RollingFileAppender myRollingFileAppender() {
return new YOUR-SUB-CLASS-OF-RollingFileAppender();
}
}
Just having this class scanned by spring will cause this Bean to be created and injected in the context.
I hope I understood your question right. You want your custom appender to be injected in the application context.
I'm trying to setup a #Configurable domain object(not managed by the spring container).
I've got this working by adding the -javaagent:path/to/spring-instrument.jar as a JVM argument but it's not 100% clear to me whether or not this -javaagent MUST be in place. I'm running this on Tomcat 8. I may be misinterpreting the documentation but it seems I may be able to use a another mechanism to accomplish this, in particular this line:
Do not define TomcatInstrumentableClassLoader anymore on Tomcat 8.0 and higher. Instead, let Spring automatically use Tomcat’s new native InstrumentableClassLoader facility through the TomcatLoadTimeWeaver strategy.
Code Samples below:
#SpringBootApplication
#EnableLoadTimeWeaving
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean
public MyService myService(){
return new MyService();
}
}
#Configurable
public class MyDomainObject {
#Autowired
private MyService myService;
public MyService getMyService(){
return myService;
}
}
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
public void test(){
log.info("test");
}
}
So is there a way to get these #Configrable objects woven without specifying the -javaagent? I'd be interested in learning if I can accomplish this when deploying as WAR to a Standalone Tomcat 8 server and/or using the embedded Tomcat 8 server when launching as a 'fat' jar.
As it stands deploying to Stand alone Tomcat 8 server doesn't throw an error but the getMyService() method above returns null. Launching as a fat jar throws the following error during startup:
Caused by: java.lang.IllegalStateException: ClassLoader [sun.misc.Launcher$AppClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:org.springframework.instrument.jar
I suppose the real question is how do I Specify a custom LoadTimeWeaver in Tomcat 8? Nothing seems to be automatically happening as the documentation states but again I may be misinterpreting what that means exactly.
you can try this :
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() {
return new InstrumentationLoadTimeWeaver();
}
or
there is a new library that just solves to dynamically setup spring InstrumentationLoadTimeWeaver to enable support for aspects without having to start the JVM with an explicit java agent
<dependency>
<groupId>de.invesdwin</groupId>
<artifactId>invesdwin-instrument</artifactId>
<version>1.0.2</version>
</dependency>
#SpringBootApplication
#EnableLoadTimeWeaving
public class TestApplication{
public static void main(final String[] args) {
DynamicInstrumentationLoader.waitForInitialized(); //dynamically attach java agent to jvm if not already present
DynamicInstrumentationLoader.initLoadTimeWeavingContext(); //weave all classes before they are loaded as beans
SpringApplication.run(TestApplication.class, args); //start application, load some classes
}
}
what about creating your own annotation #MyConfigurable ? so you can do what ever you like when it's methods are called.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.CONSTRUCTOR)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyConfigurable
{}