I am not sure of using inheritance / interface implementation in particular situation.
In my simple Spring MVC application I have #Entity class TennisPlayer, which is inherited from abstract class Player (TennisPlayer adds some attributes).
Also I have class TennisPlayerForm, which is inherited from abstract class PlayerForm (TennisPlayerForm adds some attributes again).
User fills the form about tennis player in .jsp page and TennisPlayerForm object is used to represent filled values and then on the basis of this object is created TennisPlayer object and saved into database.
Creation of TennisPlayer object is responsibility of class TennisPlayerDbService. This class is implementation of interface PlayerService.
I have following #Controller, which handles requests:
#Controller
public class NewPlayerController {
#Resource(name="tennisPlayerService")
private PlayerService playerService;
//omitted RequestMethod.GET handler method
#RequestMapping(value = "/newplayer", method = RequestMethod.POST)
public String newplayer(Locale locale, #ModelAttribute("tennisPlayerForm") #Valid TennisPlayerForm tennisPlayerForm,
BindingResult result, RedirectAttributes redirectAttributes) {
playerService.createPlayer(tennisPlayerForm);
return "redirect:/allplayers";
}
}
Part of my source code looks like this:
public interface PlayerService {
public void createPlayer(PlayerForm playerForm);
}
#Service(value="tennisPlayerService")
public class TennisPlayerDbService implements PlayerService {
private TennisPlayerDAO dao;
#Autowired
public void setDao(TennisPlayerDAO dao) {
this.dao = dao;
}
#Override
public void createPlayer(PlayerForm playerForm) {
TennisPlayerForm tennisPlayerForm = null;
if (playerForm instanceof TennisPlayerForm) {
tennisPlayerForm = (TennisPlayerForm) playerForm;
}
else {
throw new IllegalArgumentException("Must be of type TennisPlayerForm.");
}
TennisPlayer player = new TennisPlayer();
player.setName(tennisPlayerForm.getName());
player.setSurname(tennisPlayerForm.getSurname());
player.setAge(tennisPlayerForm.getAge());
player.setRacket(tennisPlayerForm.getRacket());
player.setRanking(tennisPlayerForm.getRanking());
player.setSponsor(tennisPlayerForm.getSponsor());
player.setCoach(tennisPlayerForm.getCoach());
player.setClub(tennisPlayerForm.getClub());
dao.saveAndFlush(player);
}
}
Is it justified to use inheritance and interface implementations like this in this situation, when concrete implementation of PlayerService (TennisPlayerDbService) expects instance of particular class, although these potential classes have common parent?
Finally I solved my problem according to your comments and answers.
I deleted PlayerForm abstract class, TennisPlayerForm and mixed javax.validation and javax.persistence annotations in #Entity classes Player and Tennis Player.
Previously mentioned code now looks like this:
#Controller
public class NewPlayerController {
#Resource(name="tennisPlayerService")
private PlayerService<TennisPlayer> playerService;
//omitted RequestMethod.GET handler method
#RequestMapping(value = "/newplayer", method = RequestMethod.POST)
public String newplayer(Locale locale, #ModelAttribute("tennisPlayer") #Valid TennisPlayer tennisPlayer,
BindingResult result, RedirectAttributes redirectAttributes) {
if(result.hasErrors()) {
return "newplayer";
}
playerService.createPlayer(tennisPlayer);
MessageUtil.flash(locale, redirectAttributes, "success", "signup.success");
return "redirect:/allplayers";
}
}
public interface PlayerService<T extends Player> {
public void createPlayer(T player);
public List<T> getAllPlayers();
}
#Service(value="tennisPlayerService")
public class TennisPlayerDbService implements PlayerService<TennisPlayer> {
private TennisPlayerDAO dao;
#Autowired
public void setDao(TennisPlayerDAO dao) {
this.dao = dao;
}
#Override
public void createPlayer(TennisPlayer player) {
dao.saveAndFlush(player);
}
#Override
public List<TennisPlayer> getAllPlayers() {
return dao.findAll();
}
}
Normally your service does not need to know you are working with a form. Your form is purely created to be the model in the model-view-controller architecture of your webpage. (your jsp being the view and your controller being the c-part)
Are you also planning on using other types of players than a TennisPlayer? If not it all seems like premature optimisation and you should keep it as simple as possible.
Related
I want Spring to create 2 instances of FooController. Requests to /foo should be handled by one of the instances and requests to /bar should be handled by the other instance. I want something like the below, but of course #RequestMapping doesn't work that way and also Spring gives me the ambiguous mapping error on FooController as well.
#RestController
public class FooController {
String name;
public FooController(String name) { this.name = name; }
}
#Configuration
public class FooControllerConfig {
#Bean
#RequestMapping("/foo")
public FooController getFooFooController(){
return new FooController("foo");
}
#Bean
#RequestMapping("/bar")
public FooController getBarFooController(){
return new FooController("bar");
}
}
I'm really confused by why you need this requirement? Can you please explain why this is required? Is it that each mapping requires a different name?
First you do not map Beans to a RequestMapping. While I am not even sure the spring application would start it would potentially create a new Bean with an identical name every time you access one of these mappings which would probably throw an error.
You could potentially overcome the duplicate names with your own annotation processing but that is way more work then this looks like it is worth.
Just looking at what you have there is there any reason why the following will not meet your requirements?
#RestController
public class FooController {
private static final fooName = "fooName";
private static final barName = "barName";
#RequestMapping("/foo")
public String getFoo(){
return fooName;
}
#RequestMapping("/bar")
public String getBar(){
return barName;
}
}
Don't try this at home. This code was performed by a bored, trained professional...
You can have multiple instances of the same controller class, each of which handles a different URL through the same or a different method in the controller. The only thing is, I don't know how to do it with just annotations. The way I just did it was to dynamically register each request mapping at initialization time. The FooController becomes a prototype bean (defined with annotations) so you can have Spring instantiate it multiple times, once for each mapping
FooController.java
#Controller
#Scope("prototype")
public class FooController {
private String name;
public FooController() {}
public FooController(String name) {
this.name = name;
}
public ResponseEntity<String> handleRequests() throws Exception {
return new ResponseEntity<>("Yo: " + name + " " + this.hashCode(), HttpStatus.OK);
}
EndpointService.java
#Service
public class EndpointService {
#Autowired
private BeanFactory beanFactory;
#Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
public void addFooController(String urlPath, String name) throws NoSuchMethodException {
RequestMappingInfo requestMappingInfo = RequestMappingInfo
.paths(urlPath)
.methods(RequestMethod.GET)
.produces(MediaType.APPLICATION_JSON_VALUE)
.build();
requestMappingHandlerMapping.registerMapping(requestMappingInfo,
beanFactory.getBean(FooController.class, name),
FooController.class.getDeclaredMethod("handleRequests"));
}
#EventListener
public void handleContextRefreshEvent(ContextRefreshedEvent ctxStartEvt) {
try {
addFooController("/blah1", "blahblah1");
addFooController("/blah2", "blahblah2");
addFooController("/blah3", "blahblah3");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
Results:
http://localhost:8080/blah1 returns: Yo: blahblah1 1391627345
http://localhost:8080/blah3 returns: Yo: blahblah3 2078995154
This is more about Java than Dropwizard; but I have two resources in Dropwizard: CustomerResource and ApiResource.
In CustomerResource there is a createCustomer method which basically creates a new customer. The ApiResource will also create a new customer when a third party invokes a method inside of it, so this got me thinking about duplicate code and the best way to resolve it. I have a few approaches in mind; but first here are the classes for better clarity.
#Path("/internal")
public class CustomerResource{
private DBDao dbDao;
private AnotherAPI api;
//constructor for DI
public Response Create(#internalAuth CustomerPojo customerPojo) {
//logic to validate customerpojo
//logic to ensure user isn't a duplicate
//some other validation logic
//finally user creation/saving to DB
Return response.ok(200).build();
}
}
#Path("/external")
public class ApiResource{
private DBDao dbDao;
private AnotherAPI api;
//constructor for DI
public Response Create(#ExternalAuth PartialCustomerPojo partialCustomerPojo) {
//logic to validate PartialCustomerpojo
//supplement partialCustomerPojo
//logic to ensure user isn't a duplicate
//some other validation logic
//finally user creation/saving to DB
Return response.ok(200).build();
}
}
So two main differences are how the endpoint is called (authentication) and the payload provided.
The way I thought about removing duplicate code is to create a new concrete class that takes commonality from both resources and each of them instantiates a new class like this.
public class CommonClass{
private DBDao dbDao;
private AnotherAPI api;
//constructor for DI
public boolean Create (CommonPojo commonPojo) {
//logic to validate customerPojo
//logic to ensure user isn't a duplicate
//some other validation logic
//finally user creation/saving to DB
Return response.ok(200).build();
}
}
And now inside CustomerResource and ApiResource I simply do this.
CommonClass commonClass = new CommonClass(dbDao, api);
//create a new instance customerPojo or CommonPojo and call
commonClass.create(customerPojo);
Does this sound like a good strategy? Are there other concerns beyond duplication? These two resource methods can't be inside the same class either. Any best practice will be appreciated.
I think inheritance not the best solution.
Also I think that composition is much better. This can help you to use common code and easy to change it in other places where you need to change functionality.
Also it allows you to test all classes more easy.
For example:
class CommonPojo {}
class CustomerPojo extends CommonPojo {}
class PartialCustomerPojo extends CommonPojo {}
interface IResourceValid {
boolean isResourceValid(CommonPojo pojo);
}
class CustomerPojoValidator implements IResourceValid {
#Override
public boolean isResourceValid(CommonPojo pojo) {
//your validation for customer
return false;
}
}
class PartialCustomerPojoValidator implements IResourceValid {
#Override
public boolean isResourceValid(CommonPojo pojo) {
//your validation for partial customer
return true;
}
}
class CommonResource{
private DBDao dbDao;
private AnotherAPI api;
private IResourceValid validator;
public IResourceValid getValidator() {
return validator;
}
//constructor for DI
public Response Create(CommonPojo commonPojo) {
//logic to validate customerpojo
//logic to ensure user isn't a duplicate
//some other validation logic
//finally user creation/saving to DB
validator.isResourceValid(commonPojo);
return response.ok(200).build();
}
}
//#Path("/internal")
class CustomerResource{
private CommonResource resource;
//constructor for DI
public Response Create(CustomerPojo CustomerPojo) {
return resource.Create(CustomerPojo);
}
}
//#Path("/external")
class ApiResource{
private CommonResource resource;
//constructor for DI
public Response Create(PartialCustomerPojo partialCustomerPojo) {
return resource.Create(partialCustomerPojo);
}
}
DBDao dao = new DBDao();
AnotherAPI api = new AnotherAPI();
CommonResource castomerCreator = new CommonResource(new CustomerPojoValidator(), dao, api);
CommonResource apiCreator = new CommonResource(new PartialCustomerPojoValidator(), dao, api);
CustomerResource customerResource = new CustomerResource(castomerCreator);
ApiResource apiResource = new ApiResource(apiCreator);
customerResource.Create(somePojo);
apiResource.Create(someAnotherPojo);
There are many options and it all depends on what strategy you use. I prefer to use abstract class and make non abstract methods in it. When you extend the abstract class you choose which method you want to use.
In your scenario it should look something like this:
public abstract class AbstractResource {
private CustomerService customerService;
#Autowired
public void setCustomerService(CustomerService customerService) {
this.customerService = customerService;
}
public Response create(CustomerPojo customer) {
return customerService.createCustomerPojo(customer);
}
public Response create(PartialCustomerPojo customer) {
return customerService.createPartialCustomerPojo(customer);
}
}
The CustomerResource #Override only the method that needs:
#Path("/internal")
#Component
public class CustomerResource extends AbstractResource {
#POST
#Override
public Response create(PartialCustomerPojo customer) {
return super.create(customer);
}
}
Same for the ApiResource
#Path("/external")
#Component
public class ApiResource extends AbstractResource {
#POST
#Override
public Response create(CustomerPojo customer) {
return super.create(customer);
}
}
Everything goes in onle place - CustomerService, where you do your logic.
#Service
public class CustomerService {
#Autowired
private AnotherAPI api;
#Autowired
private DBDao dao;
public Response createCustomerPojo(CustomerPojo customer) {
//logic to validate customerpojo
//logic to ensure user isn't a duplicate
//some other validation logic
//finally user creation/saving to DB
}
public Response createPartialCustomerPojo(PartialCustomerPojo customer) {
//logic to validate PartialCustomerpojo
//supplement partialCustomerPojo
//logic to ensure user isn't a duplicate
//some other validation logic
//finally user creation/saving to DB
}
}
If you want to minimize the duplication you have to use an interface and implement in in each class:
public class CustomerPojo implements PojoInterface {
}
public class PartialCustomerPojo implements PojoInterface {
}
Now you can have only one abstract method:
public abstract class AbstractResource {
private CustomerService customerService;
#Autowired
public void setCustomerService(CustomerService customerService) {
this.customerService = customerService;
}
public abstract Response create(PojoInterface customer);
}
And then in 'the one place' you need to check each instance of the parameters:
public Response create(PojoInterface customer) {
if (customer instanceof CustomerPojo){
//logic to validate customerpojo
//logic to ensure user isn't a duplicate
//some other validation logic
//finally user creation/saving to DB
}else if (customer instanceof PartialCustomerPojo){
//logic to validate PartialCustomerpojo
//supplement partialCustomerPojo
//logic to ensure user isn't a duplicate
//some other validation logic
//finally user creation/saving to DB
}
}
Edit: Sorry for the long post...
This can be achieved by both of the following ways
Create an Interface and then implement on both of the target classes so they both can provide their local implementation
Use abstract class and then extend it.
Interface does not save you from coding but keeps things inline where abstract classes do not enforce anything so both have weaknesses. You can implement multiple interfaces but can only extend one class. Keeping that in mind I lean towards interfaces more
Since Java 8. Java supports default method using interface, which is from my opinion is the best way to go. You can provide default implementation in default method and user can override the method if they wish to. This will give you best of both Interface and Abstract class.
public interface CommonCode {
default public boolean create(CommonPojo commonPojo) {
//logic to validate customerPojo
//logic to ensure user isn't a duplicate
//some other validation logic
//finally user creation/saving to DB
Return response.ok(200).build();
}
}
#Path("/internal")
public class CustomerResource implements CommonCode {
private DBDao dbDao;
private AnotherAPI api;
//constructor for DI
create(CommonPojo)
}
#Path("/external")
public class ApiResource implements CommonCode {
private DBDao dbDao;
private AnotherAPI api;
//constructor for DI
create(CommonPojo)
}
Make an interface and have both classes implement it. Add an abstract base class call between the interface and the subclasses and refactor any common code into it.
This is my controller. It accepts a multipart/form-data request with two fields, form and file. The form field is a
MyObject, the file field is a MultipartFile. Both variables are annotated with #Valid, and accordingly, I would expect Spring to invoke the Validator class of each respective field. However, this only happens with MyObject, and not with MultipartFile.
#RequestMapping("/api")
#RestController
public class Controller {
private MyObjectRepository repo;
private MyObjectValidator myObjectValidator;
private FileValidator fileValidator;
#Autowired
public myObjectController(MyObjectRepository repo, MyObjectValidator myObjectValidator,
FileValidator fileValidator) {
this.repo = repo;
this.myObjectValidator = myObjectValidator;
this.fileValidator = fileValidator;
}
#InitBinder("form")
public void initMyObjectBinder(WebDataBinder binder) {
binder.setValidator(this.myObjectValidator);
}
#InitBinder("file")
public void initFileBinder(WebDataBinder binder) {
binder.setValidator(this.fileValidator);
}
#PostMapping("myObject")
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public MyObject createMyObject(#RequestPart("form") #Valid MyObject myObject,
#RequestPart("file") #Valid MultipartFile... file) {
return repo.save(myObject);
}
}
My MyObjectValidator is triggered, but my FileValidator is not triggered. Both classes implement the Spring Validator interface. MyObjectValidator.supports(Class<?> aClass) is called, whereas FileValidator.supports(Class<?> aClass) is never called. Apart from that, my Controller is functioning perfectly, and diligently saves objects to my repo.
What could be the issue here? I've read similar questions, and common mistakes are to not use an appropriate argument inside the #InitBinder annotation, or to set the #InitBinder methods to private instead of public, but neither of this applies to my case.
This ugly workaround does what it's supposed to, but it is un-Spring-like. I call my FileValidator manually inside the Controller.createMyObject method, instead of letting Spring call it automatically through the #Valid annotation.
#PostMapping("myObject")
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public MyObject createMyObject(#RequestPart("form") #Valid MyObject myObject,
#RequestPart("file") #Valid MultipartFile... file) {
if (fileValidator.supports(file.getClass())) {
Errors errors = new BeanPropertyBindingResult(file, "Uploaded file.");
fileValidator.validate(file,errors);
if (errors.hasErrors()) {
throw new BadRequestException();
}
}
return repo.save(myObject);
}
EDIT: I have included my Validator classes on request.
import org.springframework.validation.Validator;
public abstract class AbstractValidator implements Validator {
// One shared method here.
}
public class FileValidator extends AbstractValidator {
public boolean supports(Class<?> aClass) { // This method is never triggered.
boolean isSingleFile = MultipartFile.class.isAssignableFrom(aClass); // This line has a breakpoint, it is never triggered in the debugger.
boolean isFileArray = aClass.equals(MultipartFile[].class);
return (isSingleFile || isFileArray);
}
public void validate(Object o, Errors e) {
//Several validation methods go here.
}
public class MyObjectValidator extends AbstractValidator {
public boolean supports(Class<?> aClass) { // This method is triggered.
return (MyObject.class.equals(aClass)); // This line has a breakpoint, and it is always triggered in the debugger.
}
public void validate(Object o, Errors e) {
// Several validation methods go here.
}
EDIT: I made some changes to my code like NiVeR suggested, removing the varargs parameter and changing my FileValidator.supports(Class<?> aClass) accordingly, but the behavior is still the same.
In Controller.java:
#PostMapping("myObject")
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public MyObject createMyObject(#RequestPart("form") #Valid MyObject myObject, #RequestPart("file") #Valid MultipartFile file) {
return repo.save(myObject);
}
In FileValidator.java:
public boolean supports(Class<?> aClass) {
return MultipartFile.class.isAssignableFrom(aClass);
}
I believe that the problem is related to the variadic Multipart... parameter. In the supports method of the validator you are checking for array of Multipart but I suspect that's not the correct way. Just as a trial, I would make the Multipart a single object parameter (and change the validator accordingly) to test if it works in this way.
#Magnus I think you have to add annotation in all the Validator Class
eg.
#Component
public class MyObjectValidator extends AbstractValidator {
public boolean supports(Class<?> aClass) { //This method is triggered.
return (MyObject.class.equals(aClass));
}
public void validate(Object o, Errors e) {
//Several validation methods goes here.
}
}
This is the main controller for the web entrypoint
#Controller
#RequestMapping("/webapp")
public class WebAppController {
#RequestMapping(value = "/home/{authKey}",method = RequestMethod.GET)
String index(#ModelAttribute MyMeta myMeta, Model model){
System.out.println("Token: "+myMeta.getAccessToken());
return "index";
}
#RequestMapping(value = "/config/{authKey}",method = RequestMethod.GET)
String config(#ModelAttribute MyMeta myMeta, Model model){
return "configure";
}
}
Now if you look at the interceptor you can see how I am creating the #ModelAttribute, and see the implementation
#Component
#ControllerAdvice
public class SessionInterceptor implements AsyncHandlerInterceptor {
MyMeta myMeta;
...
#ModelAttribute
public MyMeta getTest() {
return this.myMeta;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
...
// parse the key from the request
...
MetaMagicKey metaMagicKey = metaMagicKeyRepo.findKeyByMagicKey(key);
// do work here query my DB and build stuff
...
// assign the queried data built into object
this.myMeta = metaMagicKey.getId().getMyMeta();
return true;
}
My question is, I do not know the true inter-workings of Springboot so I am worried if too many people execute this I might have some object swapping, or some kind of collision? There really isn't a clean way to do this and all of the research I've done is torn between using HttpServletRequest#setAttribute() and using #ModelAttribute, I like the route I chose above as it's VERY easy to implement in my methods.
Springboot 1.4.2 - Java 8
EDIT:
What I ended up trying is this, based on several pages I've read.
I created a new component:
#Component
#RequestScope
public class HWRequest implements Serializable {
private MyMeta myMeta;
public MyMeta getMyMeta() {
return myMeta;
}
public void setMyMeta(MyMeta myMeta) {
this.myMeta = myMeta;
}
}
And then My Config class
#Configuration
public class AppConfig extends WebMvcConfigurerAdapter {
UserSessionInterceptor userSessionInterceptor;
#Autowired
public AppConfig(UserSessionInterceptor userSessionInterceptor) {
this.userSessionInterceptor = userSessionInterceptor;
}
#Bean
#RequestScope
public HWRequest hwRequest() {
return new HWRequest();
}
#Bean
public UserSessionInterceptor createUserSessionInterceptor() {
return userSessionInterceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(createUserSessionInterceptor()).addPathPatterns("/user/**");
}
}
And here is the interceptor I modified
#Component
#ControllerAdvice
public class SessionInterceptor implements AsyncHandlerInterceptor {
#Resource
HWRequest hwRequest;
...
#ModelAttribute
public HWRequest getTest() {
return this.hwRequest;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
...
// parse the key from the request
...
MetaMagicKey metaMagicKey = metaMagicKeyRepo.findKeyByMagicKey(key);
// do work here query my DB and build stuff
...
// assign the queried data built into object
this.hwRequest.setMyMeta(metaMagicKey.getId().getMyMeta());
return true;
}
And of course the modified controller to fit my needs
#Controller
#RequestMapping("/user")
public class WebAppUserController {
#RequestMapping(value = "/home/{authKey}",method = RequestMethod.GET)
String index(#ModelAttribute HWRequest request, Model model){
return "index";
}
#RequestMapping(value = "/config/{authKey}",method = RequestMethod.GET)
String config(#ModelAttribute HWRequest request, Model model){
return "configure";
}
}
Based on all of the documentation I've read this should work, but maybe I am missing something as the interceptor is STILL a singleton. Maybe I am missing something?
myMeta variable represents state in singleton bean. Of course it is not thread-safe and various users will get collisions. Do not ever store any of your application state in singleton beans.
If you want to store some state per request, use Spring's request scope. That means creating separate bean just for storing state annotated with #RequestScope annotation
Reaction on EDIT:
This bean registration can be deleted as it is already registered into Spring IoC container with #Component annotation:
#Bean
#RequestScope
public HWRequest hwRequest() {
return new HWRequest();
}
Another piece that is not needed in your AppConfig is autowiring UserSessionInterceptor bean and registering it as bean again. Delete that. As that bean is being autowired it obviously already is in IoC container, so no need to register it again.
Another confusing piece is workd session in naming. As you are dealing with #RequestScope instead of #SessionScope I would advise to change naming of your class to request (e.g. RequestInterceptor). Session vs Request are very different beasts.
Otherwise it looks like it can work and should be thread safe.
I created #ControllerAdvice that has to set me some model attribute.
#ModelAttribute
public void globalAttributes(Model model) {
model.addAttribute("pageId", PAGE_ID);
}
This is a generic example of what I need, and PAGE_ID represents some variable that actual controller has to set. Since #ControllerAdvice is running before controller, how can I declare this variable and use it in Advice? If that's even possible.
I think a better solution would been using some kind of abstract-class-pattern for your controller
public abstract class AbstractController {
#ModelAttribute
public void globalAttributes(Model model) {
model.addAttribute("pageId", getPageId());
}
abstract String getPageId();
}
public class MyController extends AbstractController {
#Override
public String getPageId() {
return "MyPageID"
}
//..your controller methods
}