Friday, January 16, 2009

Getting started with Spring Framework - II

This is the second part in my effort at getting to know Spring Framework. Please refer to the first part on the same blog on 15 Jan 2009.

5. Security Aspect.
In continuing with the previous Logging Aspect, this adds a Security Aspect with a simple introduction to Acegi Security and how it can be used to store user information. For more details on Acegi Security, please refer to [9].

First we will reuse the DBDataReaderUsingPropertyHolder class we created in exercise (3). Create the SecurityAspect class with an 'init' method that is executed before any method in class 'DBDataReaderUsingPropertyHolder' is executed i.e with AOP advice='before'. This init method adds a user to Acegi SecurityContext. We then add another method to validate this user, 'checkSecurity()'. This method, which is executed when the method is executed i.e with AOP Advice='around', retrieves ths user ID and prints it out.
package org.spring.learn.class1;

import org.acegisecurity.Authentication;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.userdetails.User;
import org.acegisecurity.userdetails.UserDetails;
import org.aspectj.lang.ProceedingJoinPoint;

//@Aspect
public class SecurityAspect {

/** this method initializes the Acegi Security Context with a Principal.
*/
public void init() {
System.out.println("Security Aspect: In init method");
SecurityContext secCtx = SecurityContextHolder.getContext();

GrantedAuthority[] gas = new GrantedAuthorityImpl[] {new GrantedAuthorityImpl("ADMIN")};
UserDetails userDetails =
new User("samba", "stc", true, true, true, true, gas);

// InMemoryDaoImpl userDetailSvc = new InMemoryDaoImpl();
// UserMap um = new UserMap();
// um.addUser(userDetails);
// userDetailSvc.setUserMap(um);

UsernamePasswordAuthenticationToken unpat =
new UsernamePasswordAuthenticationToken(userDetails, userDetails);
secCtx.setAuthentication(unpat);
}

// just defining here did not work. Investigate later. @Around("org.spring.learn.class1.DBDataReaderUsingPropertyHolder.readData()")
/** this method checks if Principal exists in the Acegi Security Context
* and prints the user name. This method MUST be executed with 'Advice=around'
* since it intakes the ProceedingJoinPoint param.
*/
public Object checkSecurity(ProceedingJoinPoint call) throws Throwable {

SecurityContext secCtx = SecurityContextHolder.getContext();
Authentication auth = secCtx.getAuthentication();

if (auth != null) {
Object obj = auth.getPrincipal();

String userName = null;
if (obj instanceof UserDetails) {
userName = ((UserDetails) obj).getUsername();
} else {
userName = obj.toString();
}

System.out.println("Authenticated the user....= " + userName);
} else {
System.out.println("Authentication object not initialized.");
}

return call.proceed();
}

}

The Application Context XML file to execute the above would be:
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

<context:property-placeholder location="resource/config/**/db.properties"/>

<!-- db access -->
<bean id="dbDataReaderUsingPropertyHolder" class="org.spring.learn.class1.DBDataReaderUsingPropertyHolder">
<property name="dataSource" ref="datasource"/>
</bean>

<bean id="datasource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</bean>

<!-- Security Aspect -->
<bean id="securityAspectBean" class="org.spring.learn.class1.SecurityAspect"/>

<aop:config>
<aop:aspect id="securityAspect" ref="securityAspectBean">
<aop:before method="init" pointcut="within(org.spring.learn.class1.DB*)"/>
<aop:around method="checkSecurity" pointcut="within(org.spring.learn.class1.DB*)"/>
</aop:aspect>
</aop:config>
</beans>

Now execute the main method in DBDataReaderUsingPropertyHolder to view the result.

6. Bean scope:
Spring provides 5 different scopes that a bean can be defined under - singleton, prototype, request, session, global session. The last 3 are specific to web aware ApplicationContext's.

Singleton: One instance only is created per container for that particular bean definition in the ApplicationContext file. The difference with Spring's singleton when compared to GoF singleton pattern is - Spring's scope is per container per bean while GoF's is per classloader. Default scope for bean's defined in Spring. A Session less bean.

Prototype: Multiple instances are created for the same bean definition everytime a request is made through ApplicationContext for the object. A stateful bean. To use Prototype, scope must be explicitly defined for the bean. One major difference in life cycle management here is, Spring does not invoke the destroy callback for a Prototype bean. Once the bean is initialized and handed to the client, Spring has no knowledge of the Prototype bean. There is a custom way of enabling this. For details refer to [10].

Please note that this is just an intro and there are more details such as the behavior of a Singleton bean injected with a Prototype Bean, the HTTP session scoped bean handling etc.. For all the details on scope's please refer to [11].
The simple test class is as below. In the main method, for the Singleton we invoke the getBean(..) multiple times and check if the hash values of the objects are identical and the result is true since the same object is returned. You can also observe that the println statement in the constructor is execute the first time only. For the Prototype, we invoke the getBean(..) multiple times and compare the returned object hash values. The result is false since every time we invoke getBean(..) a new object is returned. Here you can observe that the println statement in the constructor is executed everytime the get method is invoked.

package org.spring.learn.class1;

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

/** This test provides an overview of inherently supported scopes for beans in
* Spring - primarily Singleton and Prototype. It also depicts the added flexibility
* Spring provides in the ability to define a bean class under two different scope's.
*/
public class BeanScopeTest {

public BeanScopeTest() {
System.out.println("Bean Scope test....");
}

public static void main(String args[]) {
ApplicationContext appCtx = new FileSystemXmlApplicationContext("resource/appCtx-BeanScope.xml");
/*--- Singleton ---*/
//the log in constructor should be printed
BeanScopeTest singleton = (BeanScopeTest) appCtx.getBean("singletonBeanScope");
//log in constructor should not be printed.
BeanScopeTest singleton1 = (BeanScopeTest) appCtx.getBean("singletonBeanScope");
//output = true
System.out.println("Are the two singleton beans's equal? = " + (singleton == singleton1));

/*--- Prototype ---*/
//the log in constructor should be printed
BeanScopeTest prototype = (BeanScopeTest) appCtx.getBean("prototypeBeanScope");
//the log in constructor should be printed
BeanScopeTest prototype1 = (BeanScopeTest) appCtx.getBean("prototypeBeanScope");
//output = false
System.out.println("Are the two Prototype Beans equal? = " + (prototype == prototype1));

//3 other bean scopes supported by Spring are specific to Web-Aware ApplicationContext.
}
}

The Application Context XML file defines two different bean ID's for the same bean class. One bean ID has default scope which is Singleton while the other has Prototype.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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.0.xsd">

<bean id="singletonBeanScope" class="org.spring.learn.class1.BeanScopeTest"/>

<bean id="prototypeBeanScope" class="org.spring.learn.class1.BeanScopeTest" scope="prototype"/>

</beans>


Ref:
[9] http://www.acegisecurity.org/guide/springsecurity.html
[10] http://static.springframework.org/spring/docs/2.5.x/reference/beans.html#beans-factory-extension-bpp
[11] http://static.springframework.org/spring/docs/2.5.x/reference/beans.html#beans-factory-scopes

Thursday, January 15, 2009

Getting started with Spring

Across various technical discussions, articles, forums and even in job postings, there is always a mention about Spring framework. Given this hype around it, I decided to get acquainted with the framework to understand its pros and cons. In this process I documented the individual steps I took in understanding the concepts of the framework as detailed below. These are only a few I have got to so far and as and when I get time I will update the same with additional features. I have avoided details about specific class objects and would recommend you to refer to articles in the References section.

Although Spring installable (ZIP file) does come with samples, I decided to not get started with the samples since they are tied to the web framework. But I did refer to the samples for some of the tasks such as connecting to the DB and would recommend the same for everyone once you understand the basic concepts.

Requirements to get started:
  • A good understanding of Java.
  • An understanding of IoC [1] and Dependency Injection [2]. Refer to the corresponding references for a good description on these patterns.
  • Access to Spring API documentation and other tutorials that provide basic idea about some of the concepts used here in.
  • An assumption that you use an IDE since no execution scripts such as batch files etc. are provided. Knowledge in configuring the classpath in the IDE also is required. For eg: In Eclipse you can configure the classpath in the 'Java Build Path' section in Properties window for the specific project. Right click on the project and open the properties.
To execute all the examples you will require 'spring.jar' primarily and depending on what you are executing, corresponding jars will need to added to the classpath.

  1. Hello World: This example is the simplest case that shows how method injection can be done using Spring. The application of this feature can vary depending on the system being built. But some of the common applications could be setting default properties on beans, for creating DataSource. Some of these will be demonstrated as we slowly step into Spring features.

    Start with the simple use case Hello World. Start by creating a simple class HelloSpringBean with a property - 'message'. Handcode or generate the setters and getters using your IDE. To test Spring's functionality, write a main method that creates the ApplicationContext object, get an instance of HelloSpringBean and invoke the getter method without having to invoke the setter method.

    The ApplicationContext object is a Spring interface that is instantiated by providing an implementation of this interface with the location of the bean resource file. In our case since the bean resource is an XML on the file system we use the FileSystemXmlApplicationContext class. This class acts like a service or bean locator for those defined in the XML file. For further details please refer to [8].

    package org.spring.learn.class1;

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

    public class HelloSpringBean {

    private String message;

    public void setMessage(String msg) {
    this.message = msg;
    }

    public String getMessage() {
    return this.message;
    }

    public static void main(String[] args) {
    ApplicationContext ctx = new FileSystemXmlApplicationContext("resource/applicationContext.xml");
    System.out.println("Completed loading of the ApplicationContext");

    //simple bean with setter injection
    HelloSpringBean bean = (HelloSpringBean) ctx.getBean("helloSpringBean");
    //invoke the get method.... and check the output. Note. the set method has not been invoked.
    System.out.println(bean.getMessage());
    }
    }

    The required applicationContext.xml file to execute the above is shown below. Please ensure this file is dropped in the directory 'resource' which is a peer to the 'src' directory.

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    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.0.xsd">

    <!-- simple setter injection -->
    <bean id="helloSpringBean" class="org.spring.learn.class1.HelloSpringBean">
    <property name="message" value="Hello, Spring"/>
    </bean>
    </beans>

    With regarding to when the bean is created, the interesting observation is that the bean is created when the ApplicationContext object is created and not when ctx.getBean(..) method is invoked. To enable lazy loading, you can use the attribute 'lazy-init' as shown for the below bean definition.
    <bean id="helloSpringBean" class="org.spring.learn.class1.HelloSpringBean" lazy-init="true">
    <property name="message" value="Hello, Spring"/>
    </bean>
    Execute the main method in the above HelloSpringBean class to see the result.


  2. Accessing a Database: To execute this example you require
    Apache's commons-dbcp.jar to be in the classpath along with other jar files required for executing the example (1).

    To access a database in Spring, we will configure a DataSource in the applicationContext.xml file and access the Databaseto retrieve all the columns in a table.

    The below class DatabaseDataReader is instantiated by Spring framework and the DataSource object injected into the constructor. Note the difference here is constructor injection when compared to previous example of setter injection. In the readData() method, Spring's JdbcTemplate class is used since it abstracts out the boiler plate code such as creating DB statements, result sets and their corresponding destruction.
    We then execute the queryForList which intakes a SQL query with no arguments. The reason to use the queryForList method signature as used below is, a PreparedStatement will be created by underlying JdbcTemplate instead of the normal Statement. Please ensure the SQL query matches your DB table definitions. The output of this is a List of Map objects. For further details please refer to javadoc API.

    package org.spring.learn.class1;

    import java.util.Collection;
    import java.util.List;
    import java.util.Map;

    import javax.sql.DataSource;

    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    import org.springframework.jdbc.core.JdbcTemplate;

    public class DatabaseDataReader {

    private DataSource datasource;

    public DatabaseDataReader(DataSource ds) {
    this.datasource = ds;
    System.out.println("Datasource set= " + this.datasource);
    }

    public boolean readData() {
    JdbcTemplate template = new JdbcTemplate(datasource);
    List mapList = template.queryForList("select * from EMP", (Object[]) null);

    for (Map m : mapList) {
    Collection val = m.values();
    System.out.println("Value= " + val);
    }

    return true;
    }

    public static void main(String args[]) {
    ApplicationContext ctx = new FileSystemXmlApplicationContext("resource/applicationContext.xml");

    DatabaseDataReader dbReader = (DatabaseDataReader) ctx.getBean("databaseDataReader");
    dbReader.readData();
    }
    }

    To execute the above, you require applicationContext.xml file as shown below. Please ensure this file is dropped in the directory 'resource' which is a peer to the 'src' directory.


    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    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.0.xsd">

    <bean id="datasource"
    class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
    <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/>
    <property name="username" value="spring"/>
    <property name="password" value="spring"/>
    </bean>

    <!-- To directly use Oracle's classes.
    <bean id="datasource" class="oracle.jdbc.pool.OracleConnectionCacheImpl" >
    <property name="connectionPoolDataSource">
    <bean class="oracle.jdbc.pool.OracleConnectionPoolDataSource">
    <property name="URL" value="jdbc:oracle:thin:@localhost:1521:ORCL"/>
    <property name="user" value="spring"/>
    <property name="password" value="spring"/>
    </bean>
    </property>
    <property name="maxLimit" value="10"/>
    <property name="minLimit" value="2"/>
    </bean>
    -->
    </beans>

    Now execute the main method in DBDataReader to view the result.
    Instead of Apache's common DBCP API, you could also use DriverManagerDataSource. But the drawback is the connections are not pooled. For pooled connections use Apache's DBCP. [3].


  3. Using Properties instead of hardcoded DB config: To avoid hardcoding values in the context XML file, you can use external Properties to be injected dynamically into the Properties.
    One way to define the location of a properties file is to use
    <context:property-placeholder location="classpath:jdbc.properties"/>
    This is particulary useful when the bean class that requires properties does not inherently support a mechanism to provide Properties. This class is identical to the exercise 2, except that setter injection is used. The primary difference in this exercise is the usage of placeholder property in the context XML.

    package org.spring.learn.class1;

    import java.util.Collection;
    import java.util.List;
    import java.util.Map;

    import javax.sql.DataSource;

    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    import org.springframework.jdbc.core.JdbcTemplate;

    /**1. Use PropertyPlaceHolderConfigurer to insert DB properties dynamically from
    * an external properties file instead of hardcoding the values into context XML
    * file.
    * 2. Use setter injection instead of constructor injection in IoC container.
    *
    */
    public class DBDataReaderUsingPropertyHolder {

    private DataSource datasource;

    public DBDataReaderUsingPropertyHolder() {
    }


    /** This method is invoked when this class is instantiated which can be either
    * when ApplicationContext is created or by lazy loading i.e when getBean
    * method is invoked.
    * @param ds
    */
    public void setDataSource(DataSource ds) {
    this.datasource = ds;
    System.out.println("Datasource set= " + this.datasource);
    }

    public boolean readData() {
    JdbcTemplate template = new JdbcTemplate(datasource);
    List mapList = template.queryForList("select * from EMP", (Object[]) null);

    for (Map m : mapList) {
    Collection val = m.values();
    System.out.println("Value= " + val);
    }

    return true;
    }

    public static void main(String args[]) {
    ApplicationContext ctx = new FileSystemXmlApplicationContext("resource/appCtx-PropertyHolder.xml");
    System.out.println("Initialized ApplicationContext");

    DBDataReaderUsingPropertyHolder dbReader =
    (DBDataReaderUsingPropertyHolder) ctx.getBean("dbDataReaderUsingPropertyHolder");
    dbReader.readData();
    }
    }

    To execute the above, the context XML needs to declare a place holder for DB properties as shown below. The property-placeholder element is part of a new schema which is also defined below.


    <?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <!-- a property holder for all the objects within this application context definition. Spring framework will automatically replace the below defined variables -->
    <context:property-placeholder location="resource/config/**/db.properties"/>

    <!-- db access -->
    <bean id="dbDataReaderUsingPropertyHolder" class="org.spring.learn.class1.DBDataReaderUsingPropertyHolder">
    <property name="dataSource" ref="datasource"/>
    </bean>

    <bean id="datasource"
    class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${user}"/>
    <property name="password" value="${password}"/>
    </bean>
    </beans>

    Using the above variables and property-placeholder, Spring provides one way to separate out dynamic configuration i.e config that is susecptible to user changes such as the need to deploy the same application in different systems environments or user changes for application such as switching an application from test to production by using a switch.

    You can also configure a JNDI resource within the application context file. Below is an example using Oracle.
    <Resource name="jdbc/rac" auth="Container"
    type="oracle.jdbc.pool.OracleDataSource"
    description="Oracle Datasource"
    factory="oracle.jdbc.pool.OracleDataSourceFactory"
    url="jdbc:oracle:thin:@(description=(address_list=(address=(host=rac1)(protocol=tcp)(port=1521))(address=(host=rac2)(protocol=tcp)(port=1521))(load_balance=no))(connect_data=(service_name=racdb1)))"
    user="pet"
    password="clinic"
    ONSConfiguration="nodes=rac1:6200,rac2:6200" //if RAC is required
    fastConnectionFailoverEnabled="true" //if FCF is required.
    connectionCachingEnabled="true"
    connectionCacheName="PETCLINIC"/>


    ---- sample db.properties file ----------
    driver=oracle.jdbc.driver.OracleDriver
    url=jdbc:oracle:thin:@localhost:1521:orcl
    user=orcl
    password=orcl


  4. AOP in Spring: Aspect oriented programming in Spring provides a powerful way to intercept a call to a method - before, after, around, the method's execution which in AOP terms is called 'Advice'. There are other 'Advice''s too, but I will not get into those details and you can refer to [6] for complete details. The "Advice' provides the logic that is to be executed while when to execute this logic is provided by what is called 'PointCut'. A 'PointCut' is a combination of 'JoinPoints' which is described for a java method at which point the Aspect 'Advice' must be executed. In short, consider 'PointCut or JoinPoint' to be when, while 'Advice' to be what to be executed. For more details please refer to below [6]. Simple examples for the same are provided in [7].

    Below is a simple example for Logging. The below is an Aspect that when called, prints out the JoinPoint details before executing the java method that is configured to execute the log() method below. The method jp.proceed() will give the program handle back to the java method and when done, the program handle comes back to print another log statement. The below method log() takes the param ProceedingJoinPoint object. This object can only be used when the 'Advice=around' as you may note in the below Application Context XML file.

    package org.spring.learn.class1;

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


    public class LoggingAspect {

    /** this method can only be executed with Advice=around since the arg ProceedingJoinPoint is used. */
    public Object log(ProceedingJoinPoint jp) throws Throwable {

    System.out.println("LoggingAspect: entering log method= " + jp.toShortString()
    + " WITH PARAMS= " + jp.getArgs());
    Object point = jp.proceed();

    System.out.println("LoggingAspect: exiting log method= " + jp.toShortString()
    + " WITH RETURN AS= " + point);

    return point;
    }

    /** since this method does not take any JoinPoint arg, it can be executed before or after the method execution */
    public void log1 () {
    System.out.println("LoggingAspect: exiting log1 method= ");
    }
    }


    The application context XML file is as below.

    <?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <context:property-placeholder location="resource/config/**/db.properties"/>

    <!-- db access -->
    <bean id="dbDataReaderUsingPropertyHolder" class="org.spring.learn.class1.DBDataReaderUsingPropertyHolder">
    <property name="dataSource" ref="datasource"/>
    </bean>

    <bean id="datasource"
    class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${user}"/>
    <property name="password" value="${password}"/>
    </bean>

    <!-- Logging Aspect -->
    <bean id="loggingAspectBean" class="org.spring.learn.class1.LoggingAspect"/>

    <aop:config>
    <aop:aspect ref="loggingAspectBean">
    <!-- point cut expression - defines expression that is to be matched for the execution of the Aspect -->
    <aop:pointcut id="logPointCut" expression="execution(* org.spring.learn.class1.DB*.*(..))"/>
    <!-- advice for pointcut i.e when the pointcut is reached the code to be executed. -->
    <aop:before pointcut-ref="logPointCut" method="log1"/>

    <!-- using 'within' JoinPoint expression and 'around' advice'
    <aop:pointcut id="logWithinPointCut" expression="within(org.spring.learn.class1.DB*)"/>
    <aop:around pointcut-ref="logWithinPointCut" method="log"/>
    -->
    </aop:aspect>
    </aop:config>
    </beans>

Ref:
[1] http://martinfowler.com/bliki/InversionOfControl.html
[2] http://martinfowler.com/articles/injection.html#InversionOfControl
[3] http://www.devx.com/Java/Article/21665/0/page/4
[4] http://www.parleys.com/display/PARLEYS/Home#talk=4358355;slide=18;title=How%20to%20build%20Enterprise%20Java%20applications%20with%20Spring
[5] http://www.java2s.com/Code/Java/Spring/SetupDataSourceforOracle.htm
[6] http://static.springframework.org/spring/docs/2.5.x/reference/aop.html
[7] http://www.javaworld.com/javaworld/jw-01-2007/jw-0105-aop.html?page=3
[8] http://static.springframework.org/spring/docs/2.5.x/reference/