Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kenneth A. Kousen - Making Java Groovy - 2014.pdf
Скачиваний:
50
Добавлен:
19.03.2016
Размер:
15.36 Mб
Скачать

Inline scripted beans

185

INFO: deposit called with [100] on mjg.spring.entities.Account(id:8, balance:100.0)

INFO: withdraw called with [100] on mjg.spring.entities.Account(id:9, balance:100.0)

Jun 28, 2013 12:03:29 PM

INFO: getBalance called with [] on mjg.spring.entities.Account(id:9, balance:0.0)

The JoinPoint can be used to get more information, but those are AOP details rather than Groovy.

In both of these examples the aspect was provided in its own class. Spring provides an alternative, however, in the form of beans defined right in the bean definition file.

7.4Inline scripted beans

Another capability Spring provides to beans from dynamic languages is that they can be coded right inside the XML configuration.12

Here’s an example. The following sections can be used in a bean configuration file, as shown in the next listing.

Listing 7.20 Additions to bean configuration file for an inline scripted aspect

<lang:groovy id="aspectScript"> <lang:inline-script>

<![CDATA[

import org.aspectj.lang.JoinPoint import java.util.logging.Logger

class GroovyAspect {

Logger log = Logger.getLogger(GroovyAspect.getClass().getName())

def audit(JoinPoint jp) {

log.info "${jp.signature.name} on ${jp.target.class.name}"

}

}

]]>

</lang:inline-script> </lang:groovy>

<aop:config>

<aop:aspect ref="aspectScript">

<aop:before method="audit" pointcut="execution(* *.*(*))"/> </aop:aspect>

</aop:config>

The <inline-script> tag wraps the source code for the Groovy bean. I took the added step of wrapping the code in a CDATA section, so the XML parser will leave the Groovy source alone when validating the XML.

12I have to admit that in several years of using Spring and Groovy I’ve never found a compelling use case for inline scripted beans that couldn’t have been handled with regular classes. If you have one, please let me know.

www.it-ebooks.info

186

CHAPTER 7 The Spring framework

Rather than use annotations, this time the code is written as though it was any other bean. As a result I had to add the <config> element as well. As usual, an aspect is a combination of a pointcut and an advice. In this case the pointcut is contained in the <before> element, but this time it applies to every one-argument method in the system. The advice is the audit method in the aspectScript bean, which just prints the name of the method being invoked and the name of the object containing it.

The resulting output adds more lines to the console:

INFO: setOne on mjg.POJO

INFO: setTwo on mjg.POJO

INFO: setThree on mjg.POJO

The original motivation for inline scripted beans was that you could do as much processing as you liked in the script before releasing the bean.13 Now that Spring has moved to version 3.x, however, there are additional options for configuring beans.

7.5Groovy with JavaConfig

Spring introduced a third way to configure beans in version 3.0. Originally all beans were configured using XML. Then version 2.0 introduced annotations (assuming JDK 1.5 is available) like @Component, @Service, and @Repository and component scans that picked them up.

In version 3.0 Spring introduced a Java configuration option. Instead of defining all your beans in a central location in XML, or spreading annotations throughout the code base in Java, now you can define the beans in a Java class annotated with @Configuration. Inside the configuration file, individual beans are annotated with @Bean.

One of the advantages of this approach is that the configuration information is strongly typed, because it’s all written in Java. Another advantage, though, is that you’re now free to write whatever code you want, as long as you ultimately return the proper object.

Consider the following example. In the account manager example discussed previously, say I want to charge a processing fee once a month.14 To do so I create a class that processes accounts, called, naturally enough, AccountProcessor. I want the AccountProcessor to get all the accounts and charge each one a fee of one dollar.15

If I did this in the traditional way, I would inject the AccountDAO into the AccountProcessor. Then, in a processAccounts method, I would use the DAO to retrieve the accounts and charge the fee on each. With the Java configuration option, however, I have an alternative.

The following listing shows the AccountProcessor class, in Java this time.

13As I say, it’s a reach. The Spring docs suggest that this is a good opportunity for scripted validators, but I don’t see it.

14Gee, I feel more like a real banker already.

15It’s not much, but it’s a start.

www.it-ebooks.info

Groovy with JavaConfig

187

Listing 7.21 An account processor that debits each account by one dollar

package mjg.spring.services;

import java.util.List;

import mjg.spring.entities.Account;

public class AccountProcessor { private List<Account> accounts;

public void setAccounts(List<Account> accounts) { this.accounts = accounts;

}

public List<Account> getAccounts() { return accounts; }

public double processAccounts() { double total = 0.0;

for (Account account : accounts) { account.withdraw(1.0);

total += 1.0;

}

return total;

}

}

Instead of injecting the AccountDAO into the processor, I gave it a list of accounts as an attribute. The processAccounts method runs through them, withdrawing a dollar from each and returning the total. Without the dependency on the AccountDAO, this processor could be used on any collection of accounts from any source. This has the extra benefit of always retrieving the complete set of accounts from the DAO. Injecting the account list would initialize it when the application starts but not update it later.

So how does the collection of accounts get into my processor? The next listing shows the Java configuration file.

Listing 7.22 A Java configuration file that declares the AccountProcessor bean

package mjg.spring;

import mjg.spring.dao.AccountDAO;

import mjg.spring.services.AccountProcessor;

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class JavaConfig { @Autowired

private AccountDAO accountDAO;

@Bean

public AccountProcessor accountProcessor() { AccountProcessor ap = new AccountProcessor();

www.it-ebooks.info

188

CHAPTER 7 The Spring framework

ap.setAccounts(accountDAO.findAllAccounts());

return ap;

}

}

The @Configuration annotation indicates that this is a Java configuration file that defines beans for Spring. Each bean is defined with the @Bean annotation. The name of the method is the name of the bean, and the return type is the class for the bean. Inside the method my job is to instantiate the bean, configure it appropriately, and return it.

The implementation of a bean method can be as simple as instantiating the bean and returning it, setting whatever properties are needed along the way. In this case, though, I decided to autowire in the AccountDAO bean (which was picked up in the component scan) and then use the DAO to retrieve all the accounts and put them in the processor.

The next listing shows a Spock test to prove that the system is working. It relies on the embedded database again, which, as you may recall, configures three accounts.

Listing 7.23 A Spock test to check the behavior of the AccountProcessor

package mjg.spring.services

import mjg.spring.dao.AccountDAO;

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration import org.springframework.transaction.annotation.Transactional

import spock.lang.Specification

@ContextConfiguration("classpath:applicationContext.xml")

@Transactional

class AccountProcessorSpec extends Specification { @Autowired

AccountProcessor accountProcessor

@Autowired AccountDAO dao

def "processing test accounts should yield 3"() { given: def accounts = dao.findAllAccounts()

when: def result = accountProcessor.processAccounts()

then:

result == 3.0 accounts.every { account ->

account.balance.toString().endsWith "9"

}

}

}

Both the AccountProcessor and the AccountDAO beans are autowired into the test. The DAO is used to retrieve the accounts. Then, when the processor processes the accounts, three dollars are returned.

www.it-ebooks.info

Groovy with JavaConfig

189

The other test condition relies on the fact that the initial balance for each account is divisible by 10. Therefore, after subtracting one from each account, the updated balances should all end in the digit 9. It’s kind of kludgy, but it works.

The point of this exercise was to show that with the Java configuration option you can write whatever code you want to configure the bean before releasing it. There’s not much Groovy can add to that, though it’s worth proving that the Java configuration option works on a Groovy class as well.

Normally I wouldn’t use Spring to manage basic entity instances. Spring specializes in managing back-end services, especially those that would normally be designed as singletons. Spring beans are all assumed to be singletons unless otherwise specified. Still, you can tell Spring to provide a new instance each time by making the scope of the bean equal to prototype.

Listing 7.24 shows a Java (actually, a Groovy) configuration file, with a single bean definition of type Account called prototypeAccount. It uses the AccountDAO to generate a new bean each time a prototypeAccount is requested, essentially making Spring a factory for Account beans, all of which start with an initial balance of 100.

Listing 7.24 A Spring configuration file in Groovy as a factory for Accounts

package mjg.spring.config

import mjg.spring.dao.AccountDAO import mjg.spring.entities.Account

import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean

import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Scope

@Configuration class GroovyConfig {

@Autowired AccountDAO dao

@Bean @Scope("prototype") Account prototypeAccount() {

int newId = dao.createAccount(100.0) new Account(id:newId,balance:100.0)

}

}

The @Configuration and @Bean annotations are the same as their counterparts in the Java configuration file. The AccountDAO is autowired in as before. This time, though, the @Scope annotation is used to indicate that the prototypeAccount is not a singleton. The implementation uses the DAO to create each new account with the given balance and then populates an Account object with the generated ID.

To prove this is working properly, here is another Spock test in the next listing.

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]