Is it worthy using AOP in a Spring-boot application? - spring-boot

A few days ago, I posted this question asking whether there is a newer approach to Spring AOP and mockito.
While I understand how to use AOP, I am still missing on its returned value. The whole endeavour has shown me that it's not really popular - at least there aren't that many recent posts.
If I comment out the annotations #Aspect and #Configuration in my LoggingAspect class, effectively rendering it non-aop, all my tests are green. If I switch it back on, I start getting a load of NullPointerExceptions and loads of other errors on my mocked test classes.
I wonder if it is worth the hassle.
EDIT adding more detail from my specific implementation.
Controller:
#RestController
public class EndpointController {
private EndpointService endpointService;
#Autowired
public EndpointController(EndpointService endpointService) {
this.endpointService = endpointService;
}
#PostMapping(path = "/endpoint", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
private #ResponseBody EndpointResponse doSomething(//... //, #RequestBody SomeObject someObject) throws Exception {
return endpointService.doSomething(someObject);
}
}
In my test class, I have:
#RunWith(SpringRunner.class)
public class EndpointControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void shouldBeSuccessfulAccessingTheEndpoint() throws Exception {
SomeObject someObject = new SomeObject(// values //);
ObjectMapper mapper = new ObjectMapper();
String payload = mapper.writeValueAsString(someObject);
mockMvc.perform(post("/endpoint").contentType(MediaType.APPLICTION_JSON).content(payload)).andExpect(status().isOK));
}
}
It fails and throws a NullPointerException. When debugging, the endpointService is always null.

AOP is great for cross-cutting concerns:
Logging (we use it for access and performance logging)
Validation (such as used by Bean Validation, JSR-380)
Transactional scopes (built into frameworks such as JEE and Spring)
Security checks (e.g. Shiro)
and many more.
It could be used for other purposes, such as extending/wrapping existing functionality, though that is definitely not something I'd recommend, and fortunately never became popular, as it seems.

AOP is as valid as ever. It's used for transactions, logging, metrics, etc.
I think there was period where it might have been overused as decorators.
Production and testing are different matters.
If you're unit testing a class, it suggests that you aren't testing the aspects. You could make those aspects conditional based on profile.
If the proper operation of your object depends on the aspect, because it modifies the input, perhaps you should rethink.

Related

How to expose endpoints REST lazily?

Scenario:
The Spring Boot application should expose its REST endpoints
only when a specific action occurs.
Is there any way in Spring to lazily expose endpoints, or even the whole HTTP subsystem?
In Apache CXF, we can do something like this:
void exposeEndpoints() {
EndpointImpl endpoint = new EndpointImpl(cxfBus, serviceImpl);
endpoint.publish();
}
How to do the same thing in Spring?
You could take a look at #RefreshScope.
I would define my #RestController beans as follows:
#Configuration
#RefreshScope
public ControllerConfig {
#Bean
#ConditionalOnProperty(value = "should.initialize.rest", havingValue = true)
public SomeController someController(){
....
}
#Bean
#ConditionalOnProperty(value = "should.initialize.rest", havingValue = true)
public SomeOtherController someOtherController(){
....
}
}
and if you start your application with property should.initialize.rest with value false in your application.properties:
should.initialize.rest=false
Then your controllers won't be registered/initialized. When the application is running, you could then update your application.properties to:
should.initialize.rest=true
and make a call to /refresh, then your ApplicationContext will reload/refresh, this time with your REST controllers. You can find more about #RefreshScope below:
https://www.baeldung.com/spring-reloading-properties
https://andressanchezblog.wordpress.com/2016/09/15/refresh-scope-in-spring-cloud/
The solution I have provided below is one of the ways and it may or may not be suitable in your case.
It situation seems like more of design concern rather than particular implementation.
I would suggest you to go with little change in the design.
Store the event in DB or Session (as per requirement) as Boolean.
Create Aspect using AspectJ or Spring's own AOP.
Create Before aspect with specific package pointcuts.
In the #Before advice, check for the boolean flag, if the condition for publishing
satisfies than use joinpoint.proceed() else throw some kind of error saying service not available.
Another way is to create custom Annotation with Aspects. You can use that annotation as per requirement and not on the whole service layer.
The benefit of first approach is that you have the control at generic level and the second approach at service level.
I would suggest to create a new child context with your HTTP subsystem. Something like this:
#Service
public class MyBusinessService {
#Autowired
private final ApplicationContext parentContext;
private AnnotationConfigWebApplicationContext webContext;
public void myBusinessMethod() {
this.webContext = new AnnotationConfigWebApplicationContext();
this.webContext.setParent(parentContext);
this.webContext.scan("com.mybusiness.service.webcomponents");
this.webContext.refresh();
this.webContext.start();
}
}
DISCLAIMER: This is proof-of-concept code, I did not try to compile or run this. But hopefully it is enough to illustrate the concept.

CamelTestSupport with routes using classes with #Autowired

After help from experts over at question Camel unit test with cametestsupport, template is always null, I ended up with one more issue.
Below is my test class - a simple test that tests a route which has only ValidationProcessor POJO.
public class RouteTests extends CamelTestSupport {
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
from("direct:start")
.filter().method(ValidationProcessor.class, "validate")
.to("mock:result");
}
};
}
#Test
public void testSendMatchingMessage() throws Exception {
ObjectMapper objectmapper = new ObjectMapper();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream stream = loader.getResourceAsStream("test.json");
JSONObject testJson = new JSONObject(objectmapper.readValue(stream, Map.class));
MockEndpoint resultEndpoint = getMockEndpoint("mock:result");
resultEndpoint.expectedMessageCount(1);
template.sendBody("direct:start", testJson);
resultEndpoint.assertIsSatisfied();
}
}
The problem comes when this ValidationProcessor has an #Autowired component in it. My validation method needs data from Elasticsearch and hence I have an #Autowired for an elastic client.
When I run mvn clean test, I am getting a NullPointerException stating that this elastic client is null. I think the issue is that this test is devoid of anything to do with Spring and hence the issue, but I do not know how to overcome this.
My test needs to fetch data from Elasticsearch and hence the ValidationProcessor POJO does need #Autowired.
When you extend CamelTestSupport then it's not a Spring application. You need to extend CamelSpringTestSupport. That would create Camel in a Spring runtime, and then allow beans to have IoC via Spring. This kind of testing is often used with Camel XML routes where the routes are defined in XML files. However, you can have a plain XML file and refer to routes in Java DSL as well.
However, as Makoto answers, then vanilla Spring unit testing is of late often about using all those gazillion annotations. Camel has support for that as well as his answer shows. This is also how for example Spring Boot testing is done, etc.
You can find some unit tests in camel-test-spring you can use as inspiration as well.
What I've discovered is that it's unwise to extend CamelTestSupport when you want to use anything with Spring. In fact, there's a better way to do it - use the CamelSpringBootRunner instead.
Well...I say "better". You're going to find yourself attaching a ton of annotations. Of the things you'll need:
A boostrapper to ensure that you're bootstrapping Camel correctly
The routes you wish to add to the classpath (and all of the beans); this ensures that they get added to Camel's registry through Spring's registry
You have to dirty the context after every run or your tests get into a wonky state.
You can automock endpoints by specifying either #MockEndpoints or #MockEndpointsAndSkip. The former sends the data along to the actual route.
Below is just a start. Many of these annotations and their documentation can be found in the formal docs.
#RunWith(CamelSpringBootRunner.class)
#BootstrapWith(SpringBootTestContextBootstrapper.class)
#ActiveProfiles("test")
#SpringBootTest(classes = { YourEntryPointClass.class })
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
#UseAdviceWith
#MockEndpoints("direct:*")
public class RouteTests {
}

Spring service with cacheable methods gets initialized without cache when autowired in Shiro realm

After spending 2 days on this issue I really can't make any more progress on my own. I am working on a standard web application with Spring for dependency injection and the likes. I am also using Spring to cache several expensive methods I use a lot.
After I introduced Apache Shiro for the security layer, I was experiencing a strange issue where #Cacheable methods in a certain service no longer got cached. To this point, I was able to strip the problem down to its core, but there's still a lot of code for you to look at - sorry for that...
First, I configure all relevant packages (all classes shown in the following are in one of those).
#Configuration
#ComponentScan(basePackages = {
"my.package.config",
"my.package.controllers",
"my.package.security",
"my.package.services",
})
public class AppConfiguration {
}
Here is the configuration file for caching.
#Configuration
#EnableCaching
public class CacheConfiguration {
#Bean(name = "cacheManager")
public SimpleCacheManager cacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("datetime")
));
return simpleCacheManager;
}
}
For my minimal example, I am using a very simple service that only returns the current timestamp. The Impl class is as simple as you would imagine.
public interface DateService {
#Cacheable("datetime")
LocalDateTime getCurrent();
}
I inject this service into a controller.
#Controller
#RequestMapping("/v1/date")
public class DateController {
#Autowired
DateService dateService;
#RequestMapping(value = "/current", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<String> getCurrent() {
Subject s = SecurityUtils.getSubject();
s.login(new MyToken());
return new ResponseEntity<>(dateService.getCurrent().toString(), HttpStatus.OK);
}
}
The application is set up and started via Jetty, and everything works as expected so far. When calling <api-url>/v1/date/current for the first time the current timestamp is returned, but afterwards one always receives the cached result.
Now, I introduce Shiro with yet another config file.
#Configuration
public class ShiroSecurityConfiguration {
#Bean
#Autowired
public DefaultSecurityManager securityManager(MyRealm realm) {
List<Realm> realms = new ArrayList<>();
// MyToken is a static stub for this example
realm.setAuthenticationTokenClass(MyToken.class);
realms.add(realm);
DefaultSecurityManager manager = new DefaultSecurityManager(realms);
SecurityUtils.setSecurityManager(manager);
return manager;
}
// other Shiro related beans that are - at least to me - irrelevant here
// EDIT 2: I figured out that the described problem only occurs with this bean
// (transitively depending on DateService) in the application
// the bean is required for annotations such as #RequiresAuthentication to work
#Bean
#Autowired
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
Finally, here comes the realm which also depends on my service.
#Component
public class MyRealm extends AuthenticatingRealm {
private static final String REALM_NAME = "MyRealm";
#Autowired
private DateService dateService;
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("User authenticated at "+dateService.getCurrent());
return new SimpleAuthenticationInfo("",token.getCredentials(),REALM_NAME);
}
}
With that, the caching is broken in my entire application. There is no error message, it just doesn't use the cache anymore. I was able to implement a workaround, but I am now seeking for a better solution and maybe also some advice to better understand the essence of my issue. So, here comes the workaround.
#Component
public class MyRealm extends AuthenticatingRealm {
private static final String REALM_NAME = "MyRealm";
private DateService dateService;
#Autowired
private ApplicationContext applicationContext;
private void wireManually() {
if (dateService == null) {
dateService = applicationContext.getBean(DateService.class);
}
}
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
wireManually();
System.out.println("User authenticated at "+dateService.getCurrent());
return new SimpleAuthenticationInfo("",token.getCredentials(),REALM_NAME);
}
}
Now it's back to working, and I was able to debug the reason for that. Shiro and hence MyRealm gets initialized very early, even before the whole caching with my SimpleCacheManager and all the related stuff (cacheInterceptor etc.) is loaded. Therefore, there is no proxy to wrap around the service when it gets initialized before the realm when using #Autowired. With the workaround shown above, the service is not injected before everything is set up properly and the first request is being served, and then there is no problem.
Simply put, as soon as I make MyRealm dependent on DateService (annotating the last version of MyRealm with #DependsOn("dateServiceImpl") is enough to break the application) it gets initialized too early (i.e. before caching is set up).
So I would need to either postpone the initialization of MyRealm, but I don't know how to do that. I tried #DependsOn("cacheManager"), but that doesn't help as the other beans required for caching are loaded later nonetheless. Or - which is the same from another perspective - I could make sure the whole caching infrastructure (I am not enough of an expert to describe it in detail) is initialized earlier. Unfortunately, I also don't know how to do that...
Thanks in advance to everyone who made it to this point. Looking forward to any input, no matter if it's an idea to fix the code in a better way or an explanation why exactly Spring can't get this right on its own.
I finally figured out what the problem is and can at least explain its cause in more detail, even though my proposed solution is still a bit hacky.
Enabling the caching aspect in Spring introduces a org.springframework.cache.interceptor.CacheInterceptor, which is essentially an org.aopalliance.aop.Advice used by a org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor that implements org.springframework.aop.Advisor.
The org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor I introduced for Shiro is another Advisor which transitively depends on the DateService via DefaultSecurityManager and MyRealm.
So I have two Advisors for two different aspects - Caching and Security - of which the one for security is initialized first. In fact, whenever I introduce any Advisor dependent on DateService - even if its only a dummy implementation as in the following example - the caching doesn't work anymore for the same reason as it was broken when adding Shiro. This causes the DateService to be loaded before the caching aspect is ready, so it cannot be applied.
#Bean
#Autowired
public Advisor testAdvisor(DateService dateService) {
return new StaticMethodMatcherPointcutAdvisor() {
#Override
public boolean matches(Method method, Class<?> targetClass) {
return false;
}
};
}
Hence, the only proper fix for that is to change the order of aspect initialization. I am aware of the #Order(Ordered.LOWEST_PRECEDENCE) respectively #Order(Ordered.HIGHEST_PRECEDENCE) annotation for the case the multiple Advisors are applicable at a specific joinpoint, but this is not the case for me so this doesn't help. The order of initialization matters for other reasons.
Adding the following code in DateServiceImpl actually solves the problem:
#Autowired
BeanFactoryCacheOperationSourceAdvisor waitForCachingAspect;
With that, the service always waits for the cache before it can be initialized even though this dependency is not used anywhere in the implementation. So now everything is working as it should because the dependency tree now includes Shiro --> DateService --> Cache which makes the Shiro Advisor wait long enough.
It is still not as nice and clean as I would like it to be, but nevertheless, I think this explanation helps to understand the core of the problem and "How can I change the order in which Advisors are initialized in Spring" is a separate question I posted here.
Since Spring 4, #Lazy can be used to achieve the same behavior as in the original question in a more declarative way (see Spring 4 JavaDoc and compare it with earlier versions).
Tested this and it works.
#Component
public class MyRealm extends AuthenticatingRealm {
private static final String REALM_NAME = "MyRealm";
#Autowired
#Lazy
private DateService dateService;
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("User authenticated at "+dateService.getCurrent());
return new SimpleAuthenticationInfo("",token.getCredentials(),REALM_NAME);
}
}

spring aspect being applied outside of app context (in stubbed out unit test)

Here's a weird one. I've got a few tests failing because an aspect is being applied, so an autowired service is null, bad things ensue. The issue is that I can't understand how the aspect is even being applied, since in the test I construct the object under test with new.
#RunWith(MockitoJUnitRunner.class)
public class TheControllerTest {
#Spy
private TheController controller = new TheController();
#Mock
private HttpServletRequest request;
#Mock
private ConfigService configService;
....
#Before
public void setup() {
controller.setConfigService(configService);
....
}
#Test
public void testGetAccountsList() throws Exception {
Mockito.when(accountService.getAllAccounts()).thenReturn(Arrays.asList(account1, account2));
Map<String, Object> result = controller.getAccountsList(request);
...
}
}
I'm obviously omitting plenty of code, but really, I just don't understand how, given how controller is instantiated, it could have had the advice applied.
One possible reason could be if you are running this in Eclipse - in a project with ajbuilder enabled, even if you are explicitly expecting Spring AOP through dynamic proxies, ajbuilder would actually perform compile team weaving, and hence you would see advice enhanced classes even using normal "new". Can you please check this, the fix would be to disable "ajbuilder" - here is one reference - JUnit weaving wrong Spring AOP Bean

Spring AOP: advice is not triggered

Trying to design simple aspect,that will print word "logg" to console,when any of public methods executed.
aspect:
#Aspect
public class LoggingAspect {
#Pointcut("execution(public * *(..))")
public void publicServices() {
};
#Before("publicServices()")
public void logg() {
System.out.println("logg");
}
}
xml config:
<context:component-scan base-package="aspectlogging" />
<aop:aspectj-autoproxy/>
<bean id="loggingAspectHolder" class="aspectlogging.LoggingAspect"/>
simple bean:
package aspectlogging;
#Component
public class TestableBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
test:
public class TestLogging {
public static void main(String[] args) {
TestableBean tb = new TestableBean();
tb.setName("yes");
tb.getName();
}
}
I expect,that result of running of TestLogging will be "logg" word in console,and no output returned.
Do I understand AOP correctly in this case?
With #Around advice, you need to have a ProceedingJoinPoint pjp argument to the advising method and to call pjp.proceed() at the point in the advisor when you want the wrapped method to be called. It's easier to use #Before advice really, when what you've done will otherwise work just fine.
[EDIT]: Also, you must let Spring construct your beans for you instead of directly calling new. This is because the bean object is actually a proxy for your real object (which sits inside it). Because your target object doesn't implement an interface, you will need to have the cglib library on your classpath in addition to the Spring libraries. (Alternatively, you can go with using AspectJ fully, but that requires using a different compiler configuration.)
To create your beans, you first need to create a Spring context and then query that for the bean instance. This means you change from:
TestableBean tb = new TestableBean();
To (assuming you're using Spring 3, and that your XML config is in "config.xml" somewhere on your classpath):
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
TestableBean tb = context.getBean(TestableBean.class);
The rest of your code remains the same (after adjusting for import statements and possibly additional dependencies).
Not quite sure on this one, but maybe you need to use a spring managed TestableBean to have spring AOP pick up the method call.
edit: of course, you can't use #Around the way that you provided - but this subject has been addressed by another answer, so it's omitted here.
edit2: If you need help on how to get a spring managed bean, please feel free to ask. but since you already got your aspect bean set up, I believe you can handle this :)
edit3: Hehe. Ok.. maybe not :)
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
will load your application context.
Load beans from there by calling:
TestableBean testableBean = (TestableBean )ctx.getBean("testableBean ");
Define the TestableBean just like you did with your Aspect bean.
edit4: Now I'm pretty sure that the fault is the non-spring managed bean.
Use the simplest thing that can work. Spring AOP is simpler than using full AspectJ as there is no requirement to introduce the AspectJ compiler / weaver into your development and build processes. If you only need to advise the execution of operations on Spring beans, then Spring AOP is the right choice. If you need to advise domain objects, or any other object not managed by the Spring container, then you will need to use AspectJ.
Taken from: http://static.springsource.org/spring/docs/2.0.x/reference/aop.html

Resources