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