Aspect does not work with Spring boot application with external jar - java

I am trying to create a timer aspect for measuring methods run time.
I created an annotation named #Timer:
#Retention(RetentionPolicy.RUNTIME)
#Target(value = {ElementType.METHOD, ElementType.TYPE})
public #interface Timer {
String value();
}
And then I created the aspect as follows:
#Aspect
public class MetricAspect {
#Autowired
private MetricsFactory metricsFactory;
#Pointcut("#annotation(my.package.Timer)")
public void timerPointcut() {}
#Around("timerPointcut() ")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
/* Aspect logic here */
}
private Timer getClassAnnotation(MethodSignature methodSignature) {
Timer annotation;
Class<?> clazz = methodSignature.getDeclaringType();
annotation = clazz.getAnnotation(Timer.class);
return annotation;
}
I have a configuration class as follows:
#Configuration
#EnableAspectJAutoProxy
public class MetricsConfiguration {
#Bean
public MetricAspect notifyAspect() {
return new MetricAspect();
}
}
Everything up until here is defined in a packaged jar which I use as a dependency in my spring boot application
In my spring boot application I import the MetricsConfiguration and I debugged the code and saw that the MetricAspect bean is created.
I use it in code as follows:
#Service
public class MyService {
...
#Timer("mymetric")
public void foo() {
// Some code here...
}
...
}
But my code doesn't reach to the measure method. Not sure what I'm missing.
For completing the picture, I have these dependencies in my pom file added:
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.4</version>
</dependency>
</dependencies>
That's the #Configuration class that imports MetricsConfiguration:
#Configuration
#EnableAspectJAutoProxy
#Import(MetricsConfiguration.class)
#PropertySource("classpath:application.properties")
public class ApplicationConfiguration {
}
It's loaded with Spring's automagically configuration loading.

can #Component or #Configurable solve your issue?
#Aspect
#Component
public class yourAspect {
...
}
Enable Spring AOP or AspectJ
EDIT:
I created a project to simulate your issue, seems no problem after all. Is it affected by other issue?
https://github.com/zerg000000/spring-aspectj-test

I was unable to reproduce your problem using aspectJ 1.8.8 and spring 4.2.5. Here is my maven multi-module approach with aspect in separate jar.
I modified your code slightly but did not change any annotations. The only thing that might be differ is that I've added org.springframework:spring-aop dependency and defined my entrypoint as follows:
#Import(MetricsConfiguration.class)
#SpringBootApplication
public class Application {
// #Bean definitions here //
public static void main(String[] args) throws InterruptedException {
ApplicationContext ctx =
SpringApplication.run(Application.class, args);
ctx.getBean(MyService.class).doWork();
}
}

I had a similar problem where the aspect was built in a jar library, and the spring-boot application was else where. Turns out that the packages for the spring-boot application and the jar library were different. Due to which Spring was not looking into the package of the library to autowire into the application context.
So, had to include #ComponentScan({"base.package.application.*", "base.package.library.*"}) in the Application.java

If the external jar is Spring boot starter, you can config Aspect bean in AutoConfiguration:
(1)
#Aspect
public class MyAspect {
//....
}
(2)
package a.b.c
#Configuration
public class MyAutoConfiguration {
#Bean
MyAspect myAspect() {
return new MyAspect();
}
}
(3)config in spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
a.b.c.MyAutoConfiguration
If the external jar is not a Spring boot starter, just load Aspect as a bean, you can use #ComponentScan

Add this componentScan to resolve the issue.
#ComponentScan("package.of.aspect")
#Configuration
#EnableAspectJAutoProxy
#Import(MetricsConfiguration.class)
#PropertySource("classpath:application.properties")
public class ApplicationConfiguration {
}

Debugging spring-boot Aspectj aspects when pointcut itself has problems is not easy even with detailed logging: How to debug Spring AOP
Unfortunately, spring boot + spring-aop with annotations don't have many ways to debug aspects and why some classes, especially non-spring compoment jar classes, are not scanned, such as jar classes whose methods are in abstract classes or static final methods need the right pointcuts to work covering all classes/implementations even if they are component scanned.
The best way to debug an Asepct (or use core AOP and avoid spring-aop) is to enable aop.xml with configuration control using org/aspectj/aop.xml or META-INF/aop.xml, using the LTW aspectj weaver
-Daj.weaving.verbose=true -javaagent:~/.m2/repository/org/aspectj/aspectjweaver/1.9.5/aspectjweaver-1.9.5.jar
To debug all aspects/classes with Debug/Verbose logs to see why some classes are not being scanned:
...
this almost always helps figuring out the problem with the pointcut or class not getting picked.
Or, just use LTW aop, see, https://github.com/dsyer/spring-boot-aspectj

Adding
ComponentScan(basePackages = "com.github.something.annotation")
basePackages is the package where your aspect resides.
This solution work for me.

You need put #ComponentScan on MetricsConfiguration, as #Configuration will not automatically scan and load component.
I have tested, and it worked!

according to mojohaus explaination, you have to add build settings like below to Woven your aspect into all classes implementing your aspect interface.
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<includes>
<include>**/*.java</include>
<include>**/*.aj</include>
</includes>
<aspectDirectory>src/main/aspect</aspectDirectory>
<testAspectDirectory>src/test/aspect</testAspectDirectory>
<XaddSerialVersionUID>true</XaddSerialVersionUID>
<showWeaveInfo>true</showWeaveInfo>
<aspectLibraries>
<aspectLibrary>
<groupId>your aspect groupId</groupId>
<artifactId>your aspect artifactId</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<id>compile_with_aspectj</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile_with_aspectj</id>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.runtime.version}</version>
</dependency>
<dependency>
<groupId>your aspect groupId</groupId>
<artifactId>youar aspect artifactId</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

Related

Unable to resolve Spring AOP types in Spring boot project

I created a new Spring boot project and was trying to implement some AOP concerns.
However, my code simply doesn't recognize the Classes from AOP. I checked and confirmed that spring-aop-5.0.7.RELEASE.jar is indeed present in Maven dependencies and JRE Runtime Libraries.
My code yet is very simple:
#Aspect
#Component
public class LoggingAspect {
#Around("execution(* com.springboot.service.*(..)) ")
public Object logAround(ProceedingJoinPoint joinPoint ) throws Throwable {
}
}
But in this code Aspect cannot be resolved to a type and same for other annotation and classes like Joinpoint and #Around. Other Spring annotation and classes work perfectly fine, ex. #Component, #Controllers etc.. and the project in itself runs fine without AOP.
I have already tried cleaning and re-building the project.
What can I be missing. Any help is appreciated.
#Aspect is located in the spring-aspects.jar or one of it's dependencies. add it as dependency:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
The #Aspect and #Around (and other such) annotations are part of the org.aspectj aspectjweaver artifact which is an optional compile dependency in your version of spring-aop.
You have to include it explicitly
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>

AspectJ: custom *.aj file is ignored

Why aspectj-maven-plugin ignore my AnnotationInheritor.aj file? Am I configured something wrong?
I want to advice ItemRepository#getById with custom annotation:
#Repository
public interface ItemRepository extends JpaRepository<Item, Long> {
// AOP does not work, since autogenerated ItemRepositoryImpl#getById
// won't have #MyAnnotation annotation
#MyAnnotation
public Item getById(Long id);
}
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface MyAnnotation {
}
#Aspect
#Component
public class MyAspects {
#Around("#annotation(MyAnnotation)")
public Object execute(ProceedingJoinPoint joinPoint) {
// This advice works correct when #MyAnnotation is placed on class, I tested.
// The problem is that I have to put #MyAnnotation on interface method
}
}
Spring Data JPA use interfaces and Java annotations are never inherited from interface to subclass (due JVM limitations). To make my advice work with custom annotations there is a little AspectJ trick. So as described at previous referrence, I created AnnotationInheritor.aj file:
package com.vbakh.somepackage.aspects;
// For some reason does not work. WHY?
public aspect AnnotationInheritor {
declare #method : void ItemRepository+.getById() : #MyAnnotation;
}
And add the following configurations to my pom.xml:
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- IMPORTANT -->
<useIncrementalCompilation>false</useIncrementalCompilation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.9</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8 </encoding>
</configuration>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.10</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
P.S. Is there any way to perform the same logic without *.aj files? Means with *.java files.
I copied your code into an AspectJ project (no Spring or Spring AOP there) in order to test it. I found a few problems:
#Around("#annotation(MyAnnotation)") will not find the annotation because there is no fully qualified class name.
declare #method : void ItemRepository+.getById() : #MyAnnotation; does not match your interface method's signature Item getById(Long id).
MyAspects.execute(..) needs to throw Throwable and of course also return something, such as the result of joinPoint.proceed(). But maybe that was just sloppy copy & paste.
After fixing this, the following MCVE works beautifully:
Helper classes making the project compile:
package de.scrum_master.app;
public class Item {}
package de.scrum_master.app;
public interface JpaRepository<P, Q> {}
package de.scrum_master.app;
import org.springframework.stereotype.Repository;
#Repository
public interface ItemRepository extends JpaRepository<Item, Long> {
Item getById(Long id);
}
package de.scrum_master.app;
public class ItemRepositoryImpl implements ItemRepository {
#Override
public Item getById(Long id) {
return new Item();
}
}
Marker annotation:
package de.scrum_master.app;
import java.lang.annotation.*;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface MyAnnotation {}
Driver Application:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
ItemRepository repository = new ItemRepositoryImpl();
repository.getById(11L);
}
}
Aspects:
Just in case you wonder why I added execution(* *(..)) to the pointcut, this is because I wanted to exclude matching call() joinpoints which are available in AspectJ as opposed to Spring AOP.
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class MyAspect {
#Around("#annotation(de.scrum_master.app.MyAnnotation) && execution(* *(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint);
return joinPoint.proceed();
}
}
package de.scrum_master.aspect;
import de.scrum_master.app.Item;
import de.scrum_master.app.ItemRepository;
import de.scrum_master.app.MyAnnotation;
public aspect AnnotationInheritor {
declare #method : Item ItemRepository+.getById(Long) : #MyAnnotation;
}
Console log:
execution(Item de.scrum_master.app.ItemRepositoryImpl.getById(Long))
VoilĂ ! It works nicely.
If it does not work for you like this you have other issues such as (but not exclusively)
the "auto-generated ItemRepositoryImpl#getById" you mentioned in passing. Whenever and wherever this is generated during the build process, it needs to exist before the aspect is applied to it. In order to analyze this I would need an MCVE on GitHub, though.
whether the target code to weave the aspect into is in the same Maven module as the aspect code. If it is not, you need to change your Maven setup.
I guess it should work, even without AnnotationInheritor
made a small demo, have a look...
demo

Java Spring - how to inject #Value to a data object?

I have a data object (with getters\setter only) that needs to be aware of the Spring profile, i.e.
#Value("${spring.profiles.active}")
private String profile;
I added a logic to one of it's 'set' method that checks the profile, i.e.
public void setItem(Item msg) {
if (environmentProperties.isDevMode()) {
this.msg= msg;
}
}
since this class is often marshal\unmarhsalled externally, so, of course the #Value isn't being populated - sine I didn't use spring Autowire to create the class instance... I tried defined the class as component, and autowire to an external class that holds the profile #Value - but it doesn't work
I use spring 3.2 - with no XML definition.
any suggestions?
b.t.w.
that data-objects often wrapped inside an exception class - so when it's created the profile should also be known to the data-object...
thanks!
EDITED:
using ApplicationContextAware doesn't work - I get null the 'setApplicationContext' method is never invoked.
also trying to get context directly doesn't work - get null instead when using:
'ApplicationContext ctx = ContextLoader.getCurrentWebApplicationContext();'
FIXED:
I've eventually found an example how to access the context staticly from an external class:
#Configuration
public class ApplicationContextContainer implements ApplicationContextAware {
private static ApplicationContext CONTEXT;
/**
* This method is called from within the ApplicationContext once it is
* done starting up, it will stick a reference to itself into this bean.
*
* #param context a reference to the ApplicationContext.
*/
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
CONTEXT = context;
}
/**
* This is about the same as context.getBean("beanName"), except it has its
* own static handle to the Spring context, so calling this method statically
* will give access to the beans by name in the Spring application context.
* As in the context.getBean("beanName") call, the caller must cast to the
* appropriate target class. If the bean does not exist, then a Runtime error
* will be thrown.
*
* #param beanName the name of the bean to get.
* #return an Object reference to the named bean.
*/
public static Object getBean(String beanName) {
return CONTEXT.getBean(beanName);
}
If I understand you correctly you want to inject into Objects not managed by Spring, but created by some other code that internally calls new and returns objects e.g. a serialization framework.
To inject unmanaged Objects you will need to configure either load-time or compile-time weaving. Load-time weaving requires an agent argument and lib when you start your VM, some containers might do this for you.
Compile-time weaving requires the use of the AspectJ compiler.
Below you will find a complete example using Maven and Spring-Boot:
E.g. run it with:
mvn spring-boot:run -Drun.arguments="--spring.profiles.active=dev"
DemoApplication.java:
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.aspectj.EnableSpringConfigured;
import org.springframework.stereotype.Component;
#SpringBootApplication
public class DemoApplication {
#EnableSpringConfigured
#ComponentScan("com.example")
public static class AppConfiguration {
#Value("${spring.profiles.active}")
String profile;
#Bean
public String profile() {
return profile;
}
}
#Configurable
public static class SomePojo {
#Autowired
private String profile;
public void print() {
System.out.println(this + "\t" + profile);
}
}
#Component
public static class Runner {
public void run() {
new SomePojo().print();
new SomePojo().print();
}
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args).getBean(Runner.class).run();
}
}
pom.xml:
<?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>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.8</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
From your description, you're trying to inject the property into POJO, which is used in marshalling. With this structure you may look for different workarounds with static-based/any other complex solutions.
I'd suggest to separate the bean which is used as POJO from the logic which depends on the property value. You can extract that logic to BeanService (which can be placed to Spring context) and handle it on that level, so that you separate the responsibility between Service and Data layers.
You're doing it wrong. Your code does not need to be aware of the profile. In your example, create a Message interface, and a number of bean implementations of this interface, one for each profile, each containing an appropriate message for that profile, and assign each one to a profile so that the bean is instantiated for that profile, and inject the instance into the class that needs the message.
So,
public interface Message { String getMessage(); }
#Profile("dev") #Component
public class DevMessage implements Message {
public String getMessage() { return "this is the dev message"; }
}
#Profile("prod") #Component
public class ProdMessage implements Message {
public String getMessage() { return "this is the production message"; }
}
If you prefer to describe your beans in your #Configuration class, you can mark a whole configuration with an #Profile, and have multiple configurations.
If you inject the Message instance into a class, you can call getMessage() on it. The profile will ensure that you have the appropriate implementation for your environment.
Edit:
I've just reread your question and realised that I've got this wrong. You have entity objects stored outside the application and instantiated through some code/framework. These aren't spring components, and so can't use the spring approach to dependency injection. In this case, don't use spring for them -- it doesn't work, doesn't have to work, and shouldn't work. If you haven't instantiated the object through spring, then it should have nothing to do with spring. I don't know your problem domain, but I've been using spring since it was invented and have never ever had to do this.

Spring Boot Actuator without Spring Boot

I've been working on a Spring/Spring MVC application and I'm looking to add performance metrics. I've come across Spring Boot Actuator and it looks like a great solution. However my application is not a Spring Boot application. My application is running in a traditional container Tomcat 8.
I added the following dependencies
// Spring Actuator
compile "org.springframework.boot:spring-boot-starter-actuator:1.2.3.RELEASE"
I created the following config class.
#EnableConfigurationProperties
#Configuration
#EnableAutoConfiguration
#Profile(value = {"dev", "test"})
#Import(EndpointAutoConfiguration.class)
public class SpringActuatorConfig {
}
I even went as far as adding #EnableConfigurationProperties on every configuration class as suggested on another post on StackOverflow. However that didn't do anything. The endpoints are still not being created and return 404s.
First let's clarify that you cannot use Spring Boot Actuator without using Spring Boot.
I was wrong about not being able to it without Spring Boot. See #stefaan-neyts
answer for an example of how to do it.
I created a sample project to show how you could convert a basic SpringMVC application using a minimal amount of Spring Boot auto-configuration.
Original source: http://www.mkyong.com/spring-mvc/gradle-spring-mvc-web-project-example
Converted source: https://github.com/Pytry/minimal-boot-actuator
I could have completely removed the dispatcher-servlet.xml and the web.xml files, but I kept them to show how to perform as minimal a change as possible and to simplify converting more complex projects.
Here is a list of steps I took to convert.
Conversion Process
Add a Java Configuration file annotated with #SpringBootApplication
Add the Application configuration file as a bean to the traditional xml configuration ( added it just after the context scan).
Move view resolvers into Application java configuration.
Alternatively, add the prefix and suffix to application.properties.
You can then inject them with #Value in your application, or delete it entirely and just use the provided spring boot view resolver.
I went with the former.
Removed Default context listener from the spring context xml.
This is important!
Since spring boot will provide one you will get an "Error listener Start" exception if you do not.
Add the spring boot plugin to your build script dependencies (I was using gradle)
Add a mainClassName property to the build file, and set to an empty String (indicates not to create an executable).
Modify dependencies for spring boot actuator
You can use actuator without spring boot.
Add this to pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>
And then in your config class
#Configuration
#EnableWebMvc
#Import({
EndpointAutoConfiguration.class , PublicMetricsAutoConfiguration.class , HealthIndicatorAutoConfiguration.class
})
public class MyActuatorConfig {
#Bean
#Autowired
public EndpointHandlerMapping endpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints) {
return new EndpointHandlerMapping(endpoints);
}
#Bean
#Autowired
public EndpointMvcAdapter metricsEndPoint(MetricsEndpoint delegate) {
return new EndpointMvcAdapter(delegate);
}
}
And then you can see the metrics in your application
http://localhost:8085/metrics
Allthough it is not a good idea to use Spring Boot features without Spring Boot, it is possible!
For example, this Java configuration makes Spring Boot Actuator Metrics available without using Spring Boot:
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.PublicMetricsAutoConfiguration;
import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
#Configuration
#Import({ EndpointAutoConfiguration.class, PublicMetricsAutoConfiguration.class })
public class SpringBootActuatorConfig {
#Bean
#Autowired
public EndpointHandlerMapping endpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints) {
return new EndpointHandlerMapping(endpoints);
}
#Bean
#Autowired
public EndpointMvcAdapter metricsEndPoint(MetricsEndpoint delegate) {
return new EndpointMvcAdapter(delegate);
}
}
The Maven dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
Though the answer is already accepted, I thought of updating my experience. I did not want to convert my application to spring boot using #SpringBootApplication. Refer to another question where I have mentioned the bare minimum code required.
As we already have Spring Boot Actuator 2.x, a recipe to include actuator to an existing Spring MVC project can look like this:
#Configuration
#Import({
EndpointAutoConfiguration.class,
HealthIndicatorAutoConfiguration.class,
InfoEndpointAutoConfiguration.class,
HealthEndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
})
#EnableConfigurationProperties(CorsEndpointProperties.class)
class ActuatorConfiguration {
#Bean //taken from WebMvcEndpointManagementContextConfiguration.class
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
EndpointMapping endpointMapping = new EndpointMapping(webEndpointProperties.getBasePath());
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
corsProperties.toCorsConfiguration(),
new EndpointLinksResolver(allEndpoints, webEndpointProperties.getBasePath()));
}
#Bean
DispatcherServletPath dispatcherServletPath() {
return () -> "/";
}
}
I did include
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<version>2.1.18.RELEASE</version>
</dependency>
for compatibility with the baseline Spring version I've been using (5.1.19.RELEASE)
If your objective is to create an endpoint with metrics for Prometheus a.k.a. OpenMetrics, you can use the Prometheus JVM client which is compatible with Spring framework.
Add dependency:
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_servlet</artifactId>
<version>0.16.0</version>
</dependency>
To collect metrics of requests, add as first filter in web-app/WEB-INF/web.xml:
<filter>
<filter-name>prometheusFilter</filter-name>
<filter-class>io.prometheus.client.filter.MetricsFilter</filter-class>
<init-param>
<param-name>metric-name</param-name>
<param-value>webapp_metrics_filter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>prometheusFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
To expose metrics as HTTP endpoint, add servlet:
<servlet>
<servlet-name>prometheus</servlet-name>
<servlet-class>io.prometheus.client.exporter.MetricsServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>prometheus</servlet-name>
<url-pattern>/metrics</url-pattern>
</servlet-mapping>
After that you can see the metrics on the /metrics endpoint.
Time passes, we have Spring 6, SpringBoot 3, JakartaEE as a baseline, but people are still looking to add actuator to legacy spring applications. So a small update: spring + actuator without spring-boot. In fact not much changes (and the changes have already been pointed out).
The dependencies
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.0.3</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<version>3.0.1</version>
</dependency>
The actuator configuration
#Configuration
#ImportAutoConfiguration({
EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
ServletManagementContextAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
HealthContributorAutoConfiguration.class,
InfoEndpointAutoConfiguration.class,
HealthEndpointAutoConfiguration.class,
HeapDumpWebEndpointAutoConfiguration.class,
ThreadDumpEndpointAutoConfiguration.class,
LoggersEndpointAutoConfiguration.class,
PrometheusMetricsExportAutoConfiguration.class,
})
#EnableConfigurationProperties(CorsEndpointProperties.class)
class ActuatorConfiguration {
#Bean //taken from WebMvcEndpointManagementContextConfiguration.class
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
EndpointMapping endpointMapping = new EndpointMapping(webEndpointProperties.getBasePath());
return new WebMvcEndpointHandlerMapping(endpointMapping,
webEndpoints,
endpointMediaTypes,
corsProperties.toCorsConfiguration(),
new EndpointLinksResolver(allEndpoints, webEndpointProperties.getBasePath()),
true);
}
#Bean
DispatcherServletPath dispatcherServletPath() {
return () -> WebInitializer.APPLICATION_ROOT;
}
}
The example is easy to run directly from maven jetty plugin (mvn jetty:run-war).
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>11.0.13</version>
</plugin>
you have made the mistake by not introducing the #springboot annotation in your code.When you add #springboot ot will consider as boot program by the compiler automatically and addd the required dependency file for it and your actuator dependency file

Spring DM and Services registry

I'm learning Spring DM and I have a problem with Service registry. I'm using ServiceMix 4.3.0 with embedded Felix instance. I have 2 bundles in my project.
The first one contains interface, and mock implementation. I want to publish them to OSGi service registry:
public interface PersonAPI {
public void listAll();
public List<PersonEntity> getAll();
public void addPerson(PersonEntity pe);
public PersonEntity getPerson(int id);
}
PersonEntity is a simple class with data, nothing special.
Mock implementation is contains just a list of PeopleEntity objects, so there is nothing interesting either.
Here is part of Spring configuration XML:
<bean id="personImpl" class="com.osgi.Person.impl.mock.PersonImpl" />
<osgi:service id="personLogic" ref="personImpl" interface="com.osgi.Person.PersonAPI" />
And part taken from pom.xml file:
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.4</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-Category>sample</Bundle-Category>
<Bundle-SymbolicName>${artifactId}</Bundle-SymbolicName>
<Export-package>com.osgi.*</Export-package>
</instructions>
</configuration>
</plugin>
This installs fine on ServiceMix. Now I defined another bundle, here are most important parts:
public class PersonTester {
PersonAPI api;
public void init() {
System.out.println("PostConstruct:");
System.out.println("Have API " + api + " class " + api.getClass().getCanonicalName());
api.listAll(); //This line (or any API call, blows everything)
}
public PersonAPI getApi() {
return api;
}
public void setApi(PersonAPI api) {
this.api = api;
}
}
Spring configuration:
<osgi:reference id="personLogic" interface="com.osgi.Person.PersonAPI" />
<bean id="personTester" init-method="init" class="com.osgi.Person.PersonTester">
<property name="api" ref="personLogic" />
</bean>
Most important parts from pom.xml:
<dependencies>
<dependency>
<groupId>com.osgi</groupId>
<artifactId>Pserson-API</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
....
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.3.4</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-Category>sample</Bundle-Category>
<Bundle-SymbolicName>${artifactId}</Bundle-SymbolicName>
<Import-package>com.Person.entity,com.osgi.Person</Import-package>
</instructions>
</configuration>
</plugin>
The good news is, that "behind" injected Spring proxy is my implementation class. I can see that when using api.toString(). However when I call any method defined in my proxy, I get an exception:
Exception in thread "SpringOsgiExtenderThread-88" org.springframework.beans.factory.BeanCreationException: Error creating bean
with name 'personTester': Invocation of init method failed; nested exception is org.springframework.aop.AopInvocationException: AOP configuration seems to be invalid: tried calling method [public abstract void com.osgi.Person.PersonAPI.listAll()] on target [PersonImpl [set=[]]]; nested exception is java.lang.IllegalArgumentException:
object is not an instance of declaring class
It looks like It looks like AOP is missing the target, but why? And how to fix this?
The problem is fixed. It was something in the environment.
I started building new project (with different artifact Id) on top of this tutorial and everything looks OK. Perhaps something cached somewhere (strange, because I flushed ServiceMix bundle cache and local maven repo). I try to reproduce it on different machine, and maybe I'll come up with some better explanation.

Categories