I am developing a spring boot app with restful services and angularjs as front end. The application must support multiple languages. One of the things I am having problem is the business exceptions thrown from my services. E.g. I have a book service which may throw an exception like this
if (book == null) {
throw new ServiceException("The book you are looking for no longer exist");
}
What is the best approach to localize them?
You have to use #RestControllerAdvice to seprate your exception handling logic from your business code. As per #ControllerAdvice doc, the methods defined in the class annotated as #ControllerAdvice apply globally to all Controllers. #RestControllerAdvice is just a convenience class equal to (#RestControllerAdvice = #ControllerAdvice + #ResponseBody). Please check below class:
#RestControllerAdvice
public class GenericExceptionHandler {
#Autowired
private MessageSource messageSource;
#ExceptionHandler(ServiceException.class)
public ResponseEntity<ErrorResponse> handle(ServiceException.class e, Locale locale) {
String errorMessage = messageSource.getMessage(
"error.message", new Object[]{},locale);
ErrorResponse error = new ErrorResponse();
error.setErrorCode(HttpStatus.BAD_REQUEST.value());
error.setMessage(errorMessage);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
}
// other Custom Exception handlers
Your ErrorResponse is normal javabean as below:
public class ErrorResponse{
private int errorCode;
private String message;
//getter and setter
}
And you should have MessageSource configured in your configuration to read the locale specific error messages as below:
#Configuration
public class MessageConfig {
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasename("i18n/messages");
source.setUseCodeAsDefaultMessage(true);
return source;
}
}
I would like to suggest use of #ControllerAdvice and #ExceptionHandler.
Also you can use #RestControllerAdvice, find example here
Related
Could someone please tell me why this spring transaction is not rolling back appropriately?
The error I get is this:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available
This is my repository with a save transaction that will intentionally fail:
#Repository
public class TransactionalRepository {
private final PlayerRepository playerRepository;
#Autowired
public TransactionalRepository(PlayerRepository playerRepository) {
this.playerRepository = playerRepository;
}
public Player saveSuccess(Player player) {
return playerRepository.save(player);
}
#Transactional
public Player saveFail(Player player) {
player.setName("FAIL"); // should not be saved in DB if transaction rollback is successful
player = playerRepository.save(player);
throw new IllegalStateException("intentionally fail transaction");
}
}
And here is the test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MongoTransactionApplicationTests {
#Autowired
public TransactionalRepository playerRepository;
#Test
public void contextLoads() {
Player player = new Player();
player.setId(UUID.randomUUID().toString());
final String PLAYER_NAME = "new-"+player.getId().subSequence(0,8);
player.setName(PLAYER_NAME);
player = playerRepository.saveSuccess(player);
try {
player = playerRepository.saveFail(player);
} catch (IllegalStateException e) {
// this is supposed to fail
}
Assert.assertEquals(PLAYER_NAME, player.getName());
}
}
Download all the code here if you want to see it run
Unlike other implementations the Spring Data MongoDB module does not by default register a PlatformTransactionManager if none is present. This is up to the users configuration, to avoid errors with non MongoDB 4.x servers as well as projects already using #Transactional along with a non MongoDB specific transaction manager implementation. Please refer to the reference documentation for details.
Just add a MongoTransactionManager to your configuration.
#Bean
MongoTransactionManager txManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
You might also want to check out the Spring Data Examples and have a look at the one for MongoDB transactions.
I am trying to integrate Hystrix javanica into my existing java EJB web application and facing 2 issues with running it.
When I try to invoke following service it always returns response from fallback method and I see that the Throwable object in fallback method has "com.netflix.hystrix.exception.HystrixTimeoutException" exception.
Each time this service is triggered, HystrixCommad and fallback methods are called multiple times around 50 times.
Can anyone suggest me with any inputs? Am I missing any configuration?
I am including following libraries in my project.
project libraries
I have setup my aspect file as follows:
<aspectj>
<weaver options="-verbose -showWeaveInfo"></weaver>
<aspects>
<aspect name="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"/>
</aspects>
</aspectj>
Here is my config.properties file in META-INF/config.properties
hystrix.command.default.execution.timeout.enabled=false
Here is my rest service file
#Path("/hystrix")
public class HystrixService {
#GET
#Path("clusterName")
#Produces({ MediaType.APPLICATION_JSON })
public Response getClusterName(#QueryParam("id") int id) {
ClusterCmdBean clusterCmdBean = new ClusterCmdBean();
String result = clusterCmdBean.getClusterNameForId(id);
return Response.ok(result).build();
}
}
Here is my bean class
public class ClusterCmdBean {
#HystrixCommand(groupKey = "ClusterCmdBeanGroup", commandKey = "getClusterNameForId", fallbackMethod = "defaultClusterName")
public String getClusterNameForId(int id) {
if (id > 0) {
return "cluster"+id;
} else {
throw new RuntimeException("command failed");
}
}
public String defaultClusterName(int id, Throwable e) {
return "No cluster - returned from fallback:" + e.getMessage();
}
}
Thanks for the help.
If you want to ensure you are setting the property, you can do that explicitly in the circuit annotation itself:
#HystrixCommand(commandProperties = {
#HystrixProperty(name = "execution.timeout.enabled", value = "false")
})
I would only recommend this for debugging purposes though.
Something that jumps out to me is that Javanica uses AspectJ AOP, which I have never seen work with new MyBean() before. I've always have to use #Autowired with Spring or similar to allow proxying. This could well just be something that is new to me though.
If you set a breakpoint inside the getClusterNameForId can you see in the stack trace that its being called via reflection (which it should be AFAIK)?
Note you can remove commandKey as this will default to the method name. Personally I would also remove groupKey and let it default to the class name.
I have an issue finding the exception cause in FallBackFactory, mine is a old application, so i can not use spring cloud approach (with annotations etc..)
I found the below solution, but still not working for me:
Issue in getting cause in HystrixFeign client fallback
Here is the code i have:
public static class ProfileFallbackFactory implements ProfileProxy, FallbackFactory<ProfileFallbackFactory> {
final Throwable cause;
public ProfileFallbackFactory() {
this(null);
}
ProfileFallbackFactory(Throwable cause) {
this.cause = cause;
}
#Override
public ProfileFallbackFactory create(Throwable cause) {
LOG.info("Profile fallback create "+cause);
return new ProfileFallbackFactory(cause);
}
public Profile getProfile(String id) {
}
instance creation:
profileProxy = new HystrixFeign.Builder().setterFactory(new CustomSetterFactory())
.decode404()
.decoder(new GsonDecoder(gsonWithDateFormat))
.encoder(new GsonEncoder(gsonWithDateFormat))
.errorDecoder(new profileProxyErrorDecoder())
.target(ProfileProxy.class,profileServiceUrl, (FallbackFactory<ProfileFallbackFactory>)new ProfileFallbackFactory());
There is a logger added in ProfileProxyErrorDecoder class, but this logger is not found in logs. I can see com.netflix.hystrix.exception.HystrixRuntimeException in server logs
Can someone please point me where i am going wrong?
I found that I was unable to return collections from my JAX-WS Web Service.
I appreciate that the Java Collections API may not be supported by all clients, so I switched to return an array, but I can't seem to do this either.
I've set up my web service as follows:
#WebService
public class MyClass {
public ReturnClass[] getArrayOfStuff() {
// extremely complex business logic... or not
return new ReturnClass[] {new ReturnClass(), new ReturnClass()};
}
}
And the ReturnClass is just a POJO. I created another method that returns a single instance, and that works. It just seems to be a problem when I use collections/arrays.
When I deploy the service, I get the following exception when I use it:
javax.xml.bind.MarshalException - with linked exception:
[javax.xml.bind.JAXBException: [LReturnClass; is not known to this context]
Do I need to annotate the ReturnClass class somehow to make JAX-WS aware of it?
Or have I done something else wrong?
I am unsure of wheter this is the correct way to do it, but in one case where I wanted to return a collection I wrapped the collection inside another class:
#WebService
public class MyClass {
public CollectionOfStuff getArrayOfStuff() {
return new CollectionOfStuff(new ReturnClass(), new ReturnClass());
}
}
And then:
public class CollectionOfStuff {
// Stuff here
private List<ReturnClass> = new ArrayList<ReturnClass>();
public CollectionOfStuff(ReturnClass... args) {
// ...
}
}
Disclaimer: I don't have the actual code in front of me, so I guess my example lacks some annotations or the like, but that's the gist of it.
I am looking for the best way to do exception handling, for example.. when an error occurs in the business logic layer, is the best way to stop the METHOD using a catch and return an EVENT to the presentation layer?
What should this event contain?
Or should i always BUBBLE up exceptions and handle them in the presentation layer?
Anyone have some good links and required reading on this with regards to the best way of handling exceptions and how to handle them in the client ...
For example if i get a NullException in the BLL than i can catch this.. but whats the best way and return to the presentaiton layer and informing it of the issue..
Event? or another try / Catch in the presentation?
You can do several things;
Focus on improving the user experience when an unexpected error presents itself.
Always log errors either in eventlog or database.
Implement sufficient infrastructure to not let exceptions happen unless they are system exceptions.
Use throw instread of throw exception
Some links to help you:
http://today.java.net/pub/a/today/2003/12/04/exceptions.html
http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html
http://www.codeproject.com/KB/architecture/exceptionbestpractices.aspx
There are several ways to do it:
1) Throwing exceptions with describing message inside.
2) Firing events
3) Using special interfaces to interact with the user.For example you can implement something like IUiCallbacks interface and send the object, implementing this interface, to the BLL class or method. Later, method in BLL can call IUiCallbacks.SendMessage() or IUiCallbacks.SendError() to notify presentation. And you can have different classes, such as WinFormsUiCallbacks, WebFormsUiCallbacks and SilentUiCallbacks, implementing this interface.
I usually use 1) and 3)
Example of 3) as requested:
public interface IUiCallbacks
{
void SendMessage(string message);
void SendException(string message, Exception ex);
}
public class WinFormsUiCallbacks : IUiCallbacks
{
public void SendMessage(string message)
{
MessageBox.Show(message);
}
public void SendException(string message, Exception ex)
{
MessageBox.Show(string.Format("Unfortunately, the following errror has occurred:{0}{1}", Environment.NewLine, ex.Message));
}
}
public class OrderService
{
private IUiCallbacks _iUiCallbacks;
...
public OrderService() { ... }
public OrderService(IUiCallbacks iUiCallbacks)
{
_iUiCallbacks = iUiCallbacks;
}
...
public void AddOrder(Order order)
{
...
if(OrderAlreadyExists(order))
{
if(_iUiCallbacks != null)
_iUiCallbacks.SendMessage("The order can not be added, because it is already accepted.");
return;
}
...
}
...
}
So it can be used like this:
public partial class OrderForm : Form
{
...
public void btnAddOrderFromExcel_Click(...)
{
Order order = LoadOrderFromExcel(...);
OrderService orderService = new OrderService(new WinFormsUiCallbacks());
orderService.AddOrder(order);
}
...
}