Sunday, June 27, 2010

Update: Spring 3: OXM Blog entry

While doing my RESTful webservice example I came across a marshaller that I had missed in my original Spring 3: OXM post.


XStream

To the maven pom I added:

 com.thoughtworks.xstream
 xstream
 1.3.1


To the class being marshalled:

@XStreamAlias( "MyXMLName" )

And to the application context:


 



I have also uploaded the example project into SVN

Friday, June 25, 2010

No REST for the wicked or the Corporate Developer

REST vs. Other Web Services

I know this topic has probably been covered a million times in the last couple years, but being a developer in a large corporate where "standards" are imposed to limit the craziness that would ensue without them. I have actually only now started to have a look at it. The reason for me looking at it now is the Spring 3 RESTful support, which I plan to play with in the next day or so.

Not really going to go into SOAP and XML-RPC, as I generally know that and they are the only type of webservices we implemented in my working environment. I know that SOAP and XML-RPC, uses an envelope, XML and is well defined / strongly typed... bla bla bla...

So from random sources this is what I found about REST:

REST stands for Representational State Transfer
Each unique URL is a representation of some object.
You can get the contents of that object using an HTTP GET.
You may use a POST, PUT, or DELETE to modify the object.
It is not necessary to use XML as a data format, so for small amounts of data it can be significantly lighter on bandwidth.
REST as a protocol does not define any form of message envelope.
REST takes advantage of HTTP caches. (A cache can't to do anything a POST; but they can cache GETs and expire those entries based on PUTs and DELETEs.).

So where to use RESTful ?
Well from what I just read, it is a smaller, quicker, HTTP interface which sounds perfect for replacing all "system internal" service calls happening between components where direct RPC is not ideal and it really isn't worth the time, effort and red tape to set up a WSDL / XSD.
(that was a really long sentence)


Ignore: just for proof of ownership: GUNCP4EUNAGF

Monday, June 21, 2010

Fluent Interfaces...Great.when.good && Fn.complicated.ifelse().true.

While scanning the internet, looking for something to comment on and maybe learn, My wife, a .Net Developer, mentioned...

Sharp Test Ex... I went to check it out and was instantly intrigued...
With syntax shown below who can blame me, nice readable tests:

const string something = "something";
something.Should().Contain("some");
something.Should().Not.Contain("also");
something.ToUpperInvariant().Should().Not.Contain("some");

var ints = new[] { 1, 2, 3 };
ints.Should().Have.SameSequenceAs(new[] { 1, 2, 3 });
ints.Should().Not.Have.SameSequenceAs(new[] { 3, 2, 1 });
ints.Should().Not.Be.Null();
ints.Should().Not.Be.Empty();


I then remembered a couple years back I came across something Martin Fowler chatted about: Fluent Interface

I never paid too much attention to it then, but as the years add up and I get more lazy, this appeals to me more. So not to be out done by .Net I went looking for java equivalents. I found 2, FEST and OP4j.

FEST has a couple sub projects: Reflection, Swing, JavaFX, Mock, Assertions
To compare with the .Net example above I'll go to FEST Asserts.
FEST Asserts
"
Provides a fluent interface for writing assertions. Its main goal is to improve test code readability and make maintenance of tests easier.
"

With example Syntax below... nice ... simple ... readable... great, I'll be implementing that in the next project I have a little bit of spare time. It doesn't cater for everything but from the looks of it there will 0 ramp up time and should be usable / understandable right after adding it to the maven pom and downloading the libs.

int removed = employees.removeFired();
  assertThat(removed).isZero();

  List< Employee > newEmployees = employees.hired(TODAY);
  assertThat(newEmployees).hasSize(6)
                          .contains(frodo, sam);

  String[] newHires = employees.newHiresNames();
  assertThat(newHires).containsOnly("Gandalf", "Arwen", "Gimli"); 

  assertThat(yoda).isInstanceOf(Jedi.class)
                  .isEqualTo(foundJedi)
                  .isNotEqualTo(foundSith);



Now OP4J...
op4j

"
op4j is a powerful implementation of the Fluent Interface style of code. It allows you to create chained expressions which apply both predefined or user-defined functions to your objects in a fluid and readable way. This improves the way your code looks and greatly reduces the complexity of executing auxiliary low-level tasks in the highly bureaucratic, statically -and strongly- typed language that Java is.
"

"several hundred functions with more than one thousand different parameterizations"

Off the bat seems like a monster. They seem to have put a lot of time and effort into "bending the java spoon". From a quick look it seems they cater for a ton of common code occurences and allow you to condense quite a bit of code into 1 liners.
Although I do see value in this framework, I have some concerns on how much there is to learn, the ramp up time required, and how easy it would be for a junior developer to maintain.


Example syntax below:

// Without op4j
  Set< String > set = new LinkedHashSet< String >(list);      
  // With op4j
  Set< String > set = Op.on(list).toSet().get();


 Set < Calendar > set = Op.on(list).toSet().removeAllNull().forEach()
.exec(FnString.toCalendar("dd/MM/yyyy")).get();

 Function< List < String >,Set < Calendar > > dateProcessFunction = 
 Fn.onListOf(Types.STRING)
.toSet().map(FnString.toCalendar("dd/MM/yyyy"))
.removeAllNullOrTrue(FnCalendar.after(now)).get();



In conclusion, it is not entirely fair to compare OP4J with the much smaller "testing" fluent interfaces as they are targeted at different functionality. However, I think the idea of Fluent Interfaces is quite a nice one. On large systems with internal frameworks and structured code environments like large corporates and banks, defining and implementing a proprietary fluent interface type of framework could , if done correctly, be very very useful and simplify large amounts of commonly used code. There is however, a distinct danger that it may also lead to vastly harder to read code that is also harder to maintain and debug instead of the original concept of "The API is primarily designed to be readable and to flow"

What I do like of both "FEST Asserts" and the .Net "Sharp Test Ex" is that they are limited to testing and geared only towards quicker, more understandable test cases. Anything that makes testing quicker, easier or a little more fun is well worth it.


I also stumbled across the following,

Fluent Build eclipse builder.

How not yet had time to investigate it properly, but definitely worth keeping an eye on.

Monday, June 14, 2010

Spring 3: Scheduling... Take 2

In the previous blog entry I used Spring annotation scheduling to fire an event, but there is also another way through the application context xml.

Annotation:
In the application context.




Then within the code annotate the method with @Scheduled(fixedDelay = 10000)

XML:
Now if you annotate your class with @Service or @Component (with component-scan), you can use the scheduled-tasks tag, and schedule a method on a service and specify a cron expression, which does give you all the control you'll need.




    



Since I personally can never remember the exact format for cron I am adding the next little bit for easy reference.

* * * * * *
| | | | | |
| | | | | +-- Year (range: 1900-3000)
| | | | +---- Day of the Week (range: 1-7, 1 standing for Monday)
| | | +------ Month of the Year (range: 1-12)
| | +-------- Day of the Month (range: 1-31)
| +---------- Hour (range: 0-23)
+------------ Minute (range: 0-59)


0 23 * * 1-5 * At 11:00PM on weekdays Mon-Fri
0 0 1 * * * At midnight, on the first day of each month
* * * * * ? Every Second
0 * * * * ? Every Minute

Sunday, June 13, 2010

Spring 3: Scheduling, Components, PostBeanProcessors and bending the rules.

This blog entry should come with one of those warnings "Do not attempt this at home, the following is done by trained professionals".. bla bla bla

To begin, I, being a developer always push the limits and bend the rules of what we should / or should not do. I set out to "bend" something in Spring for a change. I work in a 24/7/365 live type environment, where there are always people using the system and any downtime causes many reports and cascading pain flowing down the leadership hierarchy in a large corporate environment. Now sometimes in this environment even with tons of effort, signoffs, QA's, UAT's, Training something goes live, or happens in live that needs to be changed / switched off.

Now using Spring for injecting all that needs to be injected is awesome, but it does require that the application context be recreated / instantiated to pickup configuration changes. The following code changes that...

As with the method cache aspect, I will make the code available at my google code project:
Source Code

So we want to configure a field in a Spring managed bean to be available to be changed while the application is running.

I have specified an annotation @LiveConfig:
package javaitzen.spring.liveconfig;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) 
@Target({ElementType.FIELD })
@Documented
public @interface LiveConfig {
  String configLocation() default "liveconfig.properties";
}


To pick up all the @LiveConfig annotations, I created a BeanPostProcessor , that will be called when the ApplicationContext instantiates and keep track of all the @LiveConfig available to it.

package javaitzen.spring.liveconfig;

import java.lang.reflect.Field;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class LiveConfigBeanProcessor implements BeanPostProcessor {
    private Log logger = LogFactory.getLog(this.getClass());

    @Autowired
    private ConfigLoader loader;
    public final Object postProcessBeforeInitialization(final Object bean, final String beanName) {

        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(LiveConfig.class)) {

                try {
                    field.setAccessible(true);
                    LiveConfig ann = field.getAnnotation(LiveConfig.class);
                    LiveConfigData lcd = new LiveConfigData();
                    lcd.setFile(ann.configLocation());
                    lcd.setBean(bean);
                    lcd.setField(field);
                    loader.addConfig(field.getName(), lcd);
                    loader.load();
                } catch (Exception e) {
                    logger.warn(e);
                }
            }
        }
        return bean;
    }

    public Object postProcessAfterInitialization(final Object bean, final String beanName) {
        return bean;
    }

}


So now when the ApplicationContext loads, it will add LiveConfigData to the ConfigLoader. This config loader uses the new Spring 3 Scheduling annotations to fire every 30 seconds asynchronously.

package javaitzen.spring.liveconfig;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * The Class ConfigLoaderImpl.
 */
@Component
public class ConfigLoaderImpl implements ConfigLoader {

    private Map< String, LiveConfigData > liveConfigs = new HashMap< String, LiveConfigData >();

    /**
     * Instantiates a new config loader impl.
     */
    public ConfigLoaderImpl() {

    }

    /**
     * Adds the config.
     * 
     * @param field
     *            the field
     * @param data
     *            the data
     */
    public void addConfig(final String field, final LiveConfigData data) {
        liveConfigs.put(field, data);
    }

    /**
     * Load.
     * 
     * @see javaitzen.spring.liveconfig.ConfigLoader#load()
     */
    @Override
    @Scheduled(fixedDelay = 30000)
    @Async
    public void load() {

        FileInputStream in = null;
        try {
            for (Entry< String, LiveConfigData > liveConfig : liveConfigs.entrySet()) {
                LiveConfigData lcd = liveConfig.getValue();
                Properties props = new Properties();
                in = new FileInputStream(lcd.getFile());
                props.load(in);
                for (Entry< Object, Object > entry : props.entrySet()) {
                    String key = (String) entry.getKey();
                    String value = (String) entry.getValue();
                    LiveConfigData data = liveConfigs.get(key);
                    data.getField().set(data.getBean(), value);
                }
            }
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }
}

The Application Context, is surprisingly simple. The context:component-scan picks up the classes annotated with @Component (ConfigLoaderImpl and the BeanPostProcessor). The task:annotation-driven lets Spring know to look for @Scheduled. The loader is @Autowired into the BeanPostProcessor.




 
 

    



For demostration purposes I just created a simple little WorkManager structure to show how to configure the fields and have a running application to test when changing the configuration changes. It also shows how to get config from a file that is not the default "liveconfig.properties"

package javaitzen.spring.liveconfig.service;

import java.util.Date;

import javaitzen.spring.liveconfig.LiveConfig;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class TheWorkManager implements WorkManager {

    @LiveConfig
    private String filePickupURL;

    @LiveConfig
    private String enableDetail;

    @LiveConfig(configLocation = "duplicate.properties")
    private String allowDuplicates;

    @Autowired
    private Work worker;

    /**
     * process.
     * 
     * @see javaitzen.spring.liveconfig.service.WorkManager#process()
     */
    @Scheduled(fixedDelay = 10000)
    public void process() {
        System.out.println("processing: " + new Date());
        for (int i = 0; i < 3; i++) {
            worker.doWork("" + i, filePickupURL, enableDetail, allowDuplicates);
        }
    }

}

Wednesday, June 9, 2010

Neater Item Removal

I stumbled apon a bit of code today, this removes items in a list and doesn't get a
java.util.ConcurrentModificationException, but it is pretty ugly.

List< Item > newItems = new LinkedList< Item >();

        for (Item item : items) {
            if (item.isAType() ) {
                newItems.add(item);
            }
        }
        items = new LinkedList< Item >();
        items.addAll(newItems);

As Part of reading that "5 Things you didn't know" series from IBM mentioned in a previous blog entry.

A much neater way is using the remove() method on an Iterator:
for (Iterator< Item > itemsIter = items.iterator(); itemsIter.hasNext();) {
     if (itemsIter.next().isAType()) {
  itemsIter.remove();
     }
 }

Monday, June 7, 2010

Spring, AspectJ, Ehcache Method Caching Aspect

I know this is not a new concept, and I am pretty sure there a couple implementations of method caching out there, possibly some much more thorough, but I feel quite strongly about it and I think that it is still one of the most beneficial things ever to come out of the AOP design philosophy. It is also something that if implemented correctly can vastly improve the performance for a lot of systems out there.

A good couple years back, I stumbled across a blog post describing the concept of a method caching interceptor, that would allow you to cache method calls without changing code and thereby make some dramatic performance improvements on existing systems. I jumped on the concept, took some code from the blog, fixed an issue or 2, changed a couple things to my requirements, demo'ed it to the system architect at the time and implemented it for the next release cycle. That bit of code has been running for about 4 years now and had a huge impact on our performance and the pressures our legacy system placed on other internal teams.

Due to the joys that are large corporate organisations, the use of the latest version of Spring and especially AspectJ are not allowed by the standards and redtape, but that allows me to write a whole new neat version without infringing on too many IP issues :) and publish it here.

Full source code, maven pom and eclipse files are available for download:
Source Code
or through SVN:
MethodCacheAspect trunk

In the project there are 3 main java files of interest:
MethodCacheAspect.java
CacheKeyStrategy.java
DefaultCacheKeyStrategy.java

The CacheKeyStrategy interface came about when I noticed that even though objects were "equal" there were 2 separate items in ehcache. This was because of 2 issues actually, order of items and just using .toString() to build up a key for ehcache. Not wanting to enforce the order or method of generating a key, I created a the interface and a default implementation. If future users want to handle it any different all they need to do is implement it and specify the implementation in Spring. I have 2 custom implementations in my live project, but for this blog posting I have just created a simple default.

note:I removed all JavaDoc just to save space, the downloadable code has docs.

package javaitzen.spring.interceptors;

public interface CacheKeyStrategy {

    String generateKey() throws IllegalStateException;

    String classForStrategy();

    void setObject(final Object object);
}


Below is the simple default key generator, just for convenience, that does a Collections.sort and uses the objects' hashcode method.

package javaitzen.spring.interceptors;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.aspectj.lang.ProceedingJoinPoint;

public class DefaultCacheKeyStrategy implements CacheKeyStrategy {

    private Logger logger = Logger.getLogger(DefaultCacheKeyStrategy.class.getName());
    private ProceedingJoinPoint pjp;

    public DefaultCacheKeyStrategy() {
    }

    public DefaultCacheKeyStrategy(final ProceedingJoinPoint pjp) {
        this.pjp = pjp;
    }

    public String generateKey() throws IllegalStateException {

        String targetName = pjp.getSignature().getDeclaringTypeName();
        String methodName = pjp.getSignature().getName();
        Object[] arguments = pjp.getArgs();

        return getCacheKey(targetName, methodName, arguments);
    }

    private String getCacheKey(final String targetName, final String methodName, final Object[] arguments) {
        StringBuilder sb = new StringBuilder();
        sb.append(targetName).append(".").append(methodName);

        for (Object arg : arguments) {
            if (arg instanceof Map< ?, ? >) {
                sb.append(expandMap((Map) arg));
            } else if (arg instanceof Collection< ? >) {
                sb.append(buildBuffer((Collection< ? >) arg));
            } else {
                sb.append(".").append(arg.hashCode());
            }
        }

        logger.log(Level.INFO, "Cache key is: [" + sb.toString() + "]");
        return sb.toString();
    }

    private StringBuilder buildBuffer(final Collection< ? > values) {
        StringBuilder bob = new StringBuilder();
        Collections.sort((List< ? >) values, new Comparator< Object >() {
            public int compare(final Object arg0, final Object arg1) {
                String thing1 = arg0.toString();
                String thing2 = arg1.toString();
                return thing1.compareTo(thing2);
            }
        });
        for(Object o : values){
            if (o != null) {
                bob.append(".").append(o.hashCode());
            }            
        }
        return bob;
    }

    private StringBuilder expandMap(final Map< ?, ? > args) {
        if (args == null) {
            return new StringBuilder();
        }

        List< Object > values = new ArrayList< Object >();
        for (Entry< ?, ? > entry : args.entrySet()) {
            values.add(entry.hashCode());
        }
        return buildBuffer(values);
    }

   public void setObject(final Object invocation) {
        this.pjp = (ProceedingJoinPoint) invocation;
    }

    public String classForStrategy() {
        return "*";
    }
}


Now for the juicy bit, the aspect checks if there are any cache key strategies compares types with information from the ProceedingJoinPoint, it extracts the relevant information and generates a key. It then checks if there is value for that key in the cache and returns that value if relevant, else it executes the method with "pjp.proceed()" and caches the result, so that for the next time the same method is called with the same parameters it can just be returned from the cache. I cant remember the exact numbers but with implementing this between our legacy system and another team we cut our daily calls to that teams web service from something like 65000 to around 6000.

package javaitzen.spring.interceptors;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MethodCacheAspect {

    private Logger logger = Logger.getLogger(MethodCacheAspect.class.getName());
    private Cache cache;
    private CacheKeyStrategy defaultKeyStrat;
    private List< CacheKeyStrategy > keyStrategies = new LinkedList< CacheKeyStrategy >();

    public void setCacheKeyStrategies(final List< CacheKeyStrategy > cacheKeys) {
        this.keyStrategies = cacheKeys;
    }

    public void addCacheKeyStrategy(final CacheKeyStrategy cacheKey) {
        this.keyStrategies.add(cacheKey);
    }

    public Object aroundAdvice(final ProceedingJoinPoint pjp) throws Throwable {

        Object[] arguments = pjp.getArgs();
        Object result;
        StringBuilder cacheKey = new StringBuilder();
        defaultKeyStrat = new DefaultCacheKeyStrategy(pjp);

         if (!keyStrategies.isEmpty()) {
            logger.log(Level.INFO, "Have a Key Strategy to use...");
            for (CacheKeyStrategy strat : keyStrategies) {
                if ((arguments != null) && (arguments.length != 0)) {
                    logger.log(Level.INFO, "Have Arguments...");
                    for (Object arg : arguments) {
                        if (Class.forName(strat.classForStrategy()).isInstance(arg)) {
                            strat.setObject(arg);
                            logger.log(Level.INFO, "Using Strategy...");
                            cacheKey.append(strat.generateKey());
                        }
                    }
                }
            }
        }

        if (cacheKey.length() == 0) {
            logger.log(Level.INFO, "Using Default...");
            cacheKey.append(defaultKeyStrat.generateKey());
        }
        Element element = cache.get(cacheKey.toString());

        // not in cache
        if (element == null) {
            result = pjp.proceed();
            if (result != null && !(result instanceof Serializable)) {
                throw new RuntimeException("[" + result.getClass().getName() + "] is not Serializable");
            }
            logger.log(Level.INFO, ">>> caching result - " + cacheKey);
            element = new Element(cacheKey.toString(), (Serializable) result);
            cache.put(element);
        } else {
            logger.log(Level.INFO, ">>> returning result from cache");
            return element.getValue();
        }

        return result;
    }

    public Cache getCache() {
        return cache;
    }

    public void setCache(final Cache cache) {
        this.cache = cache;
    }

}


In the application context below you will notice execution(* theBusinessMethod(..)). That only attaches the aspect for theBusinessMethod, in my test class in the downloadable source.

Just for quick reference here are a couple more examples from the Spring docs:
The execution of any public method:
execution(public * *(..))
The execution of any method with a name beginning with "set":
execution(* set*(..))
The execution of any method defined by the AccountService interface:
execution(* com.xyz.service.AccountService.*(..))
The execution of any method defined in the service package:
execution(* com.xyz.service.*.*(..))
The execution of any method defined in the service package or a sub-package:
execution(* com.xyz.service..*.*(..))

Application Context:



 

 
  
   
  
 

 
  
   
   
  
 

 
 
  
   classpath:javaitzen/spring/interceptors/ehcache.xml
   
  
  
 

 
 
  
   
  
  
   testCache
  
 





And finally the ehcache.xml:

 
 

 


Friday, June 4, 2010

Google, just doing it a little better than everyone else.

I log onto my gmail this morning...

Get this nice red warning on top saying there is unusual activity on my account ... I open it..



Arrghhh!!!...
So... signout the other sessions... think of new ultra secure password... change World or Warcraft password... change Google account password...

Praise Google.

Thursday, June 3, 2010

java.sql.Connection JDK 1.5 and 1.6 Compatibility Issue

A certain former work colleague of mine found this just before skipping the country to work for some evil IT empire... not to mention any names... but it starts with an "O" and ends with a "racle".

Today I opened the project again and there is a little Red X in my IDE which gives me a strange tick and psychotic tendencies.

java.sql.Connection was implemented into a verbose connection to get more information for unit tests.

Problem is he was running 1.6 jdk ... (our environment is still on 1.5) our hudson build machine is running 1.6, building as 1.5, our maven pom is set to 1.5, and most the developers are using 1.5...

If you have 1.6 the compiler complains about:


The type VerboseConnection must implement the inherited abstract method Connection.createArrayOf(String, Object[])
The type VerboseConnection must implement the inherited abstract method Connection.createBlob()
The type VerboseConnection must implement the inherited abstract method Connection.createClob()
The type VerboseConnection must implement the inherited abstract method Connection.createNClob()
The type VerboseConnection must implement the inherited abstract method Connection.createSQLXML()
The type VerboseConnection must implement the inherited abstract method Connection.createStruct(String, Object[])
The type VerboseConnection must implement the inherited abstract method Connection.getClientInfo()
The type VerboseConnection must implement the inherited abstract method Connection.getClientInfo(String)
The type VerboseConnection must implement the inherited abstract method Connection.isValid(int)
The type VerboseConnection must implement the inherited abstract method Connection.setClientInfo(Properties)
The type VerboseConnection must implement the inherited abstract method Connection.setClientInfo(String, String)
The type VerboseConnection must implement the inherited abstract method Wrapper.isWrapperFor(Class)
The type VerboseConnection must implement the inherited abstract method Wrapper.unwrap(Class)


So you implement the above methods and there are no little red X's and all is good in the world... Then someone then opens it up in 1.5:


NClob cannot be resolved to a type
SQLClientInfoException cannot be resolved to a type
SQLClientInfoException cannot be resolved to a type
SQLXML cannot be resolved to a type
The import java.sql.NClob cannot be resolved
The import java.sql.SQLClientInfoException cannot be resolved
The import java.sql.SQLXML cannot be resolved

Wednesday, June 2, 2010

Ahhh convenience...

Ok this may seem a bit mundane, but I have used Spring for a couple years now.. and only today by the chance discovered:
and thanks to MyEclipse 8.5 / Maven integration that automatically downloads Java docs and source code on Ctrl + click.

and yes it has been there since 2.0 ...

"This is a convenience method to load class path resources relative to a
given Class." - Spring Java Docs

public ClassPathXmlApplicationContext(String path, Class clazz) throws BeansException {
  this(new String[] {path}, clazz);
 }

I can't say how many times I have typed out something like:
new ClassPathXmlApplicationContext("com/some/really/long/config/package/config.xml");

but that now just becomes:
new ClassPathXmlApplicationContext("config.xml", TheClassIamIn.class);

Popular Posts

Followers