Tuesday, June 15, 2010

Spring Recipes blog - Chapter 5

Ok - read chapter 5. It's introduces classic spring AOP. Starts from the ground level.

I'm just going to run and comment the code, in the interests of time.

Ok, imported and changed the compiler version to 1.5. Now, lets run main.

Ok, problem. It needs c:/spring-framework-2.5/lib/log4j/log4j-1.2.14.jar, which is reference by the classpath, does not exist.

I know he mentioned something about this. Did he say where to get it from? On page 139, he says it's located in lib/log4j directory of the spring installation. It probably has a different number - let's check.

Yep, log4j-1.2.15.jar

Change in eclipse -

run it...

2010-06-15 07:03:58 INFO LoggingAroundAdvice:15 - The method add() begins with [1.0, 2.0]
1.0 + 2.0 = 3.0
2010-06-15 07:03:58 INFO LoggingAroundAdvice:20 - The method add() ends with 3.0
2010-06-15 07:03:58 INFO LoggingAroundAdvice:15 - The method sub() begins with [4.0, 3.0]
4.0 - 3.0 = 1.0
2010-06-15 07:03:58 INFO LoggingAroundAdvice:20 - The method sub() ends with 1.0
2010-06-15 07:03:58 INFO LoggingAroundAdvice:15 - The method mul() begins with [2.0, 3.0]
2.0 * 3.0 = 6.0
2010-06-15 07:03:58 INFO LoggingAroundAdvice:20 - The method mul() ends with 6.0
2010-06-15 07:03:58 INFO LoggingAroundAdvice:15 - The method div() begins with [4.0, 2.0]
4.0 / 2.0 = 2.0
2010-06-15 07:03:58 INFO LoggingAroundAdvice:20 - The method div() ends with 2.0
2010-06-15 07:03:58 INFO LoggingAroundAdvice:15 - The method kilogramToPound() begins with [10.0]
10.0 kilogram = 22.0 pound
2010-06-15 07:03:58 INFO LoggingAroundAdvice:20 - The method kilogramToPound() ends with 22.0
2010-06-15 07:03:58 INFO LoggingAroundAdvice:15 - The method kilometerToMile() begins with [5.0]
5.0 kilometer = 3.1 mile
2010-06-15 07:03:58 INFO LoggingAroundAdvice:20 - The method kilometerToMile() ends with 3.1

Ok.


Now the commented programs...

package com.apress.springrecipes.calculator;

// need app context
import org.springframework.context.ApplicationContext;
// using classpath version to locate beans.xml
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

public static void main(String[] args) {

// get the context
ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");

// get the caculator from the context
ArithmeticCalculator arithmeticCalculator =
(ArithmeticCalculator) context.getBean("arithmeticCalculator");

// invoke methods
arithmeticCalculator.add(1, 2);
arithmeticCalculator.sub(4, 3);
arithmeticCalculator.mul(2, 3);
arithmeticCalculator.div(4, 2);

// do same for calculator
UnitCalculator unitCalculator =
(UnitCalculator) context.getBean("unitCalculator");
unitCalculator.kilogramToPound(10);
unitCalculator.kilometerToMile(5);
}
}

// interface

package com.apress.springrecipes.calculator;

public interface ArithmeticCalculator {

public double add(double a, double b);
public double sub(double a, double b);
public double mul(double a, double b);
public double div(double a, double b);
}

// implementation
package com.apress.springrecipes.calculator;

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

public double add(double a, double b) {
double result = a + b;
System.out.println(a + " + " + b + " = " + result);
return result;
}

public double sub(double a, double b) {
double result = a - b;
System.out.println(a + " - " + b + " = " + result);
return result;
}

public double mul(double a, double b) {
double result = a * b;
System.out.println(a + " * " + b + " = " + result);
return result;
}

public double div(double a, double b) {
if (b == 0) {
throw new IllegalArgumentException("Division by zero");
}
double result = a / b;
System.out.println(a + " / " + b + " = " + result);
return result;
}
}

// this is the hand-built version - you don't really need to know this,
// since spring provides implementations.

package com.apress.springrecipes.calculator;

// java reflection classes
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// array class
import java.util.Arrays;

// use logging and log factory
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


// invocation handler implements invoke
public class CalculatorLoggingHandler implements InvocationHandler {


// create the proxy, passing in the target class
public static Object createProxy(Object target) {

// use reflection's Proxy; needs the classes class loader, interfaces
// returns the logging handler which wraps the target and acts as a proxy
// for each of it's interfaces methods.
// you can now use this to do stuff around the methods
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new CalculatorLoggingHandler(target));
}

// getting the log class
private Log log = LogFactory.getLog(this.getClass());


// inject the target
private Object target;

public CalculatorLoggingHandler(Object target) {
this.target = target;
}

// here's the invoke;
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// Log the method beginning with the method name and arguments.
log.info("The method " + method.getName() + "() begins with "
+ Arrays.toString(args));

// Perform the actual calculation on the target calculator object by calling
// Method.invoke() and passing in the target object and method arguments.
Object result = method.invoke(target, args);

// Log the method ending with the returning result.
log.info("The method " + method.getName() + "() ends with " + result);
return result;
}
}

I'm actually not seeing where the "invoke" method gets called, but main class shows how to get the proxy by calling the create proxy. So, maybe you call it
by saying arithematicCalculator.add

because you are actually calling that on the dynamic proxy. That's got to be it.

Ok, moving on.

// this is the same as above, but instead of logging, it's calling
// it's own validation method on each of the arguments.


package com.apress.springrecipes.calculator;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class CalculatorValidationHandler implements InvocationHandler {

public static Object createProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new CalculatorValidationHandler(target));
}

private Object target;

public CalculatorValidationHandler(Object target) {
this.target = target;
}

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
for (Object arg : args) {
validate((Double) arg);
}
Object result = method.invoke(target, args);
return result;
}

private void validate(double a) {
if (a < 0) {
throw new IllegalArgumentException("Positive numbers only");
}
}
}

// this is just your implementation of AfterReturningAdvice

package com.apress.springrecipes.calculator;

import java.lang.reflect.Method;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.AfterReturningAdvice;

public class LoggingAfterAdvice implements AfterReturningAdvice {

private Log log = LogFactory.getLog(this.getClass());

public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
log.info("The method " + method.getName() + "() ends with "
+ returnValue);
}
}

// your implementation of method return advice

package com.apress.springrecipes.calculator;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.apache.commons.logging.Log;

ackage com.apress.springrecipes.calculator;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.ThrowsAdvice;

public class LoggingThrowsAdvice implements ThrowsAdvice {

private Log log = LogFactory.getLog(this.getClass());

public void afterThrowing(Method method, Object[] args, Object target,
IllegalArgumentException e) throws Throwable {
log.error("Illegal argument " + Arrays.toString(args) + " for method "
+ method.getName() + "()");
}
}

// your implmentation of methodBeforeAdvice

import org.apache.commons.logging.LogFactory;
import org.springframework.aop.MethodBeforeAdvice;

public class LoggingBeforeAdvice implements MethodBeforeAdvice {

private Log log = LogFactory.getLog(this.getClass());

public void before(Method method, Object[] args, Object target)
throws Throwable {
log.info("The method " + method.getName() + "() begins with "
+ Arrays.toString(args));
}
}

// your implementation of ThrowsAdvice

ackage com.apress.springrecipes.calculator;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.ThrowsAdvice;

public class LoggingThrowsAdvice implements ThrowsAdvice {

private Log log = LogFactory.getLog(this.getClass());

// note you have to specify each exception to capture
public void afterThrowing(Method method, Object[] args, Object target,
IllegalArgumentException e) throws Throwable {
log.error("Illegal argument " + Arrays.toString(args) + " for method "
+ method.getName() + "()");
}
}


// Your implementation of MethodInterceptor
// don't forget to call "proceed"
// note how it lets you do before, after *and* exceptions all in one class



package com.apress.springrecipes.calculator;

import java.util.Arrays;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LoggingAroundAdvice implements MethodInterceptor {

private Log log = LogFactory.getLog(this.getClass());

public Object invoke(MethodInvocation methodInvocation) throws Throwable {
log.info("The method " + methodInvocation.getMethod().getName()
+ "() begins with "
+ Arrays.toString(methodInvocation.getArguments()));
try {
Object result = methodInvocation.proceed();
log.info("The method " + methodInvocation.getMethod().getName()
+ "() ends with " + result);
return result;
} catch (IllegalArgumentException e) {
log.error("Illegal argument "
+ Arrays.toString(methodInvocation.getArguments())
+ " for method " + methodInvocation.getMethod().getName()
+ "()");
throw e;
}
}
}


// beans.xml

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

// define calculator with the impl class

class="com.apress.springrecipes.calculator.ArithmeticCalculatorImpl" />

// same for unit calculator
class="com.apress.springrecipes.calculator.UnitCalculatorImpl" />

// set up logging around advice - see implementation above
class="com.apress.springrecipes.calculator.LoggingAroundAdvice" />

// define pointcuts, i.e. where to call logging around advisor, by method name
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">


add
sub





// by regex

class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">


.*mul.*
.*div.*





// by aspect j syntax
class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">

execution(* *.*To*(..))






// use autoproxy default advisor to create a proxy for each bean in the IoC
// container. This is the simplest, but must powerful, because the advisors will
// be called for every method

Friday, June 11, 2010

Chapter 4 - advanced Spring - continued again...

Ok, I just read a couple of more sections in chapter 4. One on internationalization, the other on event publishing. Next up is property editing. Ok, just read property editing. Basically, you specify a bean to handle date editing, using a CustomDateEditor provided by spring. Then, you set up a CustomEditorConfigurer that takes a map of each of the beans specified for property handling. Then you can just specify the date and value in you application bean params.

Ok. Finished reading chapter four. Now let's try to run the code.

What do we have?

C:\tutorials\springrecipes\Chapter04\Shop

Let's import the project.

Bam. Got it.

Bunch of stuff. Errors on annotation. Set the java level to 5.

Wow, works on first try:

***********************
* Welcome to My Shop! *
***********************
Shopping cart 1 contains [AAA 2.5, CD-RW 1.5]
Shopping cart 2 contains [DVD-RW 3.0]
A shopping cart costing 4 dollars has been checked out at 6/11/10 8:07 AM.
Checkout event [4.0, Fri Jun 11 08:07:44 EDT 2010]
Product ranking from Sat Sep 01 00:00:00 EDT 2007 to Sun Sep 30 00:00:00 EDT 2007

Let's comment the code...

Ok, done.

Chapter 4 - advanced Spring - continued!

Ok. It's 6:45 am. I will work on this stuff until about 9. Let's go!

We left off at 4-10. Bean Post-processors. These sandwich the initialization callback step.

To get to it, add a "StorageConfig" interface (this is a name of your own choosing). Make it implement any methods you will need to add in prep/post initialization. In this case, it's "getPath".

You want "Cashier" to check for an existing path, so this is where you implement "StorageConfig".

So, in your class that implements post processor, you need to implement BeansPostProcessor. This calls will get an instance of all beans. It has a before and after method, both of which must return the bean, even if it doesn't do anything (postProcessBeforeInitialization, postProcessAfterInitialization).

In this case, the before initialization process checks if the object is of a a type of StorageConfig; if so, it will check to make sure a path exists.

Just declare post processing class in the xml. Spring will see it's interface and make sure to pass every bean to it.

There's an issue if you use the annotation method for initialization. It will be called before the post processing because the CommonAnnotationBeansPostProcessor has a higher priority than the BeansPostProcessor. So you need to have you path checking post processor implement the PriorityOrdered interface to beat the CommonAnnotationBeans post processor to the punch.

Then assign it a priority of 0 in you configuration, and you will beat the CommmonAnnotationsBeanPostProcessor to the punch. You can use the "context:annotaton-config" tag instead of the full declaration of CommonAnnotationBeanPostProcessor".

Wednesday, June 9, 2010

Chapter 3 - advanced Spring

Ok, starting to read chapter 3 of Spring Recipes.

The first section is a basic description of using injection for properties and constructors - nothing advanced about that!

4-2 introduces the "factory-method" keyword in a bean declaration. It tells Spring that class is being created by a static method, and the class name given is the factory class name, not the class produced.

4-3 adds the "factory-bean" configuration keyword. It invokes what the author refers to as an "instance factory method". The basic concept is that the instances of beans are created and stored in a map, which itself is stored in a ProductCreator class. The ProductCreator class is created with xml that uses "property name" to represent the map, "map" to describe the map, "entry key" to describe the key to the map, and the "bean class=..." to describe the beans that go into map.

Then it instantiates the beans by specifying the "factory-bean" in the "bean id=... xml statement, along with the "factory-method" to identify the method used to get the bean.

Whew, that was long-winded.

4-4. Spring has this concept of a "Spring Factory Bean". Keep in mind that we are dealing with a superclass Product and subclass of Battery and Disc. The superlcass's "setPrice" can be called on whichever subclass of Product is set for the DiscountFactoryBean. So, when you create your subclass instances, you will be first setting the discount value, and the createInstance method of the AbstractFactoryBean implementation will do whatever it needs with the discount (setPrice in this case) when Spring calls the "createInstance", which you set up in the AbstractFactoryBean implementation (i.e. the DiscountFactoryBean"). Note that you also implement the product.getType method, which is a mere formality.

4-5. This talks about ways to configure beans that are statically defined in a class. You use the FieldRetrievingFactoryBean in the "bean id=..." statement, and then you give a "property name="static-fied", and then a value of the class anem and the static field name in a "value" property.

Another way is to skip the property declaration, and in the bean id give the static fields class and name, while specifying the class as FieldRetrievingFactoryBean.

Another way is to ad the "util" schema declaration to the root element, then use the "util:constatnt" tag, where you give the id as, e.g., aaa, and then specify a "static-field" as the static field defining the instance.

God, this is too dull for words. Ok, let's shorten up the explanations a bit.

4-6 is about the "PropertyPathFactoryBean". If an inner bean is a property of another class, it lets you pull that property and declare it as a bean in its own right. You can also combine the bean name and its property as the bean id, giving the class as the PropertyPathFactoryBean.

Naturally, in 2.x they've made a tag to cover this, if you add the "util" xsd.

You just have to say "util:property-path id="bestSeller" path="productRanking.bestSeller"

4-7 is about bean scopes. The trick here is to understand that the default is singleton when you get a bean. In other words, if you do a "context.getBean("shoppingCart"), it will always return the same instance. This means that the "getBean" is not doing a "new". To make the getBean do a "new", you have to give it a "scope="prototype" in the bean declaration".

My God. This stuff is deadly. One more...

Ok. This is good to know. Spring lifecycle management has an "afterPropertiesSet" and "destroy" method that are implementations of the "IntializingBean" and "DisposableBean" properties, respectively. So, if you need to open a file and close a file, do them in these methods. The book points out that it's helpful in particular in the case of "afterPropertiesSet", due to the fact that the properties that are set are the file an path name.

However, a better approach is to use the "init-method" and "destroy-method" in the bean id= declaration. That way, you don't have to implement the interfaces, just to call the "open-file" and "close-file" methods.

The third way (there's always a third way) is to use @PostConstruct and @PreDestroy" annotations. Just make sure to include "bean class="CommonAnnotationBeanPostProcessor" in your beans.xml.

Or, of course you can add the "context" shcema definition, and then just use the "context:annotation-config" tag in your bean somewhere. Actually, the second seems better.

Oh my good sweet Lord. There is more. There is always more. Just think - if I learn this stuff, and learn it fast, I can get a new job. A better job. I'll have more options. Spring is the BIG technology. Learn it!

4-9 This is about making beans aware of the container. For example, take the BeanNameAware interface. Say you name your bean "cashier1" (as the bean id).

So, you implement "setBeanName" as this.name = beanName in that method. So, now your bean has a name property which it pulled from the configuration.

As a piece of trivia, this is called after the bean properties are set, but before the initialization callbacks.

Dear God in Heaven - this is excruciating. C'mon you can do it. Think about the job. Think about the job.

4-10. The ultra-exciting topic of Create Bean Post Processors. My God.

Stop.

Friday, June 4, 2010

Spring recipes - Chapter 3 - continued.

It turns out there's a lot of ground covered in Chapter 3. While 2.0 covers XML spring injection and a little bit of autowiring, they went kind of crazy with it in 2.5. A lot of it involves annotations. So for example, if you type "@autowired" in your source code, you don't have to specify the injection in your XML - spring will do it automatically. If you use "@Component", you don't even have to specify the beans in your XML - it will look them up automatically. They even have subdivided the @component into @Dao, @Service and @Controller, so you can specify which ones are included. the @Required annotation just looks up and does the injection for you.

Well, there's a lot more in there, but I'm focused for now on getting the code working. While I got the first one going on, I'm have trouble importing the second chapter 3 project. Let's give it another go.

Well, it let me import something - but - ok, now I get it. it says "some project were hidden because they were already in the workspace directory.

Let me try renaming them - I don't wont' to throw out code I modified.

Ok, the problem was, it couldn't import the second one because the first had already been named "sequence".

So, in the previous blog, I already covered the SequenceGenerator project.

Now, let's see if we can make the SequenceService project work.

As always, set java compliance to 1.5. (For some reason, it doesn't like 1.6)

Jump right in and run "main" (right click and run as java application).


30100000A
30100001A


Ok, here's what main does:

public class Main {

public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");

SequenceService sequenceService =
(SequenceService) context.getBean("sequenceService");

System.out.println(sequenceService.generate("IT"));
System.out.println(sequenceService.generate("IT"));
}
}


Ah, ok. I saw this in the book. It stored the sequence service in a hash map, I think, so it could have multiple sequence services? Then it used its chosen service to track the the last sequence in another hash map.

Let's take a look at beans.xml



<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">

<context:component-scan base-package="com.apress.springrecipes.sequence">
<context:include-filter type="regex"
expression="com\.apress\.springrecipes\.sequence\..*Dao.*" />
<context:include-filter type="regex"
expression="com\.apress\.springrecipes\.sequence\..*Service.*" />
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
<context:component-scan>


Ok, there is the old component scan. So, there are no beans, specified, no injection parameters passed - just basically telling spring to find anything name DAO or Service, and do its thing.


So, what in these sources?

ackage com.apress.springrecipes.sequence;

public class Sequence {

private String id;
private String prefix;
private String suffix;

public Sequence(String id, String prefix, String suffix) {
this.id = id;
this.prefix = prefix;
this.suffix = suffix;
}

public String getId() {
return id;
}

public String getPrefix() {
return prefix;
}

public String getSuffix() {
return suffix;
}

public void setId(String id) {
this.id = id;
}

public void setPrefix(String prefix) {
this.prefix = prefix;
}

public void setSuffix(String suffix) {
this.suffix = suffix;
}
}


This is just a bean with a few properties.


package com.apress.springrecipes.sequence;

public interface SequenceDao {

public Sequence getSequence(String sequenceId);
public int getNextValue(String sequenceId);
}

An interface that gets the above named Sequence instance based on an id.

package com.apress.springrecipes.sequence;

import java.util.HashMap;
import java.util.Map;

import org.springframework.stereotype.Repository;

@Repository("sequenceDao")

public class SequenceDaoImpl implements SequenceDao {

private Map sequences;
private Map values;

public SequenceDaoImpl() {
sequences = new HashMap();
sequences.put("IT", new Sequence("IT", "30", "A"));
values = new HashMap();
values.put("IT", 100000);
}

public Sequence getSequence(String sequenceId) {
return sequences.get(sequenceId);
}

public synchronized int getNextValue(String sequenceId) {
int value = values.get(sequenceId);
values.put(sequenceId, value + 1);
return value;
}
}

This implements the DAO. The Sequence instance it stores provides its own id, a suffix and a prefix.

This annotation:

@Repository("sequenceDao")

Gives it a component name (remember there are three types of Components, including repository, and you can specify the name, "sequenceDao", which otherwise would have been given the name sequenceDaoImpl, I think).

Finally, we have SequenceService:

package com.apress.springrecipes.sequence;

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

@Service("sequenceService")
public class SequenceService {

private SequenceDao sequenceDao;

@Autowired
public void setSequenceDao(SequenceDao sequenceDao) {
this.sequenceDao = sequenceDao;
}

public String generate(String sequenceId) {
Sequence sequence = sequenceDao.getSequence(sequenceId);
int value = sequenceDao.getNextValue(sequenceId);
return sequence.getPrefix() + value + sequence.getSuffix();
}
}

The autowired sequenceDao property will inject the SequenceDaoImpl from above.

So, getting back to main,

SequenceService sequenceService =
(SequenceService) context.getBean("sequenceService");

System.out.println(sequenceService.generate("IT"));
System.out.println(sequenceService.generate("IT"));

So, it goes into sequenceService, and calls generate("IT")

Which does this:


Sequence sequence = sequenceDao.getSequence(sequenceId);
int value = sequenceDao.getNextValue(sequenceId);
return sequence.getPrefix() + value + sequence.getSuffix();


First, it gets the "Sequence" instance stored by the key "IT".

We know from the constructor that it consists of this:

sequences.put("IT", new Sequence("IT", "30", "A"));

The prefix is 30, the suffix is "A".

Then, it gets the value. We know the first value is values is 100000 from the constructor

values = new HashMap();
values.put("IT", 100000);

So the call to getNext value (stored under "IT" in the values map) is 1000000. It then increments and restores it.

public synchronized int getNextValue(String sequenceId) {
int value = values.get(sequenceId);
values.put(sequenceId, value + 1);
return value;
}


And of course add the prefix and suffix:

return sequence.getPrefix() + value + sequence.getSuffix();


So, these two calls

System.out.println(sequenceService.generate("IT"));
System.out.println(sequenceService.generate("IT"));

increment these two values:

30100000A
30100001A

The main point of this one was that by using componenent-scan and its annotations, you don't have to name the beans in beans.xml - you just use regex to identify which classes/packages you want included in the component scan, and use annotation do identify the components. The dependency injection is achieved by the @Autowired annotation.

And, voila! That's it for chapter 3.

Spring recipes - Chapter 3

Ok, I tried to import Chapter 3 last night, but ran into an issue. First of all, there were 2 chapter 3 projects, so some kind of conflict - only one was imported, I think.

Secondly, the code that I looked at didn't exactly look like the code from the book.

I've been thinking about, instead of importing the projects, creating them myself. This will give me a better feel for how to create project from scratch. So let's try that.

Ok, I just created a project called "MySequence1". First, let's create the SequenceGenerator class by copying from the books code. Ok, there's two projects, but only one has SequenceGenerator, and that's the "SequenceDeclaration" project.

Rats. It's annotated code, not the same. It looks like I might have downloaded the code from the next version of the book. Let's try to find the 2.5 version.

Hmm...it was published with 2008 version. Well, he must have changed it. Blah.

Ok, - let's just see if we can run it.

Change java to 1.5 in eclipes.

Run the java main class.

Ok, I got this output:

20100604100000-0005-0010-0020
20100604100001-0005-0010-0020

So, what happened?

Here's main:

package com.apress.springrecipes.sequence;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");

SequenceGenerator generator =
(SequenceGenerator) context.getBean("sequenceGenerator");

System.out.println(generator.getSequence());
System.out.println(generator.getSequence());
}
}

It got the context from beans.xml.

Here's beans.xml.

beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.5.xsd">







class="com.apress.springrecipes.sequence.SequenceGenerator">





class="com.apress.springrecipes.sequence.ReverseGenerator" />









class="com.apress.springrecipes.sequence.DatePrefixGenerator">




5
10
20



So main instantiates the Sequence Generator.



Parent?

class="com.apress.springrecipes.sequence.SequenceGenerator">






What does mean? It's trying to inject suffixes...

Suffixes takes a list:

public void setSuffixes(List suffixes) {
this.suffixes = suffixes;
}

Where are the Integers?

Here:


5
10
20



So the is how it was identified.

Plus, besides having the list of suffixes, it will have





an "initial" value of "100000"

Ok, now this:

System.out.println(generator.getSequence());
System.out.println(generator.getSequence());

Which generated this:

20100604100000-0005-0010-0020
20100604100001-0005-0010-0020

Here the method

public synchronized String getSequence() {
StringBuffer buffer = new StringBuffer(); // create a string buffer

buffer.append(prefixGenerator.getPrefix()); // see below - it's the date
buffer.append(initial + counter++); // initial is 100000; counter inits to 0;

// so now we have 20100604100000

// use a decimal formatter to setup a format of four zeros.
// iterate through them,
// Append the formatted suffixes.

DecimalFormat formatter = new DecimalFormat("0000");
for (int suffix : suffixes) {
buffer.append("-");
buffer.append(formatter.format(suffix));
}
return buffer.toString();
}

Ok, where's the prefix generator coming from?

Looks like this in the source:

@Resource(name = "datePrefixGenerator")
private PrefixGenerator prefixGenerator;


Heres the xml:

class="com.apress.springrecipes.sequence.DatePrefixGenerator">



Since there's not setter, obviously the @Resource annotation filled in

private PrefixGenerator prefixGenerator;

from the XML.


Ok, if you look back to some of my comments in the source code, they were like this:


public synchronized String getSequence() {
StringBuffer buffer = new StringBuffer(); // create a string buffer

buffer.append(prefixGenerator.getPrefix()); // see below - it's the date
buffer.append(initial + counter++); // initial is 100000; counter intits to 0;

// so now we have 20100604100000

// use a decimal formatter to setup a format of four zeros.
// iterate through them,
// Append the formatted suffixes.

DecimalFormat formatter = new DecimalFormat("0000");
for (int suffix : suffixes) {
buffer.append("-");
buffer.append(formatter.format(suffix));
}
return buffer.toString();
}


And voila. That's Lesson 3, part 1.

Thursday, June 3, 2010

Test driving the Plug-in

Let's try out our newly installed spring plug-in. Did it work?

Yes! Under new, project, other, "Spring" appears. Instead of creating a new project, though, I'm going to convert the chapter 2 hello world project I imported to a spring project. Just right-click it and select "add spring project nature".

Ok, you have to do some monkeying around to actually see the spring view of a bean xml file.

First, right click project to get properties. There is a "Spring" section. On the right panel, click "enable project specific settings". There is a "Beans Support" entry. Click on that, and there you will see a whole "Beans Support" panel on the right. Just add your "beans.xml" to it, and you will now be able to right click on it and "show in" Spring Explorer.

Actually, that's a bit disappointing. It pretty much shows a node/value view of the bean. I pretty much prefer editing the vanilla text. But it may come in handy.

Also, if you right click on the root bean, you can see a dependency graph, which shows the beans in the xml and the beans which have been injected into them as a dependency graph.

The context-assist ctrl+space for property names in bean.xml didn't work for me...

And the bean property validation didn't work. Oh, well.

The main point is I got preview of a what and how to use the Spring plug-in. I don't think it will make a major difference, but I may be wrong.

Up and running

I'm basing my lessons now on Gary Mak's Spring Recipes (the 2008 version). The reason I've decided to do my coding from this book is that, after checking Amazon reviews, it's clear that this book is very much oriented toward working code examples out of the box. After some of the horrific tutorials I've gone through in the past, this is a major, as in major, priority.

So, I've downloaded the code from the website, imported the project into eclipse, added the two recommended beans to the classpath, and voila - I got hello world to work on the first try. I had to update the compiler to Java 1.5, actually.

I won't duplicate what the book says. I'm into chapter 2, now, which includes setting up the spring plugin on eclipse. Eclpse 4 does things a bit differently, but I found a good site which lets you know how to do it in Eclipse 3.4:

http://java.dzone.com/news/springide-using-spring-eclipse.

Nope, doesn't work. I get a message about repository not available.

http://springide.org/updatesite gets a 404 when I enter it. Where is it, then?

Google spring ide. After some futzing around, lets try this:

http://dist.springframework.org/release/IDE

Ok, great - a lot of items show up.

A group install give some kind of error message.

Let's try installing just the core.

Well, there was an error message, but it did walk me through the install and ask to restart.

Core isn't checked, but it does have this string next to it: 2.3blahblah.release.

Let's try installing the extensions. That seemed to go ok. No error message, and a restart.

Good. They all have the 2.3.0.20091217blahblah.RELEASE.

Let's go for integrations - only optional, but there are only two.

That gets a cannot complete request.

Let's try the resources, then.

Good, it comes right up to the review and confirm display. Now the restart.

Ok, what's left?

Wow - nothing - they all have the 2.3.0.20091217blahblah.RELEASE marked next to them.

Maybe they are all installed, somehow.

Let's wrap it up and move onto the next post.


First post - Hello world!

In an effort to keep up to date with what is really my core skill, Java Enterprise Development, I've decided to teach myself Spring. I've read Spring in Action by Craig Walls, and found it to be a great introduction to many of the Spring topics, and very readable.

I've also done a tutorial on Hibernate at http://docs.jboss.org/hibernate/core/3.3/reference/en/html/tutorial.html#tutorial-webapp-deploy. This one contained a *lot* of bugs, but I managed to get through them all - with a little help from my internet denizens who had suffered the slings and arrows if this same tutorial before.

It was also fun to use Maven, which is clearly more awesome and cool than ant.

A bit about me - I've been programming for many years, going back to Cobol and mainframes. Over the years I've learned a *bunch* of languages and done a *bunch* projects. I have Sun Certified Programmer, Developer, and Architect certifications. At the time I got the Architect Certification, EJB 2.0 was the big deal. However, I modeled the front end on Apache Struts, so I have a pretty good idea about how Struts and MVC works.

Nowadays, clearly, Spring is king - thus my new blog. More posts to come.