So, I know this is a little out of date, but I think Spring Recipes for 2. 5 is the best Spring book out there. It doesn't cover 3.0, but you will definitely get the fundamentals down. Plus, I saw a review of the 3.0 book that said it was *huge*, and I really would rather keep things as streamlined a possible. I really want to nail down the fundamentals of Spring.
I previously went through the first five chapters, but it was a while ago and I'm going to do a quick review.
Using a Container
Chapter one talks about containers. You start out simply enough, with a "ReportGenerator" interface that has the method "generate". Generate accepts statistics and generates a report. There are a couple of implementing classes - PdfReportGenerate and HTMLReportGenerator. One of them will print a pdf report, the other will implement an HTML report. All the report service has to do is instantiate whichever of the two it needs, and call "generate" and pass the statistics.
The problem is that, say your product services a couple of different organizations, one likes pdf reports, the other HTML reports. That means you have to go in and muck with your report generator for each different organization. So, really for separation of concerns - you don't want your "what is this organizations type of report" logic mixed in with the "how do I gather statistics" logic, something needs to be done.
You might initially think, well, let's just create a separate class that instantiates the report service, instantiates the concrete report generator class and sets the report generator class on the report service. But, possibly as a scalability issue, they suggest setting up a "container" with a map that holds an instance of the report service, an also an instance of the report generator. So, this generator will hang around with an instance of the report service and the report generator that any old class can grab and use.
So, the report service grabs the report generator from the container whenever it needs it, confident that the correct version of it has been been put in place by the container. Later on, too keep from mucking with the container, you can have the container read a configuration file for the correct report generator class.
But what grabs the report service from the container? Well, the way you would *initially* handle that (we're working our way through all this), is to create the container from a *main* class. The main class then grabs the report generator from the container, and calls something like "createAnnualReport(2007)". The report service will have that method, and will scurry about putting together whatever data it needs to create the annual report. Then, as mentioned earlier, the report service will grab whatever report generator is provided by the container, and call "generate(collected data) on it.
One detail we skimmed over was that when container is created, it puts a static reference to itself (this) into a static variable. This allows the report service to grab the static instance of container from the Container class. Main doesn't need to bother with that when it gets the report service because it instantiated the container.
Whew! That's actually fairly complicated. Just think of the Container as a cupboard full of goodies, which loads itself when instantiated. The main class- the user of the service - happily grabs the reports service from the cupboard (Container) (after instantiating it), and then uses that service.
The service just uses the cupboard (Container) to get *whatever* report generator is there. It gets the that, then calls the report generator method.
Using a Service Locator.
That's all well and good - but what if the cupbard/container varies by organization? Some use one server, some use another, perhaps. If you're writing this framework, do you want to have the code to get the Container (from the report service) vary, depending on how the container is created? According to the book, this could be "complex, proprietary code." Actually, I wish they gave a better example of this problem. It probably has something to do with the fact that it wouldn't make sense for each use of the service to create it's own container - why have a bunch of cupboards (fleeting though they may be) when you can just have one? I'm kind of guessing here, it's a working hypotheses.
So, you've got to look up the cupboard - find it somewhere on the system. Probably that varies depending on the system. And we don't want random services, such as the report service, to have to be responsible for this. It doesn't want to change every time it's on a new system.
So, we separate out the container/cupboard lookup logic into a separate class - the service locator bunny class. This is all about separation of concerns, reducing dependencies, etc. So, instead of having the Report Service grab the report generator directly from the container, we have it do something like "ServiceLocator.getReportGenerator();" And inside the ServiceLocator bunny, it will have method like:
private static Container = Container.instance
and a method which returns the report generator after grabbing it from the cupboard. So, the Service Locator will handle all the work of figuring out where the container is.
Inversion of Control / Dependency Injection
Although we've gotten rid of a specific dependency by providing a container for the Report Service to grab its ReportGenerator from, and even removed the need of the Report Service be concerned about how to implement lookup by giving it a Resource Locater which handles that work - we can make Report Service even dumber. After all, all this was all about was getting the correct instance of the report generator to the report service. So, why not just give report service a setter method for the report generator? No lookup whatsoever in your Report Service! This is good stuff, right?
So, now the container becomes more than just a cupboard. She becomes old Mother Hubbard, giving little Report Service the right instance of the Report Generator and sending her off to school. Mother Hubbard / Container can also now reduce her exposure by no longer providing access to "this".
Note that there are several ways to do injection - setter (via a set method), construction (via - you guessed it - construction) and interface (not widely used).
Adding a Configuration File.
Ok, now that we've set up all these principals, we really ought to make the whole thing configurable. So, what can we configure, and how?
For sure, we can define the pdf or html instance of the report generator. We give it a name that the container can refer to it with, and also the actual class name:
reportGenerator=com.apress.springrecipes.report.PdfReportGenerator
Come to think of it, we can also specify the report service:
reportService=com.apress.springrecipes.report.ReportService
Anything else...hmmm,,,,
Ah - what about which classes get injected where? We gotta let old mother Hubbard, our container/cupboard, know this:
reportService.reportGenerator=reportGenerator
Nice.
Then, the book shows a slick implementation of the Container class. I *think* there might be typo on Dependency Injection part - it seems like "value" should be "parts[1]" on a certain line. I haven't tested it though.
No comments:
Post a Comment