How to get javax.validation payload validation for spring-cloud-aws in QueueMessageHandler working? - spring-cloud

I'm writing some message consumer for an AWS SQS and want to validate the received message by using the javax.validation.constraints annotations.
Unfortunately I had to find out, that the used PayloadArgumentResolver of the spring-cloud-aws-messaging dependency uses a NoOpValidator.
So I tried to inject my own HandlerMethodArgumentResolver for the payload.
#Bean
public QueueMessageHandlerFactory queueMessageHandlerFactory(
final ObjectMapper objectMapper, final Validator hibernateValidator) {
final MappingJackson2MessageConverter jacksonMessageConverter =
new MappingJackson2MessageConverter();
jacksonMessageConverter.setSerializedPayloadClass(String.class);
jacksonMessageConverter.setStrictContentTypeMatch(true);
jacksonMessageConverter.setObjectMapper(objectMapper);
final QueueMessageHandlerFactory factory = new QueueMessageHandlerFactory();
final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
argumentResolvers.add(new HeaderMethodArgumentResolver(null, null));
argumentResolvers.add(new HeadersMethodArgumentResolver());
argumentResolvers.add(new NotificationSubjectArgumentResolver());
argumentResolvers.add(new AcknowledgmentHandlerMethodArgumentResolver("Acknowledgment"));
argumentResolvers.add(new VisibilityHandlerMethodArgumentResolver("Visibility"));
final PayloadArgumentResolver payloadArgumentResolver =
new PayloadArgumentResolver(jacksonMessageConverter, hibernateValidator);
argumentResolvers.add(payloadArgumentResolver);
factory.setArgumentResolvers(argumentResolvers);
return factory;
}
So far so good and at first sight, it works well...
BUT as you can see I had to add the already in QueueMessageHandler existing argument resolvers as well to resolve the headers via #Headers/#Header of the message, too.
#SqsListener(
value = "queue",
deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS)
public void consume(
#Payload #Validated final QueueMessage queueMessage,
#Headers final Map<String,Object> headers) {
}
When I only add my PayloadArgumentResolver with the hibernate validator, it will be also used to resolve the headers, doh!
Is there any pretty solution for this or should I open an issue at spring-cloud-aws? I just want my payload to be validated via annotations :(

I don't think this is the best awswer but I have a working sample project that have this type of validation: https://github.com/Haple/sqslistener
#Data
#RequiredArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
#NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
public class EventDTO {
#NotNull(message = "foo is mandatory")
private final String foo;
#NotNull(message = "bar is mandatory")
private final String bar;
}
#Slf4j
#Service
#AllArgsConstructor
public class SampleListener {
#SqsListener("test_queue")
public void execute(final #Valid #Payload EventDTO event) {
log.info("OK: {}", event);
}
}
#Configuration
public class MessageHandler {
#Bean
QueueMessageHandler queueMessageHandler(final AmazonSQSAsync amazonSQSAsync,
final MessageConverter messageConverter,
final Validator validator) {
final QueueMessageHandlerFactory queueMessageHandlerFactory = new QueueMessageHandlerFactory();
final PayloadMethodArgumentResolver payloadMethodArgumentResolver = new PayloadMethodArgumentResolver(messageConverter, validator);
queueMessageHandlerFactory.setArgumentResolvers(Collections.singletonList(payloadMethodArgumentResolver));
queueMessageHandlerFactory.setAmazonSqs(amazonSQSAsync);
queueMessageHandlerFactory.setMessageConverters(Collections.singletonList(messageConverter));
return queueMessageHandlerFactory.createQueueMessageHandler();
}
}

Related

Dynamic injection using #SpringBean in wicket

I have a form that based on collected information generates a report. I have multiple sources from which to generate reports, but the form for them is the same. I tried to implement strategy pattern using an interface implementing report generator services, but that led to wicket complaining about serialization issues of various parts of the report generator. I would like to solve this without duplicating the code contained in the form, but I have not been able to find information on dynamic injection with #SpringBean.
Here is a rough mock up of what I have
public class ReportForm extends Panel {
private IReportGenerator reportGenerator;
public ReportForm(String id, IReportGenerator reportGenerator) {
super(id);
this.reportGenerator = reportGenerator;
final Form<Void> form = new Form<Void>("form");
this.add(form);
...
form.add(new AjaxButton("button1") {
private static final long serialVersionUID = 1L;
#Override
protected void onSubmit(AjaxRequestTarget target)
{
byte[] report = reportGenerator.getReport(...);
...
}
});
}
}
If I do it this way, wicket tries to serialize the concrete instance of reportGenerator. If I annotate the reportGenerator property with #SpringBean I receive Concrete bean could not be received from the application context for class: IReportGenerator
Edit: I have reworked implementations of IRerportGenerator to be able to annotate them with #Component and now I when I use #SpringBean annotation I get More than one bean of type [IReportGenerator] found, you have to specify the name of the bean (#SpringBean(name="foo")) or (#Named("foo") if using #javax.inject classes) in order to resolve this conflict. Which is exactly what I don't want to do.
I think the behavior you're trying to achieve can be done with a slight workaround, by introducing a Spring bean that holds all IReportGenerator instances:
#Component
public class ReportGeneratorHolder {
private final List<IReportGenerator> reportGenerators;
#Autowired
public ReportGeneratorHolder(List<IReportGenerator> reportGenerators) {
this.reportGenerators = reportGenerators;
}
public Optional<IReportGenerator> getReportGenerator(Class<? extends IReportGenerator> reportGeneratorClass) {
return reportGenerators.stream()
.filter(reportGeneratorClass::isAssignableFrom)
.findAny();
}
}
You can then inject this class into your Wicket page, and pass the desired class as a constructor-parameter. Depending on your Spring configuration you might need to introduce an interface for this as well.
public class ReportForm extends Panel {
#SpringBean
private ReportGeneratorHolder reportGeneratorHolder;
public ReportForm(String id, Class<? extends IReportGenerator> reportGeneratorClass) {
super(id);
IReportGenerator reportGenerator = reportGeneratorHolder
.getReportGenerator(reportGeneratorClass)
.orElseThrow(IllegalStateException::new);
// Form logic omitted for brevity
}
}
As far as I am able to find, looking through documentation and even the source for wicket #SpringBean annotation, this isn't possible. The closest I got is with explicitly creating a proxy for a Spring bean based on class passed. As described in 13.2.4 Using proxies from the wicket-spring project chapter in Wicket in Action.
public class ReportForm extends Panel {
private IReportGenerator reportGenerator;
private Class<? extends IReportGenerator> classType;
private static ISpringContextLocator CTX_LOCATOR = new ISpringContextLocator() {
public ApplicationContext getSpringContext() {
return ((MyApplication)MyApplication.get()).getApplicationContext();
}
};
public ReportForm(String id, Class<? extends IReportGenerator> classType) {
super(id);
this.classType = classType;
final Form<Void> form = new Form<Void>("form");
this.add(form);
...
form.add(new AjaxButton("button1") {
private static final long serialVersionUID = 1L;
#Override
protected void onSubmit(AjaxRequestTarget target)
{
byte[] report = getReportGenerator().getReport(...);
...
}
});
}
private <T> T createProxy(Class<T> classType) {
return (T) LazyInitProxyFactory.createProxy(classType, new
SpringBeanLocator(classType, CTX_LOCATOR));
}
private IReportGenerator getReportGenerator() {
if (reportGenerator = null) {
reportGenerator = createProxy(classType);
}
return reportGenerator;
}
}

Spring-Boot RestController: Passing Id as String not working

I connected my Spring-Boot-Application to a MongoDB. The application is nothing serious, just for getting into working with spring and MongoDB.
The problem it, that my id is a String and I get an Internal Server Error, when I pass the id of a database entry, in order to get it byId...
This is my domain class:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Document(collection = "songinfo")
public class SongInfo {
#Id
private String id;
private int songId;
private String songName;
private String description;
}
The Controller-Method:
#RequiredArgsConstructor
#RestController
#RequestMapping("/songsinfo")
public class SongsInfoController {
private final SongInfoService songInfoService;
#GetMapping(value = "/{id}", headers = "Accept=application/json", produces =
{MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<SongInfo> getSongInfoById(#PathVariable(value = "id") String id) {
SongInfo songInfo = songInfoService.getSongInfoById(id);
if (songInfo == null)
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
return new ResponseEntity<>(songInfo, HttpStatus.OK);
}
The SongInfoServiceImpl:*
#Override
public SongInfo getSongInfoById(String id) {
return songInfoRepository.findById(id).orElseThrow(NotFoundException::new);
}
This is the SongsInfoRepository:
public interface SongInfoRepository extends MongoRepository<SongInfo, String> {
}
Getting all songinfos from the database is working fine:
But when is pass the id from one of these entries, I get this:
What is wrong here with my implementation?
You're throwing the exception in SongInfoServiceImpl which is not handled in your SongsInfoController Class.
Solution 1: Instead of throwing the exception return null.
SongInfoServiceImpl.java
#Override
public SongInfo getSongInfoById(String id) {
return songInfoRepository.findById(id).orElse(null);
}
Solution 2: Add try catch block
SongsInfoController.java
#RequiredArgsConstructor
#RestController
#RequestMapping("/songsinfo")
public class SongsInfoController {
private final SongInfoService songInfoService;
#GetMapping(value = "/{id}",
headers = "Accept=application/json",
produces = {MediaType.APPLICATION_JSON_VALUE}
)
public ResponseEntity<SongInfo> getSongInfoById(#PathVariable(value = "id") String id) {
SongInfo songInfo = null;
try {
songInfo = songInfoService.getSongInfoById(id);
} catch(Exception e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(songInfo, HttpStatus.OK);
}
}
I think you need to divide two problem.
Check id parameter SongsInfoController
Inside controller check your parameter is valid through log or sysout
Check getSongInfoById method in SongInfoServiceImpl
Simply getSongInfoById(8752); is get error?
I want to add comment but my reputation is under 50.
If you comment above two solution check result, then I will add additional answer.

How to create a BitBucket hook that reject pushes with bad file name?

I'm trying to make a BitBucket hook that would reject a push if it contained a file not matching a naming convention. So far, I'm able to create a PreRepositoryHook implementation that register the following callback.
public class MyPreRepositoryHook implements PreRepositoryHook<RepositoryHookRequest> {
public MyPreRepositoryHook () {
}
#Nonnull
#Override
public RepositoryHookResult preUpdate(#Nonnull PreRepositoryHookContext context,
#Nonnull RepositoryHookRequest request) {
// hook only wants commits added to the repository
context.registerCommitCallback(
new MyPreCommitCallback(),
RepositoryHookCommitFilter.ADDED_TO_REPOSITORY);
// return accepted() here, the callback gets a chance to reject the change when getResult() is called
return RepositoryHookResult.accepted();
}
In MyPreCommitCallback:
#Override
public boolean onCommitAdded(#Nonnull CommitAddedDetails commitDetails) {
Commit commit = commitDetails.getCommit();
SimpleChangeset.Builder builder = new SimpleChangeset.Builder(commit);
SimpleChangeset simpleChangeset = builder.build();
Page<Change> changes = simpleChangeset.getChanges();
}
But I am unable to get the list of files since the call to simpleChangeset.getChanges always return null.
Any help in getting a list of file names would be appreciated. Thank you.
#Component
public class AltresPreRepositoryHook implements PreRepositoryHook<RepositoryHookRequest> {
private final CommitService commitService;
#Autowired
public AltresPreRepositoryHook(#ComponentImport CommitService commitService) {
this.commitService = commitService;
}
private static class AltresPreCommitCallback implements PreRepositoryHookCommitCallback {
private final RepositoryHookRequest request;
private final CommitService commitService;
private RepositoryHookResult result = RepositoryHookResult.accepted();
public AltresPreCommitCallback(RepositoryHookRequest request, CommitService commitService) {
this.request = request;
this.commitService = commitService;
}
#Nonnull
#Override
public RepositoryHookResult getResult() {
return result;
}
#Override
public boolean onCommitAdded(#Nonnull CommitAddedDetails commitDetails) {
Commit commit = commitDetails.getCommit();
ChangesRequest.Builder builder = new ChangesRequest.Builder(commit.getRepository(), commit.getId());
ChangesRequest changesRequest = builder.build();
final ChangedPathsCollector changedPathsCollector = new ChangedPathsCollector();
commitService.streamChanges(changesRequest, changedPathsCollector);
Collection<String> changedPaths = changedPathsCollector.getChangedPaths();

Custom DynamoDb TableNameResolver not being called when using CrudRepository

I am testing DynamoDB tables and want to set up different table names for prod and dev environment using the keyword"dev" for development and prod for production.
I have a POJO
#DynamoDBTable(tableName = "abc_xy_dev_MyProjectName_Employee")
public class Employee implements Cloneable {
}
On Prod I want its name to be abc_xy_prod_MyProjectName_Employee.
So, I wrote a TableNameResolver
public static class MyTableNameResolver implements TableNameResolver {
public static final MyTableNameResolver INSTANCE = new MyTableNameResolver();
#Override
public String getTableName(Class<?> clazz, DynamoDBMapperConfig config) {
final TableNameOverride override = config.getTableNameOverride();
String tableNameToReturn = null;
if (override != null) {
final String tableName = override.getTableName();
if (tableName != null) {
System.out.println("MyTableNameResolver ==================================");
return tableName;
}
}
String env = System.getenv("DEPLOYMENT_ENV");
for(Annotation annotation : clazz.getAnnotations()){
if(annotation instanceof DynamoDBTable){
DynamoDBTable myAnnotation = (DynamoDBTable) annotation;
if ("production".equals(env)){
tableNameToReturn = myAnnotation.tableName().replace("_dev_", "_prod_");
}
else {
tableNameToReturn = myAnnotation.tableName();
}
}
}
return tableNameToReturn;
}
}
This works by creating a table with the name abc_xy_prod_MyProjectName_Employee in production.
However, I have a repository with the following code
#EnableScan
public interface EmployeeRepository extends CrudRepository<Employee, String>
{
#Override
<S extends Employee> S save(S employee);
Optional<Employee> findById(String id);
#Override
List<Employee> findAll();
Optional<Employee> findByEmployeeNumber(String EmployeeNumber);
}
Thus when i try to call the method findAll via a endpoint /test case, i get the exception
There was an unexpected error (type=Internal Server Error,
status=500). User:
arn:aws:iam::87668976786:user/svc_nac_ps_MyProjectName_prod is not
authorized to perform: dynamodb:Scan on resource:
:table/abc_xy_dev_MyProjectName_Employee (Service: AmazonDynamoDBv2;
Status Code: 400; Error Code: AccessDeniedException; Request ID:
aksdnhLDFL)
i.e MyTableNameResolver doesn't get called internally when the respository methods are executed. It still points to table name with the name abc_xy_dev_MyProjectName_Employee given in the annotation #DynamoDBTable(tableName = "abc_xy_dev_MyProjectName_Employee")
You have used spring JPA as persistence dynamoDB Integration.
Below configuration can be used to set table name override as part of spring boot configuration.
Sample example is found in https://github.com/ganesara/SpringExamples/tree/master/spring-dynamo
Map Dynamo db repository with user defined mapper config reference
#EnableDynamoDBRepositories(basePackages = "home.poc.spring", dynamoDBMapperConfigRef="dynamoDBMapperConfig")
Mapper Config for table override is as below
#Bean
public DynamoDBMapperConfig dynamoDBMapperConfig() {
DynamoDBMapperConfig mapperConfig = new DynamoDBMapperConfig
.Builder()
.withTableNameOverride(DynamoDBMapperConfig.TableNameOverride.withTableNamePrefix("PROD_"))
.build();
return mapperConfig;
}
Full configuration for reference
#Configuration
#EnableDynamoDBRepositories(basePackages = "home.poc.spring", dynamoDBMapperConfigRef="dynamoDBMapperConfig")
public class DynamoDBConfig {
#Value("${amazon.dynamodb.endpoint}")
private String amazonDynamoDBEndpoint;
#Value("${amazon.aws.accesskey}")
private String amazonAWSAccessKey;
#Value("${amazon.aws.secretkey}")
private String amazonAWSSecretKey;
#Bean
public AmazonDynamoDB amazonDynamoDB() {
AmazonDynamoDB amazonDynamoDB
= new AmazonDynamoDBClient(amazonAWSCredentials());
if (!StringUtils.isEmpty(amazonDynamoDBEndpoint)) {
amazonDynamoDB.setEndpoint(amazonDynamoDBEndpoint);
}
return amazonDynamoDB;
}
#Bean
public AWSCredentials amazonAWSCredentials() {
return new BasicAWSCredentials(
amazonAWSAccessKey, amazonAWSSecretKey);
}
#Bean
public DynamoDBMapperConfig dynamoDBMapperConfig() {
DynamoDBMapperConfig mapperConfig = new DynamoDBMapperConfig
.Builder()
.withTableNameOverride(DynamoDBMapperConfig.TableNameOverride.withTableNamePrefix("PROD_"))
.build();
return mapperConfig;
}
#Bean
public DynamoDBMapper dynamoDBMapper() {
return new DynamoDBMapper(amazonDynamoDB(), dynamoDBMapperConfig());
}
}
You are using DynamoDBMapper (the Java SDK). Here is how I use it. Lets say I have a table called Users, with an associated User POJO. In DynamoDB I have DEV_Users and LIVE_Users.
I have an environment variable 'ApplicationEnvironmentName' which is either DEV or LIVE.
I create a custom DynamoDBMapper like this:
public class ApplicationDynamoMapper {
private static Map<String, DynamoDBMapper> mappers = new HashMap<>();
private static AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard()
.withRegion(System.getProperty("DynamoDbRegion")).build();
protected ApplicationDynamoMapper() {
// Exists only to defeat instantiation.
}
public static DynamoDBMapper getInstance(final String tableName) {
final ApplicationLogContext LOG = new ApplicationLogContext();
DynamoDBMapper mapper = mappers.get(tableName);
if (mapper == null) {
final String tableNameOverride = System.getProperty("ApplicationEnvironmentName") + "_" + tableName;
LOG.debug("Creating DynamoDBMapper with overridden tablename {}.", tableNameOverride);
final DynamoDBMapperConfig mapperConfig = new DynamoDBMapperConfig.Builder().withTableNameOverride(TableNameOverride.withTableNameReplacement(tableNameOverride)).build();
mapper = new DynamoDBMapper(client, mapperConfig);
mappers.put(tableName, mapper);
}
return mapper;
}
}
My Users POJO looks like this:
#DynamoDBTable(tableName = "Users")
public class User {
...
}
When I want to use the database I create an application mapper like this:
DynamoDBMapper userMapper = ApplicationDynamoMapper.getInstance(User.DB_TABLE_NAME);
If I wanted to a load a User, I would do it like this:
User user = userMapper.load(User.class, userId);
Hope that helps.

How can I change the feign URL during the runtime?

#FeignClient(name = "test", url="http://xxxx")
How can I change the feign URL (url="http://xxxx") during the runtime? because the URL can only be determined at run time.
You can add an unannotated URI parameter (that can potentially be determined at runtime) and that will be the base path that will be used for the request. E.g.:
#FeignClient(name = "dummy-name", url = "https://this-is-a-placeholder.com")
public interface MyClient {
#PostMapping(path = "/create")
UserDto createUser(URI baseUrl, #RequestBody UserDto userDto);
}
And then the usage will be:
#Autowired
private MyClient myClient;
...
URI determinedBasePathUri = URI.create("https://my-determined-host.com");
myClient.createUser(determinedBasePathUri, userDto);
This will send a POST request to https://my-determined-host.com/create (source).
Feign has a way to provide the dynamic URLs and endpoints at runtime.
The following steps have to be followed:
In the FeignClient interface we have to remove the URL parameter. We have to use #RequestLine annotation to mention the REST method (GET, PUT, POST, etc.):
#FeignClient(name="customerProfileAdapter")
public interface CustomerProfileAdaptor {
// #RequestMapping(method=RequestMethod.GET, value="/get_all")
#RequestLine("GET")
public List<Customer> getAllCustomers(URI baseUri);
// #RequestMapping(method=RequestMethod.POST, value="/add")
#RequestLine("POST")
public ResponseEntity<CustomerProfileResponse> addCustomer(URI baseUri, Customer customer);
#RequestLine("DELETE")
public ResponseEntity<CustomerProfileResponse> deleteCustomer(URI baseUri, String mobile);
}
In RestController you have to import FeignClientConfiguration
You have to write one RestController constructor with encoder and decoder as parameters.
You need to build the FeignClient with the encoder, decoder.
While calling the FeignClient methods, provide the URI (BaserUrl + endpoint) along with rest call parameters if any.
#RestController
#Import(FeignClientsConfiguration.class)
public class FeignDemoController {
CustomerProfileAdaptor customerProfileAdaptor;
#Autowired
public FeignDemoController(Decoder decoder, Encoder encoder) {
customerProfileAdaptor = Feign.builder().encoder(encoder).decoder(decoder)
.target(Target.EmptyTarget.create(CustomerProfileAdaptor.class));
}
#RequestMapping(value = "/get_all", method = RequestMethod.GET)
public List<Customer> getAllCustomers() throws URISyntaxException {
return customerProfileAdaptor
.getAllCustomers(new URI("http://localhost:8090/customer-profile/get_all"));
}
#RequestMapping(value = "/add", method = RequestMethod.POST)
public ResponseEntity<CustomerProfileResponse> addCustomer(#RequestBody Customer customer)
throws URISyntaxException {
return customerProfileAdaptor
.addCustomer(new URI("http://localhost:8090/customer-profile/add"), customer);
}
#RequestMapping(value = "/delete", method = RequestMethod.POST)
public ResponseEntity<CustomerProfileResponse> deleteCustomer(#RequestBody String mobile)
throws URISyntaxException {
return customerProfileAdaptor
.deleteCustomer(new URI("http://localhost:8090/customer-profile/delete"), mobile);
}
}
I don`t know if you use spring depend on multiple profile.
for example: like(dev,beta,prod and so on)
if your depend on different yml or properties. you can define FeignClientlike:(#FeignClient(url = "${feign.client.url.TestUrl}", configuration = FeignConf.class))
then
define
feign:
client:
url:
TestUrl: http://dev:dev
in your application-dev.yml
define
feign:
client:
url:
TestUrl: http://beta:beta
in your application-beta.yml (I prefer yml).
......
thanks god.enjoy.
use feign.Target.EmptyTarget
#Bean
public BotRemoteClient botRemoteClient(){
return Feign.builder().target(Target.EmptyTarget.create(BotRemoteClient.class));
}
public interface BotRemoteClient {
#RequestLine("POST /message")
#Headers("Content-Type: application/json")
BotMessageRs sendMessage(URI url, BotMessageRq message);
}
botRemoteClient.sendMessage(new URI("http://google.com"), rq)
You can create the client manually:
#Import(FeignClientsConfiguration.class)
class FooController {
private FooClient fooClient;
private FooClient adminClient;
#Autowired
public FooController(ResponseEntityDecoder decoder, SpringEncoder encoder, Client client) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "http://PROD-SVC");
this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "http://PROD-SVC");
}
}
From documentation: https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html#_creating_feign_clients_manually
In interface you can change url by Spring annotations. The base URI is configured in yml Spring configuration.
#FeignClient(
name = "some.client",
url = "${some.serviceUrl:}",
configuration = FeignClientConfiguration.class
)
public interface SomeClient {
#GetMapping("/metadata/search")
String search(#RequestBody SearchCriteria criteria);
#GetMapping("/files/{id}")
StreamingResponseBody downloadFileById(#PathVariable("id") UUID id);
}
Use #PathVariable like this:
#Service
#FeignClient(name = "otherservicename", decode404 = true)
public interface myService {
#RequestMapping(method = RequestMethod.POST, value = "/basepath/{request-path}")
ResponseEntity<String> getResult(#RequestHeader("Authorization") String token,
#RequestBody HashMap<String, String> reqBody,
#PathVariable(value = "request-path") String requestPath);
}
Then from service, construct the dynamic url path and send the request:
String requestPath = "approve-req";
ResponseEntity<String> responseEntity = myService.getResult(
token, reqBody, requestPath);
Your request url will be at: "/basepath/approve-req"
I prefer to build feign client by configuration to pass a url at run time (in my case i get the url by service name from consul discovery service)
so i extend feign target class as below:
public class DynamicTarget<T> implements Target<T> {
private final CustomLoadBalancer loadBalancer;
private final String serviceId;
private final Class<T> type;
public DynamicTarget(String serviceId, Class<T> type, CustomLoadBalancer loadBalancer) {
this.loadBalancer = loadBalancer;
this.serviceId = serviceId;
this.type = type;
}
#Override
public Class<T> type() {
return type;
}
#Override
public String name() {
return serviceId;
}
#Override
public String url() {
return loadBalancer.getServiceUrl(name());
}
#Override
public Request apply(RequestTemplate requestTemplate) {
requestTemplate.target(url());
return requestTemplate.request();
}
}
var target = new DynamicTarget<>(Services.service_id, ExamsAdapter.class, loadBalancer);
package commxx;
import java.net.URI;
import java.net.URISyntaxException;
import feign.Client;
import feign.Feign;
import feign.RequestLine;
import feign.Retryer;
import feign.Target;
import feign.codec.Encoder;
import feign.codec.Encoder.Default;
import feign.codec.StringDecoder;
public class FeignTest {
public interface someItfs {
#RequestLine("GET")
String getx(URI baseUri);
}
public static void main(String[] args) throws URISyntaxException {
String url = "http://www.baidu.com/s?wd=ddd"; //ok..
someItfs someItfs1 = Feign.builder()
// .logger(new FeignInfoLogger()) // 自定义日志类,继承 feign.Logger
// .logLevel(Logger.Level.BASIC)// 日志级别
// Default(long period, long maxPeriod, int maxAttempts)
.client(new Client.Default(null, null))// 默认 http
.retryer(new Retryer.Default(5000, 5000, 1))// 5s超时,仅1次重试
// .encoder(Encoder)
// .decoder(new StringDecoder())
.target(Target.EmptyTarget.create(someItfs.class));
// String url = "http://localhost:9104/";
//
System.out.println(someItfs1.getx(new URI(url)));
}
}