How to Generify REST Controller in Spring MVC to Remove Duplication - spring

I have a design issue I cannot for the life of me figure out.
Good code has no duplication. I have generified my DAO so all basic crud operations are inherited
I'm trying to do the same with Spring MVC with Annotated Controllers.
I found this question but no answer is there: How to Remove Duplication from Spring 3 MVC Standard and Ajax Request Controllers and Views
If I have something like the below example, assuming I refactor to use AbstractBaseService and BaseModel (I did this but don't have the code), how can I put the annotation info into something like a GenericAbstractBaseController or BaseController interface? I've tried (don't have the code here) but the problem is that Annotations ARE NOT inherited and CANNOT be added at run time.
I see that javassist can be used for bytecode modification so that I actually CAN add annotations after compiling to keep the code clean but this appears to be overly complex.
I sent a note to a mentor and he suggested using AOP with naming convention to weave advice or reflection to identify the annotations from the inherited class but I'm not certain how I could actually give this info to spring given that I cannot actually add annotations at runtime. I think I'm missing some critical key here that someone will come and drop - he only sent me back a couple lines.
Here is the code - how can I remove the duplicate crud logic.
#Controller
#RequestMapping("/users")
public class UserController {
#Autowired
UserService userService;
#RequestMapping(value="/", method = RequestMethod.GET)
public #ResponseBody List<User> doGetIndex(ModelMap model) {
return userService.listPage(0, 10);
}
#RequestMapping(value="/{name}", method = RequestMethod.GET)
public #ResponseBody User doGet(#PathVariable String name, ModelMap model) {
return userService.getByUsername(name);
}
//post
//put
//delete

I don't quite understand what do you mean by "annotations are not inherited", but as far as I remember the following approach should work:
public class AbstractController<T> {
#RequestMapping(value="", method = RequestMethod.GET)
public #ResponseBody List<User> doGetIndex(ModelMap model) { ... }
#RequestMapping(value="{name}", method = RequestMethod.GET)
public #ResponseBody T doGet(#PathVariable String name, ModelMap model) { ... }
}
#Controller
#RequestMapping("/users")
public class UserController extends AbstractController<User> { ... }

Related

calling a method by class level annotated with #RequestMapping that includes an autowired class

I am trying to call a method that is annotated with #RequestMapping(signIn) through a class level (from method: authentication) like so:
#RequestMapping(value = /authenticate, method = RequestMethod.POST)
public #ResponseBody Response authentication(HttpServletRequest request)
{
UserController user = new UserController();
return user.signIn(request, null);
}
and my controller looks like:
#Autowired
private UserManager userManager;
#RequestMapping(value = /signin, method = RequestMethod.POST)
public #ResponseBody Response signIn(HttpServletRequest request) {
JsonObject json = Misc.parseJson(request);
String lang = Misc.getLang(request);
user.setEmail(Misc.getEmail(json));
user.setPassword(Misc.getEncryptedPassword(json));
return ResponseUtils.success(userManager.auth(user, lang));
}
user manager is annotated with #component:
#Component
public class UserManager {
public User auth(User user, String lang) {
....
return user;
}
}
Problem is when I call the method "signIn" and just new-up a UserController instance through "/authenticate" mapping, the UserManager becomes NULL. So now I'm assuming that autowiring doesn't work when it's done this way.
Is there any other way to call the signIn method? I would hate to copy paste an already existing code to another class just to get this to work...
Autowiering only works in spring managed bean. If you create a class with new keyword, it is not a spring managed bean and autowiering would not work.
You can try to autowire the class which contains the method which is annotated or better put the code in a service class which can be used by both methods.
It's not problem with #Autowired .There are two type of Annotation
firstly method base annotation and field level annotation. You just used field level annotation.Check your import class with "org.springframework.beans.factory.annotation.Autowired" or it can be problem with initiation of "UserManager"
I don't know why you not moving logic into separate Service classs, but try this:
UserController.java
public UserController(UserManager userManager) {
this.userManager = userManager;
}
and then inside controller where authentication resource method is located:
#Autowired UserManager userManager;
#RequestMapping(value = /authenticate, method = RequestMethod.POST)
public #ResponseBody Response authentication(HttpServletRequest request) {
UserController user = new UserController(userManager);
return user.signIn(request);
}
So in the end I just separated the logic instead. Though one solution that I tried and I could have used was to just add another mapping to the signIn method instead of adding a new method in the other class since the logic was similar. Still I opted for a separate logic instead since there were a lot of unnecessary code in the signIn method for my purpose.

Spring mvc - #sessionattributes vs #Scope("session") beans which to use?

I'm not fully understanding when to use #SessionAttributes vs #Scope("session") beans.
Currently, I'm doing the following
#ControllerAdvice(assignableTypes = {DashboardController.class, FindingWholeSalersController.class})
public class AuthControllerAdvice {
private IFindWholeSalerService service;
public IFindWholeSalerService getService() {
return service;
}
#Autowired
public void setService(IFindWholeSalerService service) {
this.service = service;
}
//put firstname in session etc..
#ModelAttribute
public void addWholesalerDiscoveryCountToSession(Model model, Principal principal){
if (!model.containsAttribute("firstname")) {
String firstname = service
.findUserFirstName(principal.getName());
model.addAttribute("firstname",
firstname);
}
}
Notice this if test if (!model.containsAttribute("firstname"))
Basically, if the session attribute is already in the model, then I dont want to ask my service layer to make a database request. However, every #RequestMapping call in any of the controllers I'm advising, first makes a call to
#ModelAttribute
public void addWholesalerDiscoveryCountToSession(Model model, Principal principal)
Does the if test, and moves on its marry way.
Is this the right solution for keeping data in the session so you dont have to call your database, OR would #Scope("session") beans be a better choice OR something else?
Thanks for all advice in advance!

How do I configure a controller bean?

If we have a look at the Mkyong tutorial for a simple Hello World MVC application.
Now lets say instead of returning a straight hello, the controller instead calls a business logic layer to get what it wants.
#Controller
#RequestMapping("/welcome")
public class HelloController {
private BusinessLogic myBusinessLogic;
#RequestMapping(method = RequestMethod.GET)
public String printWelcome(ModelMap model) {
String text = myBusinessLogic.getMessage();
model.addAttribute("message", text);
return "hello";
}
}
I have two questions here.
How do I instantiate myBusinessLogic? In the exampke mkyong has given, there's no bean configuring the HelloController.
How do I configure the bean's scope?

Where should we use #Transactional and where is Service layer?

I have rest style controller in Spring. In controller I have injected dao interfaces. From controller I persist data. In the other words, I have like REST web service. people sends me data, and I persits it.
/**
* Payment rest controller which receives
* JSON of data
*/
#Controller
#RequestMapping("/data")
public class PaymentTransaction {
#Autowired
private TestDao dao;
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody()
public String test(HttpServletRequest request) {
...
}
At the moment I have #transaction annotation in Dao classes. For instance:
import org.springframework.transaction.annotation.Transactional;
#Component
#Transactional
public interface TestDao {
#Transactional(propagation = Propagation.REQUIRED)
public void first();
}
I have read that this is very bad style. Using this answer at stackoverflow , here is explain and examples why is this bad - we must not add this annotation in DAO and in controller too. We must add it in service layer.
But I don't understand what is the service layer? Or where is it? I do not have anything like this.
where should I write #Transactional annotation?
Best regards,
According to the cited post, you should design your classes somehow like this (rather pseudocode):
controller (responsible for handling clients' requests/responses)
#Controller
#RequestMapping("/data")
public class TestREST {
#Autowired
private TestService service;
public void storePayment(PaymentDTO dto) {
service.storePayment(dto); //request from a client
}
public PaymentDTO getPayment(int paymentId) {
return service.getPayment(paymentId); //response to a client
}
}
service layer (also called business layer, responsible for business logic - knows what to do with incoming messages, but does not know where they come from).
public class TestServiceImpl {
#Autowired
private TestDao dao;
#Transactional(propagation=Propagation.REQUIRED) //force transaction
public void storePayment(PaymentDTO paymentDto) {
// transform dto -> entity
dao.storePayment(paymentEntity); //read-write hence transaction is on
}
#Transactional(propagation=Propagation.NOT_SUPPORTED) //avoid transaction
public Payment getPayment(int paymentId) {
return dao.findPayment(paymentId); //read-only hence no transaction
}
}
data access layer (also called persistence layer, responsible for accessing database - knows how to use entity model / ORM, does not know anything about the upper service layer)
public class TestDAOImpl {
#PersistenceContext
private EntityManager em;
public void storePayment(PaymentEntity paymentEntity) {
em.persist(paymentEntity);
}
public PaymentEntity getPayment(int paymentId) {
return em.find(PaymentEntity.class, paymentId);
}
}
By this approach you get separation of concerns mentioned in the post. From the other hand such an approach (business layer vs data access layer) got a little dose of criticism from Adam Bien's on his blog ("JPA/EJB3 killed the DAO"). As you can see there is no a single solution for the problem, but I encourage to read some other opinions and apply the solution you find the most suitable for your needs.
When you call two Dao methods first & second from controller, 2 transactions will be done, one with starts before first method and ends after it's execution and the second one starts before second method starts and ends after it's execution. Whereas you create an additional class in between controller and dao (usually this is called service layer) and annotate it with #Transactional and call multiple Dao methods in it, a transaction is started at the start of service method and all the dao calls will be executed and transaction will be closed, which is what you require. And inject the Service into Controller.
Controller -> Service -> Dao
#Controller
#RequestMapping("/data")
public class PaymentTransaction {
#Autowired
private TestService service;
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody()
public String test(HttpServletRequest request) {
...
}
}
#Service
#Transactional
public class TestService {
#Autowired
private TestDao dao;
#Transactional
public void serviceCall(){
dao.first();
dao.second();
}
}

#Autowired for #ModelAttribute

I'm very new to Spring and I'm encountering the following problem.
I've got the following Controller, in which the #Autowired works perfectly (tried debugging and it works fine).
#Controller
#RequestMapping(value = "/registration")
#SessionAttributes("rf")
public class RegistrationController
{
#Autowired
UserJpaDao userDao;
#RequestMapping(method = RequestMethod.GET)
#Transactional
public String setupForm(Model model) throws Exception
{
model.addAttribute("rf", new RegistrationForm());
return "registration";
}
#RequestMapping(method = RequestMethod.POST)
#Transactional
public String submitForm(#ModelAttribute("rf") RegistrationForm rf, Model model) throws Exception
{
// ...
User user = rf.getUser();
userDao.save(user);
// ...
return "registration";
}
}
But when I submit my form, the #Autowired field in my RegistrationForm remains null.
RegistrationForm.java:
#Component
public class RegistrationForm
{
#Autowired
CountryJpaDao countryDao;
// ... fields...
public RegistrationForm()
{
}
#Transactional
public User getUser() throws InvalidUserDataException
{
//...
Country c = countryDao.findByCode("GB"); // Throws java.lang.NullPointerException
// ...
}
// ... getters/setters...
}
Here is the form's HTML/JSTL:
<form:form method="POST" modelAttribute="rf">
...
</form:form>
Can anyone help me?
Thank you.
(inspired by this post on SpringSource forums)
You're mixing up your concepts here. You use the likes of #Component and #Autowired for Spring-managed beans, and #ModelAttribute for transient, throwaway objects that are used to bind form data. The two should not be mixed. Your #Component and #Autowired annotations on RegistrationForm will be ignored by Spring, because they're not appropriate in that context.
Classes like RegistrationForm should represent the form data, and nothing else. Typically, the controller would ask RegistrationForm for the user ID, and would then look at the actual User object from the DAO itself. If you want RegistrationForm to look up the User itself, then your controller needs to manually supply the DAO to RegistrationForm when it asks for the User object.
As far as that post on the Spring forum is concerned, you'll notice that it never received an answer. It's not a good source to take inspiration from.
Note that I'm not saying that desiring to autowire beans into a form back object is a bad idea, I'm just saying that Spring doesn't do that.
It would work if you use the #Configurable annotation on your model, and this aspectJ configuration on your gradle file:
compileJava << {
ant.taskdef(
resource: 'org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties',
classpath: configurations.compile.asPath)
ant.iajc(
inpath: sourceSets.main.output.classesDir.absolutePath,
classpath: configurations.compile.asPath,
aspectPath: configurations.aspects.asPath,
destDir: sourceSets.main.output.classesDir.absolutePath
)
}
In this way aspectJ will generate code that does the auto wiring.
#Configurable
public class RegistrationForm
{
...
}

Resources