Spring AOP and Spring JPA, Aspect to be executed - java

I have been writing an aspect that manipulates some of my JPA entities getters. It is supposed to re-format the returned text based on the clients locale. Because not all of my getters should be reformatted I introduced an annotation #ReFormat.
The problem is my aspect is never intercepted when I advise it to an JPA entity but it works fine on non JPA entities (it works when I create my own entity object via a copy constructor).
My annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface ReFormat {
}
My aspect:
#Aspect
public class ReFormatAspect {
#AfterReturning(pointcut = "#annotation(com.path.ReFormat)", returning = "response")
public Object formatter(JoinPoint joinPoint, Object response) {
return response;
}
}
Now this aspect is intercepted successfully within my MVC controllers (or at any other place except spring data) but not for my entities.
#Entity
#Table(name = "place", schema = "db")
public class TestEntity {
#Id
#Column(name = "id")
protected long id;
#Column(name = "about", columnDefinition = "TEXT DEFAULT NULL")
protected String about;
#ReFormat
public String getAbout() {
return this.about;
}
}
I expected a point cut once the getAbout method is called, but it does not work.
Given the facts above I suppose that JPA (Hibernate) is overriding any interceptor may be by CGLib or javassist.
Note: I have this inside my context
<context:annotation-config />
<context:spring-configured />
<aop:aspectj-autoproxy proxy-target-class="true" />
So what is the exact issue, and how do I intercept any method inside an entity?
I understand this should be the view layer work, but still I need to know why :D

Your Entities are not managed by Spring, they are managed by the underlying JPA implementation. Because of this, Spring cannot wrap them in proxies and provide the wanted behavior.
There's no Spring way to solve this. Hibernate might have some interceptor tool for that (that wraps entities as they are created) but I don't know it. Maybe extending EmptyInterceptor.

You can always use AspectJ and wave your aspect in compile time. But this way you will not have an access to spring DI container.

It is possible to use AspectJ aspects together with the Spring DI container. The Spring AOP documentation sais:
Domain objects often fall into this category because they are often created programmatically using the new operator, or by an ORM tool as a result of a database query.
I created a GitHub project where I use your #ReFormat annotation on a method of a bean annotated with #Entity. In order to be able to use DI within your AspectJ aspect you need to use the aspectOf() approach. This is even possible with JavaConfig. So you don't have to use XML configuration at all.

Related

SpringBoot, JPA and Hibernate Unit-Test for Entities layer

I'm implementing a project using SpringBoot, JPA and Hibernate.
I implemented the DB entities layer with JPA repository.
I'm interested to understand the best practice to write unit-tests for this layer.
Point number one: for this layer, from your point of view, it's necessary to use an integrated DB or it' necessary to mock using, for example, Mockito?
My idea, for this layer, it's, for example, to test the entity structure: check fields validation for example, insert and retrieve some data. In this way, I think I could cover the tests for this entire data-layer.
I'm trying to understand these best practices and, in the mean time, I tried to write a first example of the test:
#ActiveProfiles("test")
#DisplayName("Test Item JPA Entity")
#DataJpaTest
#AutoConfigureTestDatabase( replace = AutoConfigureTestDatabase.Replace.NONE )
public class ItemEntityTest {
#Autowired
MyEntityRepository repo;
#Test
#Transactional
public void testEntityCreation() {
Entity e = new Entity();
e.setMyField1("A");
e.setMyField1("A");
//e.setMandatoryField("C")
repo.save(e);
}
}
Unfortunately, In this case, I notiest that the fields validation is not applied (#NotNull or #NotEmpty, or #Column(nullable=false), etc ... If I try to save the entity into my application the validation works fine... the exceptions are raised). Why?
Also some "automatic fields" (for example creation time and last modification time) are not filled.
Is this the correct path? Ho to test my entities definition?
As mentioned in that answer, the problem is that with #DataJpaTest spring will use TestEntityManager and the transactional annotation will override the default auto commit and rollback behaviour by spring boot.
So your test method will pass and assuming the hibernate is the ORM being used here, when the flushing will take place finally, hibernate's pre-insert event will fire and validations will be applied, but your test case will pass till then so it will produce false positive (as the terms used in spring docs)
Solution: You would need to inject entity manager in your test and flush manually it so that hibernate pre-insert event triggers before your test completes.
#ActiveProfiles("test")
#DisplayName("Test Item JPA Entity")
#DataJpaTest
#AutoConfigureTestDatabase( replace = AutoConfigureTestDatabase.Replace.NONE )
public class ItemEntityTest {
#Autowired
MyEntityRepository repo;
#Autowired
private TestEntityManager em;
#Test
#Transactional
public void testEntityCreation() {
Entity e = new Entity();
e.setMyField1("A");
e.setMyField1("A");
//e.setMandatoryField("C")
repo.save(e);
em.flush();
}
}
This must trigger your validations on the entity applied, this is documented in spring framework docs.
Please notice that the documentation is about spring framework and uses session factory, but the concept is same
You may check the spring boot docs as well, which points to the spring framework docs for this behaviour.

Pointcut or Aspect Around All Service Methods with Annotation #Transactional(readOnly = false)

Is it possible to use Spring AOP or AspectJ to intercept all Service methods (contained in classes in the com.app.service.* package) having the annotation
#Transactional(readOnly = false)
(other elements possible as well in Spring's #Transactional annotation, but we only care about readOnly = false).
I could only find examples pertaining to pointcuts with simple Annotations, or #Annotation(value).
My preference would be to use straight Spring, if possible.
It would probably be something like the below, but not sure about the syntax.
#Around("execution(* com.app.service..*.*(..))" && #Transactional[??])
You want to use a pointcut like this:
execution(#org.springframework.transaction.annotation.Transactional(readOnly = false) * com.app.service..*.*(..))
Unfortunately no easy way to do that. Even when we have an Annotation-based pointcut, e.g.
#Aspect
#Component
#EnableAspectJAutoProxy
public class WriteTransactionAspectBean {
#Before("#annotation(org.springframework.transaction.annotation.Transactional)")
public void test(org.springframework.transaction.annotation.Transactional t) {
System.out.println("TEST");
}
}
the issue is the Annotations aren't our own, they come from an external JAR (Hibernate). This would require Load-Time Weaving or some other difficult workaround.
Aspectj: intercept method from external jar
But to make things even worse, Annotations need RetentionPolicy=RUNTIME in order to be "discovered" by Pointcuts. And we would need to go thru every method and add this specification to every #Transactional. There's no way to automatically make all #Transactional's Runtime-retainable in the application.

AspectJ Pointcut Expression on domain objects not managed by Spring

Question: Can Spring point-cut expressions run on non-managed spring components such as domain object? From my experiments looks like it doesnt, so what is the best way to run pointcut expressions on a regular object?
I created custom annotation name #Encrypt, so that when it is used on top of a field in a domain object, the field is sent to a web service and is automatically encrypted.
I first started with method level annotation, and found that point cut expression doesn't work on Objects not managed by Spring, it has to be a spring bean.
1. Spring Aspect: Checks for custom annotation #Encrypt and prints out.
#Aspect
public class EncryptAspect {
#Around("#annotation(encrypt)")
public Object logAction(ProceedingJoinPoint pjp, Encrypt encrypt)
throws Throwable {
System.out.println("Only encrypt annotation is running!");
return pjp.proceed();
}
}
2. Custom Annotation:
#Documented
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Encrypt
{
// Handled by EncryptFieldAspect
}
3. Domain object using Annotation
public interface CustomerBo {
void addCustomerAround(String name);
}
public class CustomerBoImpl implements CustomerBo {
#Encrypt
public void addCustomerAround(String name){
System.out.println("addCustomerAround() is running, args : " + name);
}
}
4. Invocation
ApplicationContext appContext = new ClassPathXmlApplicationContext("http-outbound-config.xml");
// CustomerBoImpl customer = new CustomerBoImpl(); --> Aspect is not fired if object is created like this.
CustomerBo customer = (CustomerBo) appContext.getBean("customerBo"); // Aspect Works
customer.addCustomerAround("test");
To your first question ("Can Spring point-cut expressions run on non-managed spring components such as domain object?") the answer is no. Spring reference manual has a chapter that exactly explains how Spring AOP works and why it won't work in that case.
The options I see are (in order of how I would most likely approach this issue):
Droping the aspect and encapsulating this invariant in a service or a factory that creates CustomerBo's. It would be best, if CustomerBoImpl was immutable, so that you would not have to worry that it will be decrypted and left in that incorrect state.
If you are using Java Persistence API (JPA) to persist your domain objects, and if it is ok for the encryption to run just before saving them in the database, you may want to use listeners.(Note: the link leads to the documentation of Hibernate, which is one of the implementations of JPA)
The nuclear option - actually switching to using AspectJ which can introduce advice to constructors, field value changes etc.

Intercepting Specific Annotations with Spring AOP

I'm looking to see whether or not the following is even possible, as all preliminary searches haven't turned back anything to indicate either way.
I'd like to use Hibernate's Validator annotations to validate bean methods, and I would like to use some AOP framework (Spring, AOP Alliance, AspectJ, etc.) to intercept methods annotated with a subset of the Hibernate Validator annotations (#NotNull, #NotEmpty, #Email, etc.); I then want AOP advice to run when they are encountered.
Is this possible to do? If so, I am having a tough time visualizing how the code would work. Using Spring AOP's MethodInterceptor interface as an example:
First, the bean using Hibernate Validator:
public class SomeBean
{
private String data;
// Hibernate Validator annotation specifying that "data" cannot be an empty
// string.
#NotEmpty
public String getData() { ... } // etc.
}
Then, some code using that bean:
public void someMethod()
{
SomeBean oBean = new SomeBean();
// Validation should fail because we specified that "data" cannot be empty.
oBean.setData("");
}
Next, the AOP advice to be ran when Hibernate Validator-annotated methods are encountered.
public class ValidationInterceptor implements MethodInterceptor
{
public Object invoke(MethodInvocation invocation)
{
// Here's where we would use Hibernate's validator classes.
// My code example here is wrong, but it gets the point across.
Class targetClass = invocation.getClass(); // Should give me SomeBean.class
ClassValidator<targetClass> oValidator= new ClassValidator<targetClass>();
// Here I need to get a reference to the instance of the offending
// SomeBean object whose data has been set to empty...not sure how!
SomeBean oOffendingBean = getTheBadBeanSomehow();
InvalidValue[] badVals = oValidator.getInvalidValues(oOffendingBean);
}
}
So, not only am I choking on what the Spring AOP (pointcut definitions, etc.) configuration would look like to intercept the Hibernate Validator annotations I want, and not only do I not fully grasp how to implement the actual advice (e.g. how to instantiate the offending SomeBean from inside the advice as I mention above in the comments), but I'm not even sure if this solution is possible, Spring or otherwise.
Thanks in advance for some gentle "nudges" in the right direction!
You might be interested in the method validation feature introduced with Hibernate Validator 4.2 which provides support for validating method parameters and return values.
You then might use Seam Validation which integrates this functionality with CDI. If you want to use method validation together with Spring you could have a look this project on GitHub which shows how to integrate the method validation functionality with Spring AOP (disclaimer: I'm the author of this project as well as of Seam Validation).
To make your example working you would have to annote the parameter of the setter method with #NotEmpty like this:
public class SomeBean {
private String data;
#NotEmpty
public String getData() { return data; }
public void setData(#NotEmpty String data) { this.data = data; }
}

Spring, #Transactional and Hibernate Lazy Loading

i'm using spring + hibernate. All my HibernateDAO use directly sessionFactory.
I have application layer -> service layer -> DAO layer and all collections is lazly loaded.
So, the problem is that sometime in the application layer(that contains GUI/swing) i load an entity using a service layer method(that contains #Transactional annotation) and i want to use a lazly property of this object, but obviusly the session is already closed.
What is the best way to resolve this trouble?
EDIT
I try to use a MethodInterceptor, my idea is to write an AroundAdvice for all my Entities and use annotation, so for example:
// Custom annotation, say that session is required for this method
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface SessionRequired {
// An AroundAdvice to intercept method calls
public class SessionInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation mi) throws Throwable {
bool sessionRequired=mi.getMethod().isAnnotationPresent(SessionRequired.class);
// Begin and commit session only if #SessionRequired
if(sessionRequired){
// begin transaction here
}
Object ret=mi.proceed();
if(sessionRequired){
// commit transaction here
}
return ret;
}
}
// An example of entity
#Entity
public class Customer implements Serializable {
#Id
Long id;
#OneToMany
List<Order> orders; // this is a lazy collection
#SessionRequired
public List<Order> getOrders(){
return orders;
}
}
// And finally in application layer...
public void foo(){
// Load customer by id, getCustomer is annotated with #Transactional
// this is a lazy load
Customer customer=customerService.getCustomer(1);
// Get orders, my interceptor open and close the session for me... i hope...
List<Order> orders=customer.getOrders();
// Finally use the orders
}
Do you think can this work?
The problem is, how to register this interceptor for all my entities without do it in xml file?
There is a way to do it with annotation?
Hibernate recently introduced fetch profiles which (in addition to performance tuning) is ideal for solving issues like this. It allows you to (at runtime) choose between different loading and initialization strategies.
http://docs.jboss.org/hibernate/core/3.5/reference/en/html/performance.html#performance-fetching-profiles
Edit (added section on how to set the fetch profile using an interceptor):
Before you get started: Check that fetch profiles actually will work for you. I haven't used them myself and see that they are currently limited to join fetches. Before you waste time on implementing and wiring up the interceptor, try setting the fetch profile manually and see that it actually solves your problem.
There are many ways to setup interceptors in Spring (according to preference), but the most straight-forward way would be to implement a MethodInterceptor (see http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html#aop-api-advice-around). Let it have a setter for the fetch profile you want and setter for the Hibernate Session factory:
public class FetchProfileInterceptor implements MethodInterceptor {
private SessionFactory sessionFactory;
private String fetchProfile;
... setters ...
public Object invoke(MethodInvocation invocation) throws Throwable {
Session s = sessionFactory.openSession(); // The transaction interceptor has already opened the session, so this returns it.
s.enableFetchProfile(fetchProfile);
try {
return invocation.proceed();
} finally {
s.disableFetchProfile(fetchProfile);
}
}
}
Lastly, enable the interceptor in the Spring config. This can be done in several ways and you probably already have a AOP setup that you can add it to. See http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-schema.
If you're new to AOP, I'd suggest trying the "old" ProxyFactory way first (http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html#aop-api-proxying-intf) because it's easier to understand how it works. Here's some sample XML to get you started:
<bean id="fetchProfileInterceptor" class="x.y.zFetchProfileInterceptor">
<property name="sessionFactory" ref="sessionFactory"/>
<property name="fetchProfile" ref="gui-profile"/>
</bean>
<bean id="businessService" class="x.y.x.BusinessServiceImpl">
<property name="dao" .../>
...
</bean>
<bean id="serviceForSwinGUI"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="x.y.z.BusinessServiceInterface/>
<property name="target" ref="businessService"/>
<property name="interceptorNames">
<list>
<value>existingTransactionInterceptorBeanName</value>
<value>fetchProfileInterceptor</value>
</list>
</property>
</bean>
Create a method in the service layer that returns the lazy-loaded object for that entity
Change to fetch eager :)
If possible extend your transaction into the application layer
(just while we wait for someone who knows what they are talking about)
You need to rework your session management, unfortunately. This is a major problem when dealing with Hibernate and Spring, and it's a gigantic hassle.
Essentially, what you need is for your application layer to create a new session when it gets your Hibernate object, and to manage that and close the session properly. This stuff is tricky, and non-trivial; one of the best ways to manage this is to mediate the sessions through a factory available from your application layer, but you still need to be able to end the session properly, so you have to be aware of the lifecycle needs of your data.
This stuff is the most common complaint about using Spring and Hibernate in this way; really, the only way to manage it is to get a good handle on exactly what your data lifecycles are.

Categories

Resources