Testing Jersey-Spring Integration with JerseyTest, Maven and TestNG - spring

I want to test my Jersey resources with the Jersey Test-Framework.
I followed the descriptions provided here
http://blogs.oracle.com/naresh/entry/jersey_test_framework_makes_it
http://zhanghaoeye.javaeye.com/blog/441759
to create a simple example. My example is hosted as git repository on http://github.com/rmetzler/Jersey-Test .
$ mvn jetty:run works as expected but I keep getting NullPointerExceptions when running $ mvn clean test.
java.lang.NullPointerException
at com.sun.jersey.spi.container.ContainerResponse.mapException(ContainerResponse.java:429)
at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1295)
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1239)
at com.sun.jersey.test.framework.impl.container.inmemory.TestResourceClientHandler.handle(TestResourceClientHandler.java:119)
at com.sun.jersey.api.client.Client.handle(Client.java:616)
at com.sun.jersey.api.client.WebResource.handle(WebResource.java:559)
at com.sun.jersey.api.client.WebResource.get(WebResource.java:182)
at example.jersey.spring.MyResourceTest.testMyResource(MyResourceTest.java:30)
...
I bet I made a small mistake that I'm unable to find. I would show my source to another developer but unfortunately I work alone at home. So maybe someone of you could help me?
UPDATE
I created an Eclipse project by running $ mvn eclipse:eclipse . Now when I run the test as JUnit Test in Eclipse it is green. When running it as TestNG Test it fails. So I guess it has something to do with how the test is executed by TestNG.

I did the same thing except for I am using guice, not spring. This is my implementation (sorry, no time to clean up, you'll have to extract the interesting parts yourself). Note that I used a delegate jerseytest class so I can inherit some code from my base test class. Also you have to map the junit pre- and post-methods to testng ones.
Hope this helps a bit.
package mypackage;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URL;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.apache.commons.lang.UnhandledException;
import org.apache.xerces.dom.DOMInputImpl;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.FunctionObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.w3c.dom.Document;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.reflect.core.Reflection.staticMethod;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
import com.sun.jersey.spi.container.WebApplication;
import com.sun.jersey.spi.container.WebApplicationFactory;
import com.sun.jersey.test.framework.AppDescriptor;
import com.sun.jersey.test.framework.JerseyTest;
import com.sun.jersey.test.framework.LowLevelAppDescriptor;
import com.sun.jersey.test.framework.impl.container.inmemory.TestResourceClientHandler;
import com.sun.jersey.test.framework.spi.container.TestContainer;
import com.sun.jersey.test.framework.spi.container.TestContainerException;
import com.sun.jersey.test.framework.spi.container.TestContainerFactory;
import com.sun.jersey.test.framework.spi.container.inmemory.InMemoryTestContainerFactory;
import mypackage.StaticConfig;
import mypackage.MediaTypes;
public abstract class JerseyIntegrationTestBase extends TransactionalIntegrationTestBase {
private static final Logger LOG = LoggerFactory.getLogger( JerseyIntegrationTestBase.class );
private static final class GuiceInMemoryTestContainerFactory extends InMemoryTestContainerFactory {
#Override
public TestContainer create( final URI baseUri, final AppDescriptor ad ) {
return new GuiceInMemoryTestContainer( baseUri, (LowLevelAppDescriptor) ad );
}
/**
* Kopie der Klasse im inmemory-Testcontainer von Jersey, leicht an Guice-Injection angepasst. The class defines methods for
* starting/stopping an in-memory test container, and for running tests on the container.
*/
private static class GuiceInMemoryTestContainer implements TestContainer {
final URI baseUri;
final ResourceConfig resourceConfig;
final WebApplication webApp;
/**
* Creates an instance of {#link GuiceInMemoryTestContainer}
*
* #param Base
* URI of the application
* #param An
* instance of {#link LowLevelAppDescriptor}
*/
private GuiceInMemoryTestContainer( final URI baseUri, final LowLevelAppDescriptor ad ) {
this.baseUri = UriBuilder.fromUri( baseUri ).build();
LOG.info( "Creating low level InMemory test container configured at the base URI " + this.baseUri );
resourceConfig = ad.getResourceConfig();
// Falls man mal in Tests die requests und responses sehen möchte:
// this.resourceConfig.getProperties().put( ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
// LoggingFilter.class.getName() );
// this.resourceConfig.getProperties().put( ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
// LoggingFilter.class.getName() );
resourceConfig.getProperties().putAll( StaticConfig.getJerseyParams() );
webApp = WebApplicationFactory.createWebApplication();
}
#Override
public Client getClient() {
ClientConfig clientConfig = null;
final Set providerSingletons = resourceConfig.getProviderSingletons();
if ( providerSingletons.size() > 0 ) {
clientConfig = new DefaultClientConfig();
for ( final Object providerSingleton : providerSingletons ) {
clientConfig.getSingletons().add( providerSingleton );
}
}
final Client client = clientConfig == null
? new Client( new TestResourceClientHandler( baseUri, webApp ) )
: new Client( new TestResourceClientHandler( baseUri, webApp ), clientConfig );
return client;
}
#Override
public URI getBaseUri() {
return baseUri;
}
#Override
public void start() {
if ( !webApp.isInitiated() ) {
LOG.info( "Starting low level InMemory test container" );
webApp.initiate( resourceConfig, new GuiceContainer( null ).new ServletGuiceComponentProviderFactory(
resourceConfig, IntegrationTestBase.getInjector() ) );
}
}
#Override
public void stop() {
if ( webApp.isInitiated() ) {
LOG.info( "Stopping low level InMemory test container" );
webApp.destroy();
}
}
}
}
private final JerseyTest _jerseyTest;
public JerseyIntegrationTestBase() {
// PackagesResourceConfig.getPackages macht genau das, was wir wollen, ist aber private, daher
// auf die harte Tour...
// FORMATTER: OFF
final String[] packages =
staticMethod( "getPackages" ).withReturnType( String[].class ).withParameterTypes( Map.class )
.in( PackagesResourceConfig.class ).invoke( StaticConfig.getJerseyParams() );
// FORMATTER: ON
_jerseyTest = new JerseyTest( new LowLevelAppDescriptor.Builder( packages ).build() ) {
#Override
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
return new GuiceInMemoryTestContainerFactory();
}
};
}
/**
* #return
* #see JerseyTest#client().
*/
protected Client client() {
return _jerseyTest.client();
}
#BeforeClass( alwaysRun = true )
public void setUp() throws Exception {
_jerseyTest.setUp();
}
#AfterClass( alwaysRun = true )
public void tearDown() throws Exception {
_jerseyTest.tearDown();
}
}

Because JerseyTest is using #Before annotation from Junit for initialising the application, and you have to extend JerseyTest to enable testng support, like this:
public class JerseyTestNG extends JerseyTest {
#Override
protected Application configure() {
ResourceConfig config = new ResourceConfig(YourService.class);
}
#BeforeClass
public void setUp() {
super.setUp();
}
#AfterClass
public void tearDown() {
super.tearDown();
}
}
#BeforeClass also will make sure all tests within are executed after the Jersey container is ready when using surefire plugin. Otherwise those tests will fail quickly.
and if you want to it read applicationContext-test.xml other than the default one, set one more property to ResourceConfig:
config.setProperties(new HashMap<String, String>() {{
put("contextConfigLocation", "applicationContext-test.xml");
}});
also, adding some features to ClientConfig maybe helpful:
#Override
protected void configureClient(ClientConfig config) {
config.register(LoggingFilter.class);
config.register(MOXyJsonProvider.class);
config.register(new EncodingFeature(GZipEncoder.class));
}
All tested on Jersey 2.6.

A simple example using Jersey + Spring + TestNG + Jetty here:
http://ameethpaatil.blogspot.com/2011/09/restful-webservice-jersey-spring-30x.html

Related

Running a quarkus main (command line like) from an AWS lambda handler method

I have a quarkus-camel batch application that needs to run under a lambda in AWS. This is working fine with pure java and spring-boot.
I need to be able to start the Quarkus Application from the AWS lambda handler method.
Running in batch works fine, but under lambda I get the following error:
Caused by: io.quarkus.bootstrap.BootstrapException: Failed to determine the Maven artifact associated with the application /var/task
This is the main java class. I need to know what to do in the handleRequest method to start the Quarkus (CAMEL) application.
package com.example;
import io.quarkus.runtime.annotations.QuarkusMain;
import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.arc.Arc;
import io.quarkus.runtime.QuarkusApplication;
import org.apache.camel.quarkus.core.CamelRuntime;
import javax.inject.Inject;
import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
#QuarkusMain
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
public static void main(String... args) {
Quarkus.run(CamelApp.class, args);
}
public static class CamelApp implements QuarkusApplication {
#Inject
ProducerTemplate camelProducer;
#Inject
CamelContext camelContext;
#Override
public int run(String... args) throws Exception {
System.out.println("Hello Camel");
CamelRuntime runtime = Arc.container().instance(CamelRuntime.class).get();
runtime.start(args);
camelProducer.sendBody("direct:lambda", "how about this?");
return runtime.waitForExit();
}
}
public Object handleRequest(final Object input, final Context context) {
logger.info("input: {}", gson.toJson(input));
logger.info("context: {}", gson.toJson(context));
Quarkus.run(CamelApp.class);
// CamelRuntime runtime = Arc.container().instance(CamelRuntime.class).get();
// runtime.start(new String[] {"A","B","C"});
// camelProducer.sendBody("direct:lambda", "how about this?");
// runtime.waitForExit();
return input;
}
}

JUnit 5 exlude tagged test methods within test class

#SpringBootTest
#AutoConfigureMockMvc
#ExcludeTags({"no"})
public class MyClassTest {
#Test
public void test1() {
}
#Test
#Tag("no")
public void test2() {
}
...
}
#RunWith(JUnitPlatform.class)
#SelectClasses({MyClassTest.class})
#IncludeTags({"no"})
public class MyClassTestSuiteTest {
}
Having a Spring Boot 2.3.1 project and testing some REST controllers, in a test class some of the test methods are tagged, and shall not be run, when MyClassTest is run. The annotated methods are run in a test suite (with #IncludeTags("no"). JUnit 5.6.2.
With the test suite I'm not sure it #RunWith has to be used for a test suite, or the JUnit 5 #ExtendWith is the right one? In fact, if not necessary, I don't want to mix JUnit 4 and 5, stick to JUnit 5.
Is there a way to configure simply via annotation or similar, to not run the tagged methods when MyClassTest is run? Like #ExcludeTags for test suites, but this does not work on a class like in the example.
Perhaps two test suites can be created, one with #ExludeTags("no"), one with #IncludeTags("no"). But still, how to prevent then that MyClassTest it run at all?
I don't want to create some Run Configuration in a particular IDE. The preferred way would be to use annotations or similar. Perhaps a Maven configuration would also suffice.
Perhaps on test method level execution of the particular test method can be avoided with some criteria evaluation, if the executed test class is MyClassTest, then don't run that test method.
Interesting here is, I cannot replace #RunWith(JUnitPlatform.class) simply with #ExtendWith(JUnitPlatform.class) as there is type incompatibility. Using #ExtendWith(SpringExtension.class) doesn't give me the possibility to run the class (for example with right-click on the class name, no entry to Run/Debug). But #ExtendWith replaces #RunWith in JUnit 5, what extension to use to run the test suite?
Create Execution Condition ExcludeTagsCondition
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.commons.util.AnnotationUtils;
public class ExcludeTagsCondition implements ExecutionCondition {
private static final ConditionEvaluationResult ENABLED_IF_EXCLUDE_TAG_IS_INVALID =
ConditionEvaluationResult.enabled(
"#ExcludeTags does not have a valid tag to exclude, all tests will be run");
private static Set<String> tagsThatMustBeIncluded = new HashSet<>();
public static void setMustIncludeTags(final Set<String> tagsThatMustBeIncluded) {
ExcludeTagsCondition.tagsThatMustBeIncluded = new HashSet<>(tagsThatMustBeIncluded);
}
#Override
public ConditionEvaluationResult evaluateExecutionCondition(
ExtensionContext context) {
final AnnotatedElement element = context
.getElement()
.orElseThrow(IllegalStateException::new);
final Optional<Set<String>> tagsToExclude = AnnotationUtils.findAnnotation(
context.getRequiredTestClass(),
ExcludeTags.class
)
.map(a ->
Arrays.asList(a.value())
.stream()
.filter(t -> !tagsThatMustBeIncluded.contains(t))
.collect(Collectors.toSet())
);
if (!tagsToExclude.isPresent() || tagsToExclude.get().stream()
.allMatch(s -> (s == null) || s.trim().isEmpty())) {
return ENABLED_IF_EXCLUDE_TAG_IS_INVALID;
}
final Optional<String> tag = AnnotationUtils.findAnnotation(element, Tag.class)
.map(Tag::value);
if (tagsToExclude.get().contains(tag.map(String::trim).orElse(""))) {
return ConditionEvaluationResult
.disabled(String.format(
"test method \"%s\" has tag \"%s\" which is on the #ExcludeTags list \"[%s]\", test will be skipped",
(element instanceof Method) ? ((Method) element).getName()
: element.getClass().getSimpleName(),
tag.get(),
tagsToExclude.get().stream().collect(Collectors.joining(","))
));
}
return ConditionEvaluationResult.enabled(
String.format(
"test method \"%s\" has tag \"%s\" which is not on the #ExcludeTags list \"[%s]\", test will be run",
(element instanceof Method) ? ((Method) element).getName()
: element.getClass().getSimpleName(),
tag.orElse("<no tag present>"),
tagsToExclude.get().stream().collect(Collectors.joining(","))
));
}
}
Create annotation #ExcludeTags
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#ExtendWith(ExcludeTagsCondition.class)
public #interface ExcludeTags {
String[] value();
}
On your test
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
#ExcludeTags({"foo", "bar"})
#SpringBootTest
class AppTest {
#Test
#Tag("foo")
void test1() {
System.out.println("test1");
}
#Test
#Tag("bar")
void test2() {
System.out.println("test2");
}
#Test
#Tag("baz")
void test3() {
System.out.println("test3");
}
}
When you run the test, you should see the following output:
test method "test1" has tag "foo" which is on the #ExcludeTags list "[bar,foo]", test will be skipped
test method "test2" has tag "bar" which is on the #ExcludeTags list "[bar,foo]", test will be skipped
test3
And your test runner should show 1 test passing and 2 skipped.
Now for your test suite:
Create an annotation #MustIncludeTags
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
public #interface MustIncludeTags {
String[] value();
}
Now setup your test suite like so:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.runner.RunWith;
#RunWith(JUnitPlatform.class)
#SelectClasses({MyTestSuite.SetupTests.class, AppTest.class})
#MustIncludeTags({"foo", "bar"})
public class MyTestSuite {
public static class SetupTests {
#BeforeAll
public static void beforeClass() {
ExcludeTagsCondition.setMustIncludeTags(
Optional.ofNullable(MyTestSuite.class.getAnnotation(MustIncludeTags.class))
.map(MustIncludeTags::value)
.map(Arrays::asList)
.orElse(new ArrayList<>())
.stream()
.collect(Collectors.toSet())
);
}
#Disabled
#Test
void testDummy() {
// this test needs to be present for the beforeAll to run
}
}
}
When you run your test suite with the #MustIncludeTags the #ExcludedTags are overridden.
As you can see from the following test execution:

Asserting log messages using Mockito, TestNG and Log4j2

I've been following asserting-log-messages-with-log4j2-and-mockito to write TestNG test to test logging for Log4j2. Most of what is written in the post seems to work. However, when I'm running my test I'm getting:
Wanted but not invoked:
appender.append(<Capturing argument>);
-> at LoggingTest.test(LoggingTest.java:105)
However, there were exactly 2 interactions with this mock:
appender.getName();
-> at org.apache.logging.log4j.core.config.AbstractConfiguration.addAppender(AbstractConfiguration.java:603)
appender.getName();
-> at org.apache.logging.log4j.core.config.AppenderControl.<init>(AppenderControl.java:51)
My TestNGclass is:
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.mockito.ArgumentCaptor;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class LoggingTest {
private Appender appender;
private ArgumentCaptor<LogEvent> captor;
#BeforeMethod
public void setUp() {
appender = mock(Appender.class);
captor = ArgumentCaptor.forClass(LogEvent.class);
reset(appender);
when(appender.getName()).thenReturn("Appender");
when(appender.isStarted()).thenReturn(true);
when(appender.isStopped()).thenReturn(false);
LoggerContext context = (LoggerContext)LogManager.getContext();
Configuration config = context.getConfiguration();
config.addAppender(appender);
LoggerConfig rootConfig = config.getRootLogger();
rootConfig.setLevel(Level.INFO);
rootConfig.addAppender(appender, Level.INFO, null);
context.updateLoggers();
}
#Test
public void test() {
LogManager.getLogger().info("testing");
verify(appender).append(captor.capture());
LogEvent logEvent = captor.getValue();
assertThat(logEvent.getMessage()).isEqualTo("test");
}
}
I've been looking at this for a while and have not been able to find a my error. Could someone point me in the right direction?

Spring Boot & Hibernate Validation's ConstraintMappingContributor

The hibernate validations documentation describes how to create ConstraintMappingContributors here.
It states:
You then need to specify the fully-qualified class name of the
contributor implementation in META-INF/validation.xml, using the
property key hibernate.validator.constraint_mapping_contributors. You
can specify several contributors by separating them with a comma.
Given I have many of these, what would be the most appropriate way to auto-discover these i.e. via #Component and add them dynamically at runtime to the ConstrainMappingConfiguration during Spring Boot startup.
For example.. if a developer creates a new ConstraintMappingContributor, it should be picked up and added automatically when spring boot starts, requiring no other file changes.
This is what I came up with, seems to be working for me.
package...
import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
#Configuration
public class ValidationConfiguration {
private final List<ConstraintMappingContributor> contributors;
public ValidationConfiguration(Optional<List<ConstraintMappingContributor>> contributors) {
this.contributors = contributors.orElseGet(ArrayList::new);
}
#Bean
public LocalValidatorFactoryBean validatorFactory() {
return new ValidatorFactoryBean(this.contributors);
}
}
package...
import org.hibernate.validator.HibernateValidatorConfiguration;
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import javax.validation.Configuration;
import java.util.List;
public class ValidatorFactoryBean extends LocalValidatorFactoryBean {
private final List<ConstraintMappingContributor> contributors;
ValidatorFactoryBean(List<ConstraintMappingContributor> contributors) {
this.contributors = contributors;
}
#Override
protected void postProcessConfiguration(Configuration<?> cfg) {
if (cfg instanceof HibernateValidatorConfiguration) {
HibernateValidatorConfiguration configuration = (HibernateValidatorConfiguration) cfg;
this.contributors.forEach(contributor -> contributor.createConstraintMappings(() -> {
DefaultConstraintMapping mapping = new DefaultConstraintMapping();
configuration.addMapping(mapping);
return mapping;
}));
}
}
}
I invoke it like this...
if(SpringValidatorAdapter.class.isInstance(this.validatorFactory)){
SpringValidatorAdapter.class.cast(this.validatorFactory).validate(entity, errors);
}

Spring aop doesn't run when project starts

I'v implemented a spring-boot aop demo and it runs well, but when I want to use it to load some resource when the project starts, it doesn't work somehow
Aop:
package com.neo.mysql;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* Created by li_weia on 2017/7/6.
*/
#Aspect
#Component
public class DynamicDataSourceAspect {
#Before("#annotation(VendorSource)")
public void beforeSwitchDS(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在#DS注解
if (method.isAnnotationPresent(VendorSource.class)) {
VendorSource annotation = method.getAnnotation(VendorSource.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
#After("#annotation(VendorSource)")
public void afterSwitchDS(JoinPoint point){
DataSourceContextHolder.clearDB();
}
}
The VendorSource annotation:
package com.neo.mysql;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by li_weia on 2017/7/6.
*/
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface VendorSource {
String value() default "vendor-master";
}
It runs well here, I can successfully change datasource by annotation:
package com.neo.web;
import com.neo.entity.SiteEntity;
import com.neo.mapper.ClassMappingDao;
import com.neo.mysql.VendorSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
public class UserController {
private final ClassMappingDao siteMapper;
#Autowired(required = false)
public UserController(ClassMappingDao siteMapper) {
this.siteMapper = siteMapper;
}
#RequestMapping("/getSites")
#VendorSource("vendor-read")
public List<SiteEntity> getUsers() {
return siteMapper.getAllSite();
}
}
but it doesn't work here, the aop method is not invoked at all:
package com.neo.component;
import com.neo.entity.SiteEntity;
import com.neo.mapper.ClassMappingDao;
import com.neo.mysql.VendorSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* Created by li_weia on 2017/7/7.
*/
#Component
public class TestComponent{
private final ClassMappingDao userMapper;
#Autowired(required = false)
public TestComponent(ClassMappingDao userMapper) {
this.userMapper = userMapper;
init();
}
#VendorSource("vendor-read")
public void init() {
List<SiteEntity> sites = userMapper.getAllSite();
for(SiteEntity site: sites){
System.out.println(site.getSite());
}
}
}
You need to fully qualify the annotation, like so:
#Before("execution(public * *(..)) && #annotation(com.neo.mysql.VendorSource)")
private void whatever() {}
Also, as mentioned in my comment above, you need to have spring-boot-starter-aop on classpath. Maybe you already do, but since you didn't say, it's worth mentioning.
Edit:
I didn't notice the real problem before, I wasn't paying attention.
Spring AOP only triggers if you make calls from another class. This is because Spring needs to be able to intercept the call and run the pointcut. Calling the method from constructor is not going to do anything.
You can do a hackish workaround. Create a #PostConstruct void postConstruct() {} method in your class (not constructor), autowire ApplicationContext, and then do MyClassWithInitMethod myClass = context.getBean(MyClassWithInitMethod.class) in the postConstruct method. Then call the method on myClass, and AOP will kick in.
Frankly, I didn't previously check what you are doing in your pointcut, and it's a terrible idea. When multiple threads run, they are going to overwrite the static context, and create a race-condition that you'll then create another question for. Don't do it! Use the factory pattern instead, and inject the DataSourceFactory in the classes that now have the annotation.

Resources