I'm trying to create a demo AOP application but it just does not work right.
I read through all tutorials and got it working with #RestController but as I tried it with a plain java spring driven application I just can't get it to work. Please review my files and tell me where my mistake lies in.
Application Class
#SpringBootApplication
#ComponentScan("com.xetra.experimental")
#EnableAspectJAutoProxy
public class AoptryoutnowebApplication {
public static void main(String[] args) {
SpringApplication.run(AoptryoutnowebApplication.class, args);
DefaultClassToAspectImpl defaultClassToAspectImpl = new DefaultClassToAspectImpl();
defaultClassToAspectImpl.doStuff();
}
}
ClassToAspect Interface
public interface ClassToAspect {
void doStuff();
}
ClassToAspect Implementation
#Component
public class DefaultClassToAspectImpl implements ClassToAspect {
#FooAnnotation
public void doStuff(){
System.out.println("DoStuff!");
}
}
Annotation for Pointcut
public #interface FooAnnotation {
}
Aspect Class
#Aspect
public class FooAspect {
#Pointcut("#annotation(FooAnnotation)")
public void methods(){
}
#Before("methods()")
public void doAspect(){
System.out.println("FooAspect before");
}
}
Try this:
replace #EnableAspectJAutoProxy with #EnableAspectJAutoProxy(proxyTargetClass = false)
change pointcut to
#Pointcut("execution (* your.package..*.*(..)) && #annotation(fooAnnotation))")
The problem is you are using a non Spring managed instance by doing new DefaultClassToAspectImpl(). Spring AOP only works for Spring managed beans, because by default Spring AOP is proxy based.
So instead of doing new DefaultClassToAspectImpl() you should be obtaining the instance from the ApplicationContext. When using Spring Boot the SpringApplication.run call returns an ApplicationContext. Here you can use one of the getBean methods to obtain the instance you want.
ApplicationContext ctx = SpringApplication.run(AoptryoutnowebApplication.class, args);
ClassToAspect bean = getBean(ClassToAspect.class);
bean.doStuff();
This way you get the Spring managed
Related
I have created a sample spring boot application which have a class "CacheManagement "where I have a function "getAllValue" which return the a customer object whose values I need to cache.
This class also have a function checkCache which uses the data from cache. How Can I implement this. Somebody please help me on this. I am providing the class I have written.PFB the class
#Service
public class CacheManagement {
#Cacheable(value = "service",key = "list")
public Customer getAllValue(){
Customer customer = new Customer();
System.out.println("Inside cache");
customer.setId("1");
customer.setName("Shilpa");
return customer;
}
public String checkCache() {
Customer cus = getAllValue();
System.out.println(cus.toString());
return "1";
}
}
PFB the main class:
#SpringBootApplication
public class DemoApplication implements CommandLineRunner{
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Override
public void run(String... arg0) {
CacheManagement cacheService = new CacheManagement();
cacheService.getAllValue();
cacheService.checkCache();
}
}
According to my need the sysout statement should only print once right? But my issue is it is printing twice .Means the getAllValue() call from checkCache() method is calling the function itself not fetching data from cache.
Please help me to resolve this issue.
There are multiple issues with your code.
CacheManagement cacheService = new CacheManagement(); creates an instance which is not a Spring container managed bean. The Spring magic will not work here . You should get a Spring container managed bean of cacheManagement for all your Spring framework support to kickin.
#SpringBootApplication
#EnableCaching
public class DemoApplication implements CommandLineRunner {
#Autowired
CacheManagement cacheService;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Override
public void run(String... arg0) {
cacheService.getAllValue();
cacheService.checkCache();
}
}
2.Assuming you have enabled caching using #EnableCaching ( Refer here )
#SpringBootApplication
#EnableCaching
public class DemoApplication implements CommandLineRunner{
...
}
The Spring Caching works with the help of Spring AOP and Spring AOP works on proxies. Calling getAllValue() from within a method of the same class ( here checkCache()) is called a self-invocation. Spring AOP will not be able to advice the method call in this case , as it will not go through the proxy and hence the caching logic fails to work.
To understand this concept in detail , do refer the official spring reference documentation : Understanding AOP Proxies . Read through the section starting with The key thing to understand here is that the client code inside the main(..)
The resolution to your issue is to make sure the checkValue() call goes through the proxy so that the cached result is returned. Since you need the method (checkValue()) as part of the same bean , a self reference , will do it.
You must read through the official documentation on the same topic though to understand the implications. Section starting with As of 4.3, #Autowired also considers self references for injection ..
Following code should fix the issue for you
#Service
public class CacheManagement {
#Autowired
CacheManagement self;
#Cacheable(value = "service",key = "list")
public Customer getAllValue(){
Customer customer = new Customer();
System.out.println("Inside cache");
customer.setId("1");
customer.setName("Shilpa");
return customer;
}
public String checkCache() {
Customer cus = self.getAllValue();
System.out.println(cus.toString());
return "1";
}
}
Read more on caching here
I have a Spring boot application, and I want to log some info what happens when a Controller method id invoked.
For some reason, my Aspect is not working.
Here is my #Component class annotated with #Aspect:
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controller() {
}
#Pointcut("execution(* *.*(..))")
protected void allMethod() {
}
#Before("controller()&& allMethod()")
public void logBefore(JoinPoint joinPoint) {
}
The logBefore method is not called, when any Controller method is called with REST.
Important: As you've stated you are using a Spring Boot setup, my assumption is that you've implemented the Spring AOP module instead of the "actual" AspectJ library. The difference is significant, as the implementation of AOP differs between them. Spring uses the AspectJ annotations to apply proxying, while AspectJ "weaves" the code into your application. In short, Spring AOP might be easier to implement, while AspectJ offers more fine-grained functionality (such as compile-time weaving). A comparison can be found here.
I have tried out the configuration from the code snippet you provided in your post. The advice was invoked after I added several annotations:
#SpringBootApplication
// Be sure to add EnableAspectJAutoProxy and set proxyTargetClass to true
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class DemoApplication {
...
}
// Be sure to add #Aspect and #Component
#Component
#Aspect
public class DemoAop {
private static Logger logger = LoggerFactory.getLogger(DemoAop.class);
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controller() {
}
#Pointcut("execution(* *.*(..))")
protected void allMethod() {
}
#Before("controller()&& allMethod()")
public void logBefore(JoinPoint joinPoint) {
logger.info("TEST");
}
}
At runtime your controller is annotated with #RestController but not #Controller.
Simply changing the Pointcut to RestController works:
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void controller() {
}
I'm leaning spring boot 2.0, I use #Value annotation as same as early release, but it's not work in #Configration annotation.
application.yml
test:
a: test
TestApplication.java
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(TestApplication.class, args);
System.out.println(context.getEnvironment()
.getProperty("test.a"));//got test
}
}
TestConfigration.java
#Configuration
public class TestConfigration{
#Value("${test.a}")
String a;
#Bean
public Bean getBean(){
System.out.println(a);//there!!!the a is NULL!!! WHY?
return new Bean();
}
}
TestController.java
#RestController
#RequestMapping("/")
public class TestController{
#Value("${test.a}")
String a;
#RequestMapping("/")
public String test(){
return a;//got test
}
}
why?
Emmmm ... there is no problem in the questions's code , it works.
I ignored a warn log when spring boot start:
2018-03-26-21:21:27 WARN [org.springframework.context.annotation.ConfigurationClassPostProcessor] - Cannot enhance #Configuration bean definition 'TestConfigration' since its singleton instance has been created too early. The typical cause is a non-static #Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'.
it's because I register a org.mybatis.spring.mapper.MapperScannerConfigurer with #Bean but miss static keywords, it's necessary if a implementation of BeanFactoryPostProcessor register and #Value is used in same configuration class.
I don't know it and I used to be wrong for too long,I didn't find it out because the db configuration and mybatis configuration is not in same class in my past project.
thanks to #ailav and #Impulse The Fox ,and forgive my weird English:)
I have a spring boot application and some other components which application should interact with. However, in my unit testing I am using just application functionality and I would like to mock outer API calls. I am stuck as I can't find the way to mock case like this:
My start class with main method:
#ComponentScan("com.sample.application")
#SpringBootApplication
public class MyApp implements CommandLineRunner {
#Autowired
private OuterAPI outerAPI;
public static void main(String[] args) {
SpringApplication.run(AdRedirectorMain.class, args);
}
#Override
public void run(String... args) throws Exception {
outerAPI.createInstances();
}
...
}
And here is my test class example:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApp.class)
public class MyAppTest {
// any tests
}
I am working with Spring Boot, JUnit, Mockito.
So, I am facing the problem - how could I avoid this method call createInstances() with Mockito, via reflection or in any other way.
Have a look at Mocking and spying beans in the Spring Boot documentation.
You can use #MockBean in your test class to replace an autowired bean with a Mockito mock instance.
You can use #MockBean http://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
or you can define an interface that you OuterAPI implements then for your test you provide a dummy implementation that makes a dummy call instead of actual call to the outerAPI.createInstances();
Another option that you have is to have configuration class like this:
#Configuration
#Profile(value = {"yourtest-profile"})
public class TestConfiguration{
#Primary
#Bean
public OuterAPI outerAPI() {
return Mockito.mock(OuterAPI.class);
}
}
and put it under scr/test/java
I have a simple method in a Service in a SpringBoot Application. I have the retry mechanism setup for that method using #Retryable.
I am trying integration tests for the method in the service and the retries are not happening when the method throws an exception. The method gets executed only once.
public interface ActionService {
#Retryable(maxAttempts = 3, backoff = #Backoff(delay = 2000))
public void perform() throws Exception;
}
#Service
public class ActionServiceImpl implements ActionService {
#Override
public void perform() throws Exception() {
throw new Exception();
}
}
#SpringBootApplication
#Import(RetryConfig.class)
public class MyApp {
public static void main(String[] args) throws Exception {
SpringApplication.run(MyApp.class, args);
}
}
#Configuration
#EnableRetry
public class RetryConfig {
#Bean
public ActionService actionService() { return new ActionServiceImpl(); }
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration( classes= {MyApp.class})
#IntegrationTest({"server.port:0", "management.port:0"})
public class MyAppIntegrationTest {
#Autowired
private ActionService actionService;
public void testAction() {
actionService.perform();
}
Your annotation #EnableRetry is at the wrong place, instead of putting it on the ActionService interface you should place it with a Spring Java based #Configuration class, in this instance with the MyApp class. With this change the retry logic should work as expected. Here is a blog post that I had written on if you are interested in more details - http://biju-allandsundry.blogspot.com/2014/12/spring-retry-ways-to-integrate-with.html
Biju Thanks for putting up a link for this, it helped me to resolve a lot of questions. The only thing that I had to seperately was I still had to add 'retryAdvice' as a bean in the spring xml based approach, Also had to make sure I had context annotation enabled and aspectj is available at classpath. After I added these below I could get it working.
<bean id="retryAdvice"
class="org.springframework.retry.interceptor.RetryOperationsInterceptor">
</bean>
<context:annotation-config />
<aop:aspectj-autoproxy />