Spring

From Elvanör's Technical Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Spring is a JEE application framework. One of Spring main interest is to glue several components together, taking care of configuration and dependencies between those components. It also serves as a base framework for Grails.

Configuration

Web Deployment Descriptor Configuration

  • Due to the breaking of request path elements by the servlet container, it seems you cannot use a directory path mapping to Spring dispatcher servlet. Something like "/spring/*" is apparently not supported, since the servlet path is not what is expected by the dispatcher. Look here for more information.
  • A recommended configuration is either to use extension mapping (*.html) or map everything to the SpringDispatcher by using the "/" mapping. This prevents the use of the default servlet though, so be careful.

Logging Configuration

  • The best is to rely on Spring Log4J configuration which easily allows you to have per web-application logging. Just copy and adapt the following code into your web deployment descriptor:

<context-param> <param-name>log4jConfigLocation</param-name> <param-value>classpath:log4j_analysis.properties</param-value> </context-param> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener>

Per Environment Configuration

  • If you wish to have per environment configuration like in Grails, you have basically two options:
    • Configuration files are present in the web application (probably in the WAR), and in this case it is the responsability of the build system to build the WAR with the correct files;
    • Or you can externalise your configuration files, and then you have to maintain those files in your production server and development environment (those files probably won't be commited to SVN).
  • With the first option, you can easily build the WAR with an Ant script. For development, with Eclipse you can somehow configure the deployment of the application to the temporary Tomcat folder too.

Configuring Beans

Annotations

  • Annotations allow you to do a lot of things without touching the XML configuration files. In particular it allows you to autowire beans easily. You cannot however provide annotations for beans that are "outside" your code (eg, beans coming from libraries like Hibernate's SessionFactory...). You cannot just because you don't have a class to write these annotations on; so instead for those "core beans" you should rely on XML configuration.
  • If you have one annotated bean that's registered via autoscanning, but you still wish to configure its properties via dependency injection, you must refer to it in your XML file via the id attribute. The bean will be named based on its classname (first letter will be converted to lower case).

Hibernate session factory & Data Sources

  • The class to use to configure an Hibernate session factory is org.springframework.orm.hibernate3.LocalSessionFactoryBean. If you use Hibernate annotations, you must set configurationClass to "org.hibernate.cfg.AnnotationConfiguration". You can also use a subclass of LocalSessionFactoryBean to configure annotated classes (which allows you to not use an XML file at all, but configure everything through Spring).
  • The data source can of course be configured with Hibernate XML configuration, but it is recommended to configure it as a Spring bean and set the session factory dataSource property to it. This allows any Spring configured data source object to be used, not only Hibernate connection providers.

Using the ApplicationContext directly

  • If one of your bean implements ApplicationContextAware, it can be injected the application context. In turn, this bean can call applicationContext.getBean to retrieve a Spring managed bean directly. This can be very useful with some prototyped scoped beans - although it may be better to use a factory pattern in this case.
  • Using getBean() is not recommended, as it couples the code to the Spring framework and forces you to somehow manage manually the dependencies. It may be better to change your design so that everything may be managed by Spring.

Bean Factories

  • Creating a FactoryBean allows you to override the default behavior of Spring when managing beans. Don't confuse this with creating your own object factory, which can be a normal Spring managed bean.

Factory Pattern

  • If you have lots of small and prototype objects, you should create a Factory to create those objects. The factory can be a singleton and thus managed as a Spring bean - every component that needs to have access to a prototype object should get the factory injected, and create the object from the factory.

Controller Layer

Mapping requests to controller actions

  • If using annotations, your controller classes should not extend a base controller class like MultiActionController. As soon as you use the @Controller annotation, Spring correctly configures and recognizes your class as a controller. You also don't need to write a handleRequest() method; any action name can be used - see below.
  • If you use the recommended latest setup (annotations and CoC), you don't need to specify a mapping for the controller. You also don't need to specify @RequestMapping annotations for the actions in a multiaction controller. They will also be mapped by convention (although the mapping is actually done by a specific class, so you can customize this).
  • However you usually need to use an annotation for your controller actions in order to use whatever signature you want. If you don't, Spring recognizes as actions only the methods having a very specific signature, which is quite constraining. If you use an annotation, you can use whatever signature you need and powerful features such as binding of request parameters to method arguments. So usually you would just use:
@RequestMapping(method = RequestMethod.GET)
  • It does not seem very easy to map a default action for the URL "/controller/" (without any action name). There seems to be a bug in Spring, where if you have several actions the last one may be used as a default for all URLs not matched, but this does not happen all the time...

Data Binding

  • Spring supports JavaBeans PropertyEditor classes. A PropertyEditor allows you to implement custom data binding for a complex property type.
  • By convention, classes named after a target type (class), with an added "Property" suffix and in the same package get automatically registered as custom property editors. If this is not enough, there are many ways to register in Spring custom property editors. See the Spring reference documentation.
  • Generally, custom property editors will implement setAsText() and getAsText() methods. It may be useful to call getValue() or setValue() to get the underlying value or set it. Be careful, when used through Grails data binding (this may also be the case with pure Spring), getValue() will return a non-null value even when you are creating the object. Eg, a new object is created via the default constructor.

Persistence Layer and Data Access

DAO

  • Data Access Objects are just a layer that will allow you to isolate your DB access code to manage your entities. Later you could in theory switch to another data access framework just by implementing a new DAO implementation. This would allow to switch from Hibernate to JDBC, Ibatis or whatever.
  • In practice if you stick to Hibernate, the interest of DAOs is not very clear to me (Grails has no DAOs for example). There are several ways to write your actual Hibernate code. You can use HibernateTemplate (a Spring class), or make your DAOs extend HibernateDaoSupport. I currently prefer to work directly with the Hibernate APIs, as suggested in this section of Spring documentation. You then write usual Hibernate code, and obtain your session via getCurrentSession() - the transactions will be setup automatically via Spring.

Hibernate Configuration

  • Sample Hibernate configuration for Spring:
	<bean id="ranungwenDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/ranungwen?&useUnicode=true&characterEncoding=UTF-8" />
		<property name="username" value="ranungwen" />
		<property name="password" value="ranungwen_dev" />
	</bean>
	<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="dataSource" ref="ranungwenDataSource" />
		<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
		<property name="configLocation" value="classpath:/com/shoopz/ranungwen/domain/hibernate.cfg.xml" />
		<property name="hibernateProperties">
			<map>
				<entry key="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect">
				</entry>
				<entry key="hibernate.hbm2ddl.auto" value="update">
				</entry>
			</map>
		</property>
	</bean>
	<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>
  • Be very careful that your Hibernate.cfg.xml file should *not* have the following property set for the session factory:
 <property name="current_session_context_class">thread</property>

This is because you want to use the Spring transactional support and bind sessions to Spring session management, not to threads. So remove this setting or you will have exceptions about transactions not being created. See this bug for more information.

Transaction Support

General

  • You can specify transactional behavior for a public method or a whole class with the @Transactional annotation. You can specify the properties of the transaction (eg, its propagation and isolation levels, etc) via the annotation. You only need to have in your XML configuration file the <tx:annotation-driven /> tag.
  • It's important to be aware than for Spring managed transactions, rollback occur only if an unchecked exception is thrown. If a checked exception is thrown, the transaction does not rollback.
  • When the default propagation level of REQUIRED is used, this means that a @Transactional method called from an outer @Transactional method won't recreate a transaction - the same transaction will be used. Thus you can basically declare your DAO simple methods as transactional, and your complex service methods also as transactional, even if the latter call the DAO methods (there will be no performance penalty for this presumably).
  • Defining good transaction boundaries is hard, but very important. Be careful to not do too much in a transaction; however operations should still be grouped as much as possible.

Proxying

  • Normally, proxying (which is needed for declarative transactions) happens via JDK proxying for interfaces (classes that implement at least one interface), and via cglib for classes not implementing interfaces. Note that this means that you cannot cast a JDK proxied object back to its "normal" class, you will get an exception. You may just use the interface methods.
  • Forcing the use of cglib everywhere is possible, although apparently not on a per class basis (eg, it must be global). The setting is:
<tx:annotation-driven proxy-target-class="false" />
  • You can also use AspectJ for proxying, I did not yet investigate this mechanism.

Hibernate

  • When using Hibernate, you should use HibernateTransactionManager as a transaction manager. This allows you to use declarative transactions (although you can still use programmatic transactions relying on Spring transaction infrastructure). Under the hood, it will use Hibernate's transaction support with JDBC. Normally you can switch to a JTA transaction manager (and still use Hibernate) with only configuration changes (org.springframework.transaction.jta.JtaTransactionManager).
  • Hibernate JDBC transaction support is called local; JTA transaction support is called global as it is managed by the application server.

Service Beans

  • Be careful that if you define the scope of a service to be an HTTP request, but you call this service from a singleton scoped service, the same instance of the called service will be used all the time (so it's not request bound anymore). This is logical since injection happens only once for singletons, but can cause hard to track bugs. Use caution.

Locale Support

  • Spring supports different mechanisms for determining the current locale (the locale associated to the current thread). Grails uses the cookie mechanism (with a fallback to the HTTP request Accept-Language header).

Obtaining the current HTTPServletRequest

  • Obtaining this object in a domain object should generally considered as a bad practice, as it couples the object to an HTTP request. However this can be done using the following code (this works at least in Grails):
import org.springframework.web.context.request.RequestContextHolder as RCH
def request = RCH.currentRequestAttributes().currentRequest

Data Binding

  • Spring supports custom data binding for certain classes through the use of PropertyEditors.