Category: Java

Always use interfaces for dependency injection

I working on a big legacy project that uses Spring IoC. And when it started I see in console warning messages like:

WARN  org.springframework.aop.framework.Cglib2AopProxy - Unable to proxy method [public final javax.sql.DataSource org.springframework.jdbc.core.support.JdbcDaoSupport.getDataSource()] because it is final: All calls to this method via a proxy will be routed directly to the proxy.

Here a sample code:

@Service
@Transactional
public class UserServiceImpl implements UserService {

}

@Controller
public class UserController {

    @Autowired
    UserServiceImpl  userService; // Here a problem: we declared field as concrete class UserServiceImpl  instead of interface UserService

}

As I found it happens when fields autowired as concrete class instead of injection by interface.

According to Spring AOP documentation:

Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. (JDK dynamic proxies are preferred whenever you have a choice).
If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used. All of the interfaces implemented by the target type will be proxied. If the target object does not implement any interfaces then a CGLIB proxy will be created.

Thus since userService injected by concrete class UserServiceImpl instead of interface UserService, so Spriong IoC tries to create a proxy class via CGLIB bytecode magic that shows this error message.

Also this CGLIB magic proxying may be disabled by configuration:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <ehcache:annotation-driven cache-manager="genericCacheManager" proxy-target-class="false"/>
    <tx:annotation-driven proxy-target-class="false"/>
    <aop:aspectj-autoproxy proxy-target-class="false"/>
</beans>

In this case this exception will be thrown during runtime:


Caused by: java.lang.IllegalStateException: Cannot convert value of type [com.sun.proxy.$Proxy56 implementing com.eample.services.UserService,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised] to required type [com.eample.services.UserServiceImpl] for property ‘userService’: no matching editors or conversion strategy found


To fix this we just need to use autowiring by interface and this is in common simple refactoring.

Please vote for this feature request to make IntellijIdea helpful in this New inspection: Use dependency injection by interface instead of concrete class

See also:
Spring: Why do we autowire the interface and not the implemented class?
Why always have single implementaion interfaces in service and dao layers?

Performance differences

Реклама

[LinkSet] Avoiding NullPointerException in Java

Transliteration to ASCII

If you need to make a translitartion from any language to ASCII symbols you can use a Transliterator from ICU4J.

private static final String TRANSLITERATION_RULE = "Any-Latin; Latin-ASCII";

private static String transliterate(String name) {
    String ascii = TRANSLITERATOR.transliterate(name);
    // Some Russian names may contain Soft Sign ( Ь ) and ( Ъ ) that may cause error http://sourceforge.net/p/icu/mailman/message/34413588/
    ascii = ascii.replaceAll("[ʹʺ]", "");
    return ascii;
}

ICU Transform Demonstration
1) Select «Names» from «Inset sample» combo box.
2) Insert the rule «Any-Latin; Latin-ASCII» to the «Compound 1» fields.
3) Press «Transform» button

Also a good example:
How do I convert Chinese characters to their Latin equivalents?

What are the system Transliterators available with ICU4J?

[Linkset] Few interesting links about Strings in Java

[Linkset] Migration to Java 8

Notes from Oracle http://www.oracle.com/technetwork/java/javase/8-compatibility-guide-2156366.html

Also possible problems:

  • switching to 64bit JVM can require more memory (actually as I know it was only in few builds of Oracle JDK 6 and all next versions comes with enabled flag -XX:+UseCompressedOops)
  • changes in garbage collection can lead to unexpected pauses
  • inclusion of XML parsers into JDK proper requires changes to web application packaging or configuration
  • memory and runtime characterics of String#substring completely change in «minor» JDK revision
  • sorting a collection with a custom (incorrectly implemented) comparator suddenly throws exceptions it did not throw before  (Java error: Comparison method violates its general contract)
  • Calling Thread#stop(Throwable) (which was never a good idea and has been deprecated for a very long time) throws a UnsupportedOperationException since Java 8
  • Updated Unicode support changing sorting and casing behavior for some strings
  • Changes in generics compilation
  • inability to extend BitSet and implement Set<Integer> due to new default methods
  • Bigin rounding behavior (affected early builds of JDK7 and JDK8)

http://stackoverflow.com/questions/28228450/java-is-backward-compatible-but-why-we-need-to-upgrade-many-libraries-when-we-u

Also I tried to mark all this issues on StackOverflow with tags migration+java-8 and migration+java-7

Most valuable article http://product.hubspot.com/blog/upgrading-to-java-8-at-scale
Key points: you need to update ASM lib and use cglib-nodep where possible.

Managing compatibility

See linkset [LinkSet] Compatibility
Tool for checking API and BPI http://ispras.linuxbase.org/index.php/Java_API_Compliance_Checker
http://wiki.apidesign.org/wiki/SignatureTests
https://stackoverflow.com/questions/2040693/how-to-identify-a-missing-method-binary-compatibility-in-a-jar-statically
http://clirr.sourceforge.net/
http://www.sab39.org/Software/Japitools/42/
http://tattletale.jboss.org/

How Spring achieves compatibility with Java 6, 7 and 8

And don’t forget to enable -parameters option https://julien.ponge.org/blog/java8-parameter-names-and-jdk-dogfooding/

https://stackoverflow.com/questions/8111310/java-7-jdk-7-garbage-collection-and-documentation

http://allegrotech.io/How-to-migrate-to-Java-8.html

https://www.techempower.com/blog/2013/03/26/everything-about-java-8/

http://zeroturnaround.com/rebellabs/what-migrating-to-java-8-will-do-to-your-codebase-a-practical-example/

In some cases it possible speed downgrade
https://stackoverflow.com/questions/23756966/why-is-stringbuilderappendint-faster-in-java-7-than-in-java-8

Also from my experience of migration from JDK6 to JDK8:

1 . APT (Annotations Processing Tool) was removed. We used Enunciate for Web Services and it uses APT. So now we waiting next version of Enunciate that will use JSR 269 instead of APT.

And actually there was a lot of API that was removed from Java and in first link Oracle told about it just see «Features Removed from Java SE 8» and «Features Removed from JDK 8».

2 . java.util.Locale class was updated and it causes error in our application.
We have some users with ISO country code CS i.e. Serbia and Montenegro. In 2004 Montenegro separated from Serbia and this code CS was removed in new version of Locale.getISOCountries().
Since our application had checks on this code we got a lot of exceptions.

3 . maven-tomcat-plugin hasn’t yet version for Tomcat 8. You can vote or contribute MTOMCAT-234. And in Tomcat 8 in some configs we needed to change path starting with root /

4 . HashMap descendants should keep contract between put() and putAll()
All classes that extends HashMap and overrides `put() method may have errors if not overrides putAll() method.
You can see interest bug in Spring framework for details https://jira.spring.io/browse/SPR-7969.

5 . Clashing Getters.
Also we had an error when our domain classes contains two getters for the same property.
For example, class MerchantData contains two methods getParentMerchant() and isParentMerchant()

public void setParentMerchant(MerchantData parentMerchant) {
this.parentMerchant = parentMerchant;
}

public MerchantData getParentMerchant() {
return parentMerchant;
}

public boolean isParentMerchant() {
return subMerchants != null &amp;&amp; !subMerchants.isEmpty();
}

According to JavaBeans specification all getters of boolean properties should be named isSomething().
Hibernate tries to determine the type of property parentMerchant and it looks that type is Boolean.
Then he tries to get id of parentMerchant ant fail with error:

Caused by: org.hibernate.PropertyAccessException: IllegalArgumentException occurred calling getter of com.example.merchant.dao.MerchantData.id
at org.hibernate.property.BasicPropertyAccessor$BasicGetter.get(BasicPropertyAccessor.java:171)

To fix that we need to rename method isParentMerchant() or get rid of it.
This kind of issues are happened only when we run project on JDK8.
So, please take it into account when you create a new getters.

I asked guys from JetBrains to make inspection for IntelliJ that reports about this potential bugs, please vote.
Also I created plugin https://github.com/stokito/IntellijClashingGettersInspection

Something similar Why did PropertyDescriptor behavior change from Java 1.6 to 1.7?

6 . Groovy code may fails with java.lang.IncompatibleClassChangeError during constructing exceptions that was compiled in Java 6. Good article How to fix IncompatibleClassChangerError with groovy on jdk7
It’s very often error in Grails applications

7 . Maven is not working in Java 8 when JavaDoc tags are incomplete
You can easily find all this incomplete tags in IntelliJ by running inspection Ctrl+Alt+Shift+i «Declaration has JavaDoc problems»
Also there was few other errors in maven plugins, most of them already fixed but check them anyway
https://cwiki.apache.org/confluence/display/MAVEN/Java+8+Upgrade

8 . ActiveMQ still has a bug that occurs only in JDK 8 AMQ-5356 Unable to see message contents from the web UI

9 . You really should resolve your JAR HELL before upgrading.

Also known bug:
Xstream no-args constructor error

Tutorial: Migrating to Java 8

Migration plan:

  1. Make build stable: migrate to the latest Maven, introduce Continuous Integration.
  2. Resolve JAR hell, explicitly define all libraries and plugins versions.
  3. Improve test coverage, take care to rounding of BigDecimal, working with dates, localization (country codes and time zones),  Comparators, XML parsing,  Reflection and serialization. Integration tests for all parts of application that using external JAR’s or classes for example database  (JDBC drivers).
  4. Fix basic and common errors: clashing geters, missing @Override annotations.
  5. Upgrade all bytecode-level manipulation libraries (CGLIB, ASM, JavaAssist)
  6. Try to compile with old JDK (6, 7) but try to run with new JDK (8, 9).
  7. Run all regression tests and fix all bugs that you’ll find.
  8. Upgrade other core libraries (Hibernate, Spring, JDBC divers) and try to remove obsolete (com.sun package)
  9. Test again and fix new founded bugs.
  10. Then try to compile with new JDK (8,9) but with target language level 6. But still run with new one.
  11. Test again and fix new founded bugs.
  12. Then try to compile with new JDK (8,9) and with last target language level (8, 9). And run with last.
  13. Test again and fix new founded bugs. Migration finished

[Linkset] «Stringly typed» Antipatern: Avoid string where other types are more appropriate