Clean code with aspects
- what is Aspect Oriented Programming and why we need it
- what is AspectJ
- using AspectJ with Spring (configuring AspectJ and spring to work together)
- and i will explain aspects on the example from previous post.
What Is Aspect Oriented Programing and why we need it
During software development we can use different programming paradigms such as OOP (object oriented programing) or POP(procedural oriented programing).
Today most of us use Object Oriented Programming methodologies for resolving real life problems, during software development process.
But during our work, we are constantly meeting with some code which crossing through our code base and breaking its modularity and making it dirty.
This part of code usually don’t have business values, but we need them to resolve our problems.
For example we can take a look on database transactions.
Transactions are very important for our software, because they take care about data consistency. The code which start and handle transaction are very important for our application but it is used for technical stuff (starting, committing and rolling back transactions). This things make it difficult to understand what is real meaning of code (to see real business value of code).
Of course i will not make any example of how to handle transactions using aspects because there is a lot of frameworks which will take care about transactions instead of us. I’ve just mentioned transactions because you probably know how to insert data into database using a plain JDBC API.
So to make our code cleaner we will use a design patterns, which is a good approach for the problem solving. But also sometimes a usage of design patterns will not lead us to easy solution, and most of us will resort to the easier solution what will produced “dirty” code.
In this situation we should give a chance to Aspect Oriented approach for the problem solving. When we think about AOP we should not think about something totally new for us, we should think about AOP as a complements of OOP. AOP is there to make easier code modularisation, make code cleaner, and provide us with easier and faster understand what some part of application should do.
AOP introduce a few new concepts which will allow us easier code modulation. If we want to efficiently use Aspects we need to know its basics principe and terminology.
When we start with using AOP we will meet a new termines:
- Crosscutting concerns, it is code which should be moved in separate module (i.e. code for handling transactions).
- Aspect, it is a module which contains concerns.
- Pointcut, we can look at it as pointer which will instruct when corresponding code should be run
- Advice, it contains a code which should be run when some join point is reached.
- Inner-type declaration, allow modification of class structure.
- Aspect-weaving, is mechanism which coordinate the integration with the rest of the system.
I will show at the end w hat are they and how to use them within an example.
What is AspectJ
AspectJ is an extension of Java programing language which allow usage of AOP concepts within Java programing language.
When you use AspectJ you do not need to make any changes in your existing code.
AspectJ extend java with a new construct called aspect, and after AspectJ 5 you can use annotation based development style.
AspectJ and Spring
Spring framework already provide its own implementation of AOP. Spring AOP is simpler solution than AspectJ but it is not so robust as AspectJ. So if you want to use aspects in your spring application you should be familiar with possibilities of Spring AOP before choosing AspectJ to do work.
Before we see the example of using aspect i will show you how to integrate AspectJ with Spring and how to configure Tomcat to be able to run AspectJ application with Spring. In this example I’ve used LTW (load time weaving) of aspects.
So I will start explaining first how to do it from Spring. It is easy, just add next line in your application configuration file:
<context:load-time-weaver aspectj-weaving="autodetect"/>
That is all what is needed to be done with in spring configuration.
The next step is configuration of Tomcat. We need to define new class loader for application. This class loader need to be able to do load time weaving so we use:
<loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader" />
The loader need to be in Tomcat classpath before you can use it.
Of course, in order to make this work, we need to create aop.xml file. This file contains instruction which will be used by class loader during class transformation process.
Here is example of aop.xml file which I’ve used for alphabet convertion.
<aspectj> <weaver options="-Xset:weaveJavaxPackages=true"> <!-- only weave classes in our application-specific packages --> <include within="ba.codecentric.medica.model.*" /> <include within="ba.codecentric.medica..*.service.*" /> <include within="ba.codecentric.medica.controller..*" /> <include within="ba.codecentric.medica.utils.ModelMapper" /> <include within="ba.codecentric.medica.utils.RedirectHelper" /> <include within="ba.codecentric.medica.aop.aspect.CharacterConvertionAspect" /> <include within="ba.codecentric.medica.security.UserAuthenticationProvider" /> <include within="ba.codecentric.medica.wraper.MedicaRequestWrapper"/> </weaver> <aspects> <!-- weave in just this aspect --> <aspect name="ba.codecentric.medica.aop.aspect.CharacterConversionAspect" /> </aspects> </aspectj>
This last xml file is most interesting for all of you which are willing to try AspectJ. It instruct AspectJ weaving process.
The weaver section contains information about what should be weaved. So this file will include all Classes inside:
- ba.codecentric.medica.model.*
- ba.codecentric.medica..*.service.*
- ba.codecentric.medica.controller..*
- ba.codecentric.medica.utils.ModelMapper
- ba.codecentric.medica.utils.RedirectHelper
- ba.codecentric.medica.aop.aspect.CharacterConvertionAspect
- ba.codecentric.medica.security.UserAuthenticationProvider
- ba.codecentric.medica.wraper.MedicaRequestWrapper
So the first line including all classes inside of model package. The second one include all Classes which are part of services sub packages inside of ba.codecentric.medica package (i.e. ba.codecentric.medica.hospitalisation.service). The third one include everything belowe controller package. And rest of the lines include specified classes.
Options attribute define addition option which should be used during weaving process. So in this example -Xset:weaveJavaxPackages=true instruct AspectJ also to weave java packages.
Aspects section contain list of aspects which will be used during weaving process.
For more information about configuration with xml you can see AspectJ documentation.
Example of usage AspectJ
I prefer usage of Annotation so the next example will show you how to use AspectJ with annotation. Annotation driven programming with AspectJ is possible from version AspectJ 5. Here is some code of a complete aspect which contains concerns used for alphabet conversion.
package ba.codecentric.medica.aop.aspect; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import ba.codecentric.medica.utils.CharacterConverter; import ba.codecentric.medica.utils.ContextHelper; import ba.codecentric.medica.utils.LanguageHelper; /** * Aspect used for transformation characters from one alphabet to another. * * @author igor * */ @Aspect public class CharacterConvertionAspect { private static Log LOG = LogFactory.getLog(CharacterConvertionAspect.class); public int getConvertTo() { return getLanguageHelper().getConvertTo(); } protected LanguageHelper getLanguageHelper() { return ContextHelper.getBean("languageHelper"); } public CharacterConvertionAspect() { LOG.info("Character converter aspect created"); } @SuppressWarnings("rawtypes") @Around("execution(public java.lang.String ba.codecentric.medica.model..*.get*(..)) && !cflow(execution(* ba.codecentric.medica.controller..*.*(..))) && !cflow(execution(public void ba.codecentric.medica..*.service..*.*(..))) && !cflow(execution(* ba.codecentric.medica.security.UserAuthenticationProvider.*(..)))") public Object convertCharacters(ProceedingJoinPoint pjp) throws Throwable { LOG.info("Character conversion trigered"); Object value = pjp.proceed(); if (value instanceof String) { LOG.info("Convert:" + value); Signature signature = pjp.getSignature(); Class type = signature.getDeclaringType(); String methodName = signature.getName(); Map<Class, List<string&lgt;&lgt; skipConvertionMap = getBlackList(); if(skipConvertionMap.containsKey(type)){ List<string&lgt; list = skipConvertionMap.get(type); if(list == null || list.contains(methodName)){ LOG.info("Value will not be converted because it is on blacklist"); return value; } } return getConverter().convertCharacters((String) value, getConvertTo()); } LOG.info("Convertion will not be performed (" + value + ")"); return value; } @Around("execution(public void ba.codecentric.medica.model..*.set*(java.lang.String))") public Object convertCharactersToLat(ProceedingJoinPoint pjp) throws Throwable { Object value = pjp.getArgs()[0]; LOG.info("Converting value:" + value + ", before persisting"); if (value instanceof String){ value= getConverter().convertCharacters((String)value, CharacterConverter.TO_LAT); } return pjp.proceed(new Object[]{value}); } /** * Convert parameter to Latin alphabet * * @param pjp * @return * @throws Throwable */ @Around("execution(public * ba.codecentric.medica.wraper.MedicaRequestWrapper.getParameter*(..))") public Object convertParametersToLat(ProceedingJoinPoint pjp) throws Throwable { Object value = pjp.proceed(); return getConverter().convert(value, CharacterConverter.TO_LAT); } /** * If result of the invocation is String, it should be converted to chosen alphabet. * * @param jp * @return converted value * @throws Throwable */ @Around("execution(* ba.codecentric.medica.controller..*.*(..))") public Object procedWithControllerInvocation(ProceedingJoinPoint jp) throws Throwable { Object value = jp.proceed(); return getConverter().convert(value, getConvertTo()); } public CharacterConverter getConverter() { return ContextHelper.getBean("characterConverter"); } @SuppressWarnings("rawtypes") public Map<Class,List<string&lgt;&lgt; getBlackList(){ return ContextHelper.getBean("blackList"); } }
First of all we can see that class is annotated with @Aspect annotation. This indicate that this class is actually an aspect. Aspect is a construction which contains similar cross-cutting concerns. So we can look at it as a module which contains cross cutting code and define when which code will be used and how.
@Around("execution(public void ba.codecentric.medica.model..*.set*(java.lang.String))") public Object convertCharactersToLat(ProceedingJoinPoint pjp) throws Throwable { Object value = pjp.getArgs()[0]; LOG.debug("Converting value:" + value + ", before persisting"); if (value instanceof String) { value = getConverter().convertCharacters((String) value, CharacterConverter.TO_LAT); } return pjp.proceed(new Object[] { value }); }
This is a method which is annotated with @Around annotation. The around annotation is used to represent around advice. I have already mentioned that, advice is the place which contains a cross-cutting code. In this example I’ve only used “around” advice, but except that there is also before,after,after returning and after throwing advice. All advice except around should not have a return value. The content inside of around annotation define when code from advice will be weaved. This also can be done when we define pointcuts. In this example I did’t use pointcuts for defining join points because it’s simple aspect. With pointcut annotations you can define real robust join points. In this case advice will be executed during setting values of entity beans which have only one parameter of type String.
ProcidingJoinPoint pjp, in the example above, present the join point, so for this example it is setter method of entity bean. Value of object send to the entity setter method will be first converted and then the setter method will be called with a converted value.
If I didn’t use aspects, my code could look like:
public void setJmbg(String jmbg) { this.jmbg = getConverter().convertCharacters(jmbg, CharacterConverter.TO_LAT); }
I’ve already said that for this example I use LTW. So in the next few lines I will try to explain weaving process briefly. Weaving is process in which the class is transformed with defined aspect. In the next picture you can see illustration of weaving process.
For better understanding of weaving, you can consider it as code injection around the calling method, in this case.
Conclusion
So in this example I’ve just covered some basic principles of aspect programming with AspectJ.
This aspect helped me to keep the code clean. The result of using aspect is clean separation of crossing-cut code and the code of real business value. The controllers, services and entity beans stayed clean and technical code is extracted in separate module which allow you to easier understand and maintain your code more easily. For more details information about defining pointcuts and general about AspectJ project you can see on the project page.
Happy coding and don’t forget to share!
Reference: Clean code with aspects from our JCG partner Igor Madjeric at the Igor Madjeric blog.