Back | Next
May 28, 2013

MOAR HYPERMEDIA PAYPAL

Nice to see that Paypal are building more HATEOAS-friendly APIs. If you want to build a clean, HATEOAS-centric RESTful API, don't hesitate to check out Spring Data HATEOAS.

May 15, 2013

Dependency Injection with Dagger on Android

Limitations of Android Dependency Injection Solutions

A question that I often in my work as the Spring Developer Advocate at SpringSource is: How do I do dependency injection in Android? Spring for Android doesn't have a dependency injection story as there are a lot of restrictions to what an inversion-of-control (IoC) container can hope to achieve on Android.

First, Android controls all the important objects in an Android application including the Activity instances. Normally, Spring instantiates an object ("bean") -satisfying any constructor injections required - and then attempts to satisfy any injection sites on fields (field injection) and through methods (setter injection). When it's done, Spring hands back the fully configured reference and makes it available for injection in other beans. As Android creates the Activity instance - it's not possible to simply tell Android to use a bean configured with a dependency injection container - the only thing a DI container can do is handle injections after the Activity has been created. This implies some fragile state management and explicit dependencies on the dependency injection container that is akin to the sort of hacks required to use Spring inside of EJB 2.x beans from years ago. At a minimum - it requires invasive code in the heart of your business logic components.

Second, reflection has historically been very slow on Android (and any constrained environment, including the original PCs that debuted Java in 1994!), and a lot of the tried-and-true solutions for byte-code manipulation don't work nearly so well on Android as they should. A lot of this has improved in recent years, but nonetheless solutions that favor compile-time weaving or other solutions that minimize reflection are preferable.

Dagger

With all of these limitations in mind, I still personally like Dagger, from Square. Dagger provides a JSR 330 compliant dependency injection container (and so - in theory - you could run JSR 330-compliant Spring code on Android). Using it is fairly simple, except that it's a bit more invasive than I'm used to in Spring. It relies on a Java agent that changes the code at compile time, inserting code that eliminates the need for costly, or sometimes impossible, reflection.

A Dagger Module

The simplest way to get started with it is to create a module class. Let's look at an example:


import ...

// Dagger and JSR 330 APIs
import dagger.*;
import javax.inject.*;

// RestTemplate 
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.web.client.RestTemplate;


@Module(
 injects = {
  WelcomeActivity.class
 }
)
public class CrmModule {
  // ...
} 

You'll note a few things of interest already. This class is annotated with the @Module annotation. The @Module annotation is more or less like Spring's @Configuration annotation. It tells the container that this class contains the definition of beans, which themselves are annotated with @Provides, much like Spring's @Bean-annotated configuration annotation.

The injects attribute in Dagger's @Module annotation enumerates all the classes that may that may receive injections that *are not defined explicitly using an @Provider-annotated method in the module. I think. I'll be honest, I haven't completely figured this out. The nice thing is that if you get it wrong, you get a compile-time error! Handy!

As shown, we could start declaring fields or constructor arguments in our WelcomeActivity instance and annotating them with JSR 330's @Inject. When Dagger sees @Inject, it attempts to instantiate an instance of the bean using the default, no-args constructor and then inject it at the injection site.

However, not all beans can be provided this way. For example, if the type declared is an interface, then how's Dagger to know which implementation to provide? What if we only want one, and only one, instance (a singleton)? What if we want to inject values into a third party bean where it would be impractical to attempt to update the code with @Inject annotations? For all these scenarios, you can explicitly wire up the bean definitions using provider methods in your module, like this:


import ...

// Dagger and JSR 330 APIs
import dagger.*;
import javax.inject.*;

// RestTemplate 
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.web.client.RestTemplate;


@Module(
  injects = {
    WelcomeActivity.class
  }
)
public class CrmModule {
    private Crm application;
    private Context context;

    public CrmModule(Crm crm) {
        this.application = crm;
        this.context = this.application.getApplicationContext();
    }

    @Provides
    @InjectAndroidApplicationContext
    public Context provideApplicationContext() {
        return this.context;
    }

    @Singleton
    @Provides
    public RestTemplate provideRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().add(new MappingJacksonHttpMessageConverter());
        return restTemplate;
    }

    @Singleton
    @Provides // CustomerService is the interface, CustomerServiceClient is the implementation
    public CustomerService provideCustomerService(
             @InjectAndroidApplicationContext Context context, 
             RestTemplate restTemplate ){
        String baseUri = context.getString(R.string.base_uri);
        CustomerServiceClient customerServiceClient = new CustomerServiceClient( baseUri );
        customerServiceClient.setRestTemplate(restTemplate);
        return customerServiceClient;
    }

}

The @Provides-annotated methods work as expected: you construct an object in a method and the value returned is made available for consumption through injection in other beans by type(s, as it also creates a binding between any interfaces implemented by the bean. We use this to our advantage when we create the CustomerService bean.

Note that - just as with Spring's @Configuration class - you can declare provider methods with arguments and the container will attempt to provide a value for the argument based on the type of the argument. In some cases, there may be many types that satisfy the requirement and so you can use a @Qualifier-annotated meta-annotation, like @InjectAndroidApplicationContext, to specify which instance you want. Here, we annotate the Crm argument in the provideCustomerService method with @InjectAndroidApplicationContext. By using the same annotation at the provider method as well as at the site of the injection, we establish a unique binding.

Using the Android Application instance as an Application-Wide Global Context.

Our module class expects an instance of the current application's Application class. I've specified the class ....Crm for this application class in my AndroidManifest.xml through the android:name attribute in the manifest element. As I mentioned earlier, key components in an Android application, like the Application subclass, are instantiated, managed and destroyed by Android itself. Dagger can't instantiate the Application subclass and provide the reference for us. Instead, we need to insert Dagger in a place that it can capture a reference to that Application instance. We do this in our onCreate method in our Application instance.

import ...
import android.app.Application;
import dagger.ObjectGraph;

import java.util.*;

public class Crm extends Application {

    private ObjectGraph objectGraph;

    @Override
    public void onCreate() {
        super.onCreate();
        Object[] modules = getModules().toArray();
        objectGraph = ObjectGraph.create(modules);
    }

    protected List<Object> getModules() {
        return Arrays.<Object>asList(
                new CrmModule(this)
        );
    }

    public ObjectGraph getObjectGraph() {
        return this.objectGraph;
    }
}

Here, in the onCreate method we assemble the various module classes - including our CrmModule - that we want to instantiate and use it to create an objectGraph. Because Application subclasses provide an effective application-global singleton, it's convenient to stash a reference to the Dagger objectGraph in this class, as well. The objectGraph more or less corresponds to Spring's ApplicationContext. In our onCreate method we create the CrmModule passing in a reference to this which can then be used in a provide method in our module so that we can easily inject an Android application Application instance (which in turn provides a Context, which is a very useful sort of god object in Android applications) wherever we use Dagger for dependency injection.

Dependency Injection with Android Activity Instances and Other Android Components

Since we want to inject beans into our Activity subclasses, and Activity classes are managed by Android in the same way as Application instances, we need a similar hook into the Activity's lifecycle to let Dagger work its magic after the object's been created. For convenience, you'll probably have a common base-class where you can insert this behavior and then simply extend. Here is such a base-class.

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import ...Crm;

import javax.inject.Inject;

public class CrmBaseActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Crm crm = (Crm) getApplication(); 
        crm.getObjectGraph().inject(this);
    }
}

The code simply obtains the current Application instance (the Application subclass, Crm), which we know has a pointer to the current, stashed, ObjectGraph instance, and then uses the ObjectGraph#inject(Object) method to have Dagger scan the object given as an argument and satisfy any dependencies.

You'll use a similar approach when dealing with other Android components like Fragment classes.

Summary

This blog has demonstrated how to use Dagger in the context of Android. Dagger also works outside of Android, of course, but if you're working outside of Android, you're better off just using Spring. For another look at how to use Dagger, check out the Dagger introduction on the Square site which focuses on Dagger, generically.

April 24, 2013

Speaking at the Oakland JUG tonight at 6:30

Hi guys, tonight I'll be speaking at the Oakland JUG on Multi Client Development with Spring. The talk will be geared towards developing modern web applications that are REST-centric using Spring, Spring MVC, Spring Android, and Spring Mobile. If we have time, we could go into even more detail on other technologies like Spring Security and Spring Security OAuth. I hope to see you guys there as it figures to be a fun time! On a related note, I will have my pal Andy Piper in tow. He's a Cloud Foundry ninja, and I'm sure he'll have answers for people with questions!

April 14, 2013

Java Configuration with Spring Batch

In the first post in this series, I introduced Spring's Java configuration mechanism. There is the base configuration style, and - as we saw - there are annotations (like @EnableWebMvc) that turn on container-wide, and often conventions-oriented features. In the case of @EnableWebMvc, the annotation enables Spring MVC and turns on support for correctly exposing @Controller-annotated beans as HTTP endpoints. Java configuration APIs of the same style as @EnableWebMvc can often optionally implement interfaces that are used to contribute, change, or otherwise tailor the API. Most Spring projects today offer beta-or-better APIs that work in this same manner. In this post, we'll look at Spring Batch's Java Configuration API available in the upcoming 2.2 release.

Spring Batch provides a solution for batch processing. It lets you describe jobs, which themselves have steps, which read input from some source system (a large RDBMS dataset, a large CSV file or XML document, etc.), optionally process the input, and then write the output to a destination system like an RDBMS, a file, or a message queue.


@Configuration
@EnableBatchProcessing
public class BatchInfrastructureConfiguration   {

    @Bean
    public TaskScheduler taskScheduler() {
        return new ConcurrentTaskScheduler();
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource ds) {
        return new DataSourceTransactionManager(ds);
    }


    @Bean
    public DataSource dataSource(Environment environment) {

        String pw = environment.getProperty("dataSource.password"),
                user = environment.getProperty("dataSource.user"),
                url = environment.getProperty("dataSource.url");

        Class classOfDs = environment.getPropertyAsClass("dataSource.driverClassName", Driver.class);

        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setPassword(pw);
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setDriverClass(classOfDs);

        return dataSource;
    }
}

Once you've done this, you can start describing jobs using the Spring Batch configuration DSL. Here, we define a job named flickrImportJob which in turn has one step, step1, that in turn reads data using an ItemReader named photoAlbumItemReader and writes data using an ItemWriter named photoAlbumItemWriter.


@Configuration
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Import(BatchInfrastructureConfiguration.class)
public class BatchImporterConfiguration {

    @Bean(name = "flickrImportJob")
    public Job flickrImportJob(
            JobBuilderFactory jobs,
            @Qualifier("step1") Step s1 
    ) {
        return jobs.get("flickrImportJob")
                .flow(s1)            
                .end()
                .build();
    }


    @Bean(name = "step1")
    public Step step1(StepBuilderFactory stepBuilderFactory,
                      @Qualifier("photoAlbumItemReader") ItemReader ir,
                      @Qualifier("photoAlbumItemWriter") ItemWriter iw
    ) {
        return stepBuilderFactory.get("step1")
                .chunk(10)
                .reader(ir)
                .writer(iw)
                .build();
    }
    // ... omitting definitions of ItemReader and ItemWriters 
}
   

Java Configuration with Spring Social

In the first post in this series, I introduced Spring's Java configuration mechanism. There is the base configuration style, and - as we saw - there are annotations (like @EnableWebMvc) that turn on container-wide, and often conventions-oriented features. In the case of @EnableWebMvc, the annotation enables Spring MVC and turns on support for correctly exposing @Controller-annotated beans as HTTP endpoints. Java configuration APIs of the same style as @EnableWebMvc can often optionally implement interfaces that are used to contribute, change, or otherwise tailor the API. Most Spring projects today offer beta-or-better APIs that work in this same manner. In this post, we'll look at Spring Social.

Spring Social a OAuth 1.0, 1.0.a and 2.0-compliant REST-services client and builds on top of this strongly typed Java bindings. Here's an example setup.


@Configuration
@EnableJdbcConnectionRepository
@EnableFacebook(appId = "${blog.facebook.appId}", appSecret = "${blog.facebook.appSecret}")
public class SocialConfiguration {

    @Bean
    public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, 
                                               ConnectionRepository connectionRepository) {
        return new ConnectController(connectionFactoryLocator, connectionRepository);
    }

    @Bean
    public ProviderSignInController providerSignInController(
            ConnectionFactoryLocator connectionFactoryLocator, 
            UsersConnectionRepository usersConnectionRepository) {
        
        return new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, new SignInAdapter() {
            @Override
            public String signIn(String userId, Connection connection, NativeWebRequest request) {
                return SignInUtils.signIn(userId);
            }
        });
    }

    @Bean
    public DisconnectController disconnectController(Environment environment, UsersConnectionRepository usersConnectionRepository) {
        return new DisconnectController(usersConnectionRepository, environment.getProperty("blog.facebook.appSecret"));
    }

    @Bean
    public UserIdSource userIdSource() {
        return new UserIdSource() {
            @Override
            public String getUserId() {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (authentication == null) {
                    throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
                }
                return authentication.getName();
            }
        };
    }
}

Once you've done this, you can simply inject instances of the Facebook API binding and make calls against it. Spring Social will attempt to make the call if there is an authenticated user, and if not will prompt the user to approve the API access and then proceed with the call.


@Inject private Facebook facebook;

@RequestMapping("/hello")
public void showProfileInformation( Model model){ 
  model.addAttribute( "name", facebook.userOperations().getUserProfile().getName() );
}
April 13, 2013

Java Configuration

In my last post I talked a little bit about how ? at runtime ? Spring beans are all just the same thing. I tried to emphasize that beans defined in any number of styles can be mixed and matched. While the XML format will always be supported, I personally like the increasingly numerous Java-configuration centric APIs and DSLs that the Spring projects are exposing.

Java configuration offers some of the strengths of both XML-based configuration and convention-oriented component scanning. It?s a single definition of the components in your system, and it?s type safe and defined by Java types, not a secondary grammar that doesn?t enjoy the same validation cycle as the compiled code.


@Configuration
@PropertySource("classpath:services.properties")
@ComponentScan  
@EnableTransactionManagement  
public class ServiceConfiguration {

    @Bean 
    public DataSource dataSource(Environment e) {
        org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource();
        ds.setDriverClassName(e.getProperty("dataSource.driverClassName"));
        ds.setPassword(e.getProperty("dataSource.password"));
        ds.setUsername(e.getProperty("dataSource.user"));
        ds.setUrl(
                String.format("jdbc:postgresql://%s:%s/%s",
                        e.getProperty("dataSource.host"),
                        Integer.parseInt(e.getProperty("dataSource.port")),
                        e.getProperty("dataSource.db")));
        return ds;
    }

    @Bean 
    public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

This class does a lot of things that you might recognize from the XML format. It?s a configuration class because of the @Configuration annotation. You feed this class to an instance of AnnotationConfig(Web)ApplicationContext, which will scan the bean and look for methods annotated with @Bean. It invokes each in turn, giving each bean the same lifecycle services as it would an XML bean, and makes the returned value from the method available for injection. Other methods may depend on it by specifying an argument of the type, optionally qualifying it with the ID, for example, using @Qualifier. We turn on declarative transaction management (@Transactional) and define a bean that?ll be used to handle transaction management with the @EnableTransactionManagement annotation. Annotations of the form @Enable* typically correspond to *:annotation-driven elements in the XML, enabling features and component models declaratively. The @ComponentScan annotation tells Spring to register all components in the same package or lower as this configuration class, in this case beans annotated with @Component or @Service. The @PropertySource annotation tells Spring to load property values, which can then be injected with a reference to the system object called Environment.

Then, we can easily imagine moving our application into the web tier, so we?ll setup Spring MVC using Java configuration, like this.


@Configuration
@EnableWebMvc
@Import(ServiceConfiguration.class)
public class WebConfiguration extends WebMvcConfigurerAdapter {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
        internalResourceViewResolver.setSuffix(".jsp");
        internalResourceViewResolver.setPrefix("/WEB-INF/views/");
        return internalResourceViewResolver;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

This class is too annotated with @Configuration. It enables Spring MVC (bean validation, file upload support, REST, etc. etc.), and scans the context for beans of well known plugin objects like ViewResolvers. But here we see something new: the configuration class extends a base class, or implements an interface having the word ?Configurer? in it. Typically the framework runs these callback implementing instances and uses it to tailor how it builds itself. Here, for example, we have callback methods we can implement to provide things like HttpMessageConverters, validation, view controllers, etc. We use @ComponentScan to scan and register beans (typically beans of type @Controller) in or below the package that the configuration class is in. And, because we want to inject references to the services we?ve just defined, we?ve used the @Import annotation to import the bean definitions from the ServiceConfiguration class.

This was by no means an exhaustive look at Java configuration, but hopefully you see that there is a lot of convenience in this approach. You can still substitute values from external property files, you can still override and extend the frameworks, and you can still achieve a single configuration artifact ? a place where you can get a bird?s eye view of the system.

There are many Spring projects that provide idiomatic, Java configuration centric APIs and DSLs as Spring core does for building services using Spring and web applications using Spring MVC.

One Big Bag of Beans

At runtime, Spring is just a bunch of beans. Spring provides basic services like dependency injection and lifecycle management, of course, but everything else is just a bean that can be injected, and participate in all the services that the container provides. One big bag of beans. To take advantage of this, all you have to do is tell Spring about your beans. You don?t need to tell it much ? just basic things, like the bean's class, optionally an id by which to disambiguate it, an optional scope, and information about which dependencies a bean has. If you want to build the bean in a special way, besides just using a regular no-arg constructor, you need to tell Spring what to do: use the constructor, use a factory method, etc.

Before Java 5, the natural way to describe this information to Spring in the enterprise Java landscape was XML. So, that begat Spring?s XML flavor. But, as I say, this is just a format by which Spring ingests information about your classes. Metadata. There are lots of other ways. The Groovy Bean Builder. The Spring Scala approach. At one point early on there was a property-file based approach that you could use to manage beans (not from SpringSource, obviously, but still pretty novel!) Some of you may remember the work being done by the XBean project before Spring 2 had official support for namespaces.

Then Java 5 came out and with it annotations. We had ways for the class files to carry the metadata with them! And very shortly after that, we saw the first annotation-centric approaches to ? by convention ? applying services in Spring with the @Transactional annotation. Then the stereotype annotations emerged -@Controller, @Component, @Service, etc, that ? along with component scanning and @Autowired ? made it dead simple to register beans entirely based on convention. Concurrently, work began on Spring Java Config. This approach predates Guice or CDI, for example. Spring Java Configuration lived on the side, as a separate project where the ideas could be fleshed out. In 2009, for Spring 3.0, it was merged into the core framework.

Remember, at runtime all of these beans exist as beans in the same, giant bag, described using the same metadata. For this reason, Spring makes it very easy to assemble applications that employ different approaches as appropriate. You can let component scanning and stereotypes carry you 80% of the way, use Java configuration to assemble everything else and then use XML DSLs in domains where an expressive DSL is available. Beans defined by component scanning can participate in AOP defined by Java configuration, etc. Beans defined in Java configuration can have injected references to beans defined in XML. One type of stuff at runtime. One big bag of beans.

April 11, 2013

Breaking the 1000ms Time to Glass Mobile Barrier

I just found this video on reddit.com, Breaking the 1000ms Time to Glass Mobile Barrier.

It's a video from a Googler - Ilya Grigorik - on delivering applications that render in under much less than 1000ms, not just time to first byte.

This is particularly interesting because he's got a lot of great data points on user tendencies given slower experiences, and on experiences on the dektop vs. mobile, etc.

I don't expect this is all completely new to anyone, but I certainly learned a few things! This is a nice talk if you like performance, and could definitely inform some of the discussion on resource pipelines, mobile, and javascript and web applications.

March 31, 2013

Fixing Facebook

I've been trying all sorts of things to make Facebook interesting. Some of you will know that I was super sick (puking my brains out) in China in early January this year. This left me bed ridden and bored for 3 days. Out of utter desperation and having exhausted all the news sites and comics books and tech journals - I turned to Facebook, the anus of the internet. I like my friends/family, but if I'm honest I just don't care *that* much about everything that's going on. Who does, really, after all if they're your friends you'll hear about the important stuff eventually, anyway... So I've never been a really good Facebook user. A lot of the people I know on Facebook through a shared passion for technology. This makes keeping up with them a worthy endeavour because I might learn about some interesting facet of the technology world. I figured Facebook was as good an outlet as anything. So I've added them, but I wasn't doing a good job in keeping up to date until January. Since January, I've been taking a new approach, however, to expand the scope of the content on Facebook. I've started "friending" perfect strangers or casual acquaintances. Anybody who "friend request"s me I accept. Some requesters are spammers, of course, but as often as not it's someone who knows me via the books, blogs, conferences, twitter, etc. It's a small world, after all.

The results have been really surprising. My friends expand my world view but only in so much as they themselves like certain things, and my friends don't typically like things I vehemently dislike. That's sort of the nature of connecting with someone - familiarity and trust. So, nothing venturd, nothing gained, in following my existing friends. Perfect strangers, on the other hand, are wildcards. Who knows what they'll put up? I *really* like the content on my wall these days. I suppose this must be obvious, but try "friending" perfect strangers. You'll probably make some real friends in the process.

I'd still argue the level of discussion on Twitter's better simply because it forces conciseness and allows a conversation to iterate and evolve very quickly. (I couldn't write this post there, after all!) By befriending perfect strangers, though, I've at least expanded the scope of content on Facebook. Maybe the discussion'll be improved, too.

I also use Google+, and on Google+ my content is essentially wide open. I thus don't really need to friend anyone because it's wide-open and out there for people to see. The scope of content there is thus very good.

The final benefit is that - as I'm interested in the variety of content on the Facebook wall now, I've almost accidentally been able to better follow my family and friends' content, too! Small wonder, that.

C'ya on Facebook! (or Twitter, or Google+)

March 20, 2013

REPL vs. Unit Tests misses the Point

An interesting thread emerged on Twitter today regarding ways to explore a problem: REPL or Unit tests? Michael Nygard (@mtnygard) said:

Exploratory unit tests are a crappy REPL with a really slow response time.

This - to mine own scripting and interpreted-language friendly ears - sounds about right. On the other hand, there's definitely an argument to be made for unit tests, which a lot of people did. To wit, @judsonlester replied:

Also: a REPL is a crappy unit test that requires a lot of extra typing and programmer recall.

This also makes sense! So, we've got two great perspectives offering competing views. It's important to not to get too lost in the methodology and step back from it. What we have here are two competing perspectives on the same agreed upon approach: better programs result from thinking about the problem space before trying to solve it. I would argue that - depending on what you're trying to do - there are a lot of valid approaches. If you're trying to build your own API that others will consume - then exploring possible arrangements in TextMate for me is far more useful than a unit test or a REPL. If you're trying to explore and kick the tires on an unknown API, then a REPL (if it's available!) gives you more immediate feedback. Once you've gained some facts about the behavior of the API, codify them with a unit test.

This whole discussion, of course, centers around whether or not your programming language of choice supports a REPL! I work principally on the JVM, particularly in Java and Scala. As Java has no REPL, it can be useful to drop into some other shell or language and use that. Groovy is great for this as it is a full featured (more-than fully featured) language that you can interact with in a REPL and that can interface with JVM libraries.

In the same vein, years ago, there was a project called BeanShell, which provided a REPL for Java. I confess I haven't heard much about it of late, probably due to the prevalence of Groovy. There was even a JSR (JSR 274) to advance the project to a standard with the expectation that it would one day be folded into the Java platform itself.

Anyway, I'm just thinking aloud. I thought it was an interesting thread. If you've got any thoughts, I'd love to hear about it on Twitter or Google+

Back | Next