0

We recently migrated from a pretty old Spring version (3.2.16.RELEASE) to one of the latest (4.2.5.RELEASE). Because of this change we were able to remove in some .xml files more than 1k lines of code and replace them with just about 50-100 annotations in the Java classes. All in all a great change.

Yesterday I also started the process of removing all interfaces that were being added by the @Service-annotation. So, before we had something like this:

//The interface:
public interface SomeInterfaceService extends DtoProcessingService<SimpleDto> {
}

//The class:
@Service("someClassService")
public class SomeClassService
    extends SomeAbstractClassService<SimpleDto, Strategy>
    implements SomeInterfaceService {
  ...
}

Which we've refactored to:

// The interface has been removed

//The class:
@Service
public class SomeClassService
    extends SomeAbstractClassService<SimpleDto, Stategy> {
  ...
}

In the new Spring versions, @Service automatically uses the class-name starting with a lowercase (so someClassService).

At first we received some errors, and a possible solution for this was a Spring xml-setting to explicitly use proxy classes. So, I've read a bit about what the force of these proxies imply, and found the following things:

... According to the project page "CGLIB is used to extend Java classes and implements interfaces at runtime". So in this case the trick consists on create a proxy that EXTENDS the original object and therefore can be used instead. (source)

1) final methods cannot be advised, as they cannot be overriden. (source)
2) You will need the CGLIB 2 binaries on your classpath, whereas dynamic proxies are available with the JDK. Spring will automatically warn you when it needs CGLIB and the CGLIB library classes are not found on the classpath. (source)
3) The constructor of your proxied object will be called twice. This is a natural consequence of the CGLIB proxy model whereby a subclass is generated for each proxied object. For each proxied instance, two objects are created: the actual proxied object and an instance of the subclass that implements the advice. This behavior is not exhibited when using JDK proxies. Usually, calling the constructor of the proxied type twice, is not an issue, as there are usually only assignments taking place and no real logic is implemented in the constructor. (source)
4) Your classes cannot be final. (source 1 & source 2)

None of our classes were final, none of their methods were final, none of the classes had implicit Constructors, so I've added this setting to the Spring xml:

 <tx:annotation-driven proxy-target-class="true"/>

And the error disappeared.

Now I have a few problems and concerns though:
Some classes / interfaces were a bit too difficult to refactor, so we left them as they were (for now). These are mostly class-trees with a lot of generics, abstraction and therefore difficult to refactor easily. Also, there are some other classes with interfaces I haven't even touched yet that still use interfaces.
Is this possible with the setting above, to use both CGLIB proxies AND JDK interfaces?

My guess is not entirely. Why? The classes with interfaces still seem to be initialized, but the fields that used to be filled automatically with Spring / Tapestry, aren't anymore for these classes (and I'm getting NullPointerExceptions everywhere). For example, the following used to work fine before the changes, but geometryMessageService is now null:

// Note that this is an unchanged class

@Transactional
@Service("geometryService")
public class DefaultGeometryService implements GeometryService {
  ...

  private GeometryMessageService geometryMessageService;

  public void setGeometryBerichtService(final GeometryBerichtService geometryBerichtService) {
    this.geometryBerichtService = geometryBerichtService;
  }

  public void someMethod(){
    ...
    this.geometryBerichtService.doSomething(); // <- NullPointerException
    ...
  }
}
Community
  • 1
  • 1
Kevin Cruijssen
  • 9,153
  • 9
  • 61
  • 135
  • That should simply work, assuming that `someMethod` is in the interface and you are actually programming to interfaces. You are using a setter but I nowhere see an `@Autowired` or the likes. So it more looks like you aren't configuring things right. Also why remove the interfaces? Also if you look at the classes with a debugger, you are actually looking at the proxy and not the actual wrapped instance! If you don't force class based proxies spring tries it best and will create the needed proxy where needed. – M. Deinum Jul 21 '16 at 13:32
  • @M.Deinum `someMethod` is indeed in the interface. We aren't using `@Autowired` anywhere because we use `Tapestry` (still version 4..). The reason we want to remove the interfaces is because we were under the impression they are no longer needed with the new Spring version with the `@Service` allowing to automatically create the services by class-name (starting with a lowercase). Removing 100-200 interfaces, including the `implements`, including the `("className")` after the `@Service` is less code, which is usually preferred. Especially since they were all empty Marker Interfaces. – Kevin Cruijssen Jul 21 '16 at 13:48
  • Interfaces serve a purpose and why remove something that worked before just for the sake of less code. Interfaces should expose a contract if they are empty you are doing things the wrong way imho. Nonetheless the fact that you are using proxies might be the reason that Tapestry breaks things. I was under the impression that those beans where spring managed and configured if they aren't that is a whole other case. Can you add some configuration for the bean that isn't working anymore. – M. Deinum Jul 22 '16 at 05:31

0 Answers0