Spring in action--Part1-Core Spring

后端开发 专栏收录该内容
30 篇文章 0 订阅

全书章节

  1. Core Spring
    The first part introduces you to the essentials of the Spring Framework.
  2. Spring on the web
    Part 2 expands on that by showing how to build web applications with Spring.
  3. Spring in the back end
    Part 3 steps behind the front end and shows where Spring fits in the back end of an application.
  4. Integrating Spring
    The final part shows how Spring can be used to integrate with other applications and services.

    The Spring Framework was created with a very specific goal in mind—to make developing Java EE applications easier.

Spring Boot can take away much of the boilerplate configuration required in a Spring application,enabling you to focus on the business functionality.


Part1.Core Spring

its primary features are dependency injection (DI) and aspect-oriented programming (AOP).

Chapter1.Springing into action

A handful of rough spots, such as applets, Enterprise JavaBeans(EJB), Java Data Objects (JDO).Finally,Spring comes to the stage!

1.1Simplifying Java development

Spring was created to address the complexity of enterprise application development and makes it possible to use plain-vanilla JavaBeans to achieve things that were previously only possible with EJB.

A bean by any other name… Although Spring uses the words bean and JavaBean liberally when referring to application components, this doesn’t mean a Spring component must follow the JavaBeans specification to the letter. A Spring component can be any type of POJO. In this book, I assume a loose definition of JavaBean, which is synonymous with POJO.

How does Spring simplify Java development?
To back up its attack on Java complexity, Spring employs four key strategies:
1.Lightweight and minimally invasive development with POJOs
2.Loose coupling through DI and interface orientation
3.Declarative programming through aspects and common conventions
4.Eliminating boilerplate code(样板代码) with aspects and templates(模板)

1.1.2Injecting dependencies

Coupling is a two-headed beast. On the one hand, tightly coupled code is difficult
to test, difficult to reuse, and difficult to understand, and it typically exhibits “whack-amole” bug behavior (fixing one bug results in the creation of one or more new bugs).紧耦合引发坏处

On the other hand, a certain amount of coupling is necessary—completely uncoupled
code doesn’t do anything. In order to do anything useful, classes need to know about each other somehow. Coupling is necessary but should be carefully managed.工程实现中不得不出现耦合。因此一定要松耦合!

With DI, objects are given their dependencies at creation time by some third party that coordinates each object in the system.

对比:
Without DI:

package com.springinaction.knights;
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest;
public DamselRescuingKnight() {
this.quest = new RescueDamselQuest();//跟RescueDamselQuest紧耦合
}
public void embarkOnQuest() {
quest.embark();
}
}

With DI:

package com.springinaction.knights;
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {//Quest is injected
this.quest = quest;
}
public void embarkOnQuest() {
quest.embark();
}
}

As you can see, BraveKnight, unlike DamselRescuingKnight, **doesn’t create his own
quest**. Instead, he’s given a quest at construction time as a constructor argument. This
is a type of DI known as constructor injection.(构造器注入)

One of the most common ways a dependency is swapped out is with a mock implementation during testing.

package com.springinaction.knights;
import static org.mockito.Mockito.*;
import org.junit.Test;
public class BraveKnightTest {
@Test
public void knightShouldEmbarkOnQuest() {
Quest mockQuest = mock(Quest.class);// create mock Quest
BraveKnight knight = new BraveKnight(mockQuest);//inject mock Quest
knight.embarkOnQuest();
verify(mockQuest, times(1)).embark();
}
}

injecting a quest into a knight

SlayDragonQuest如下:(BraveKnight在上面)

package com.springinaction.knights;
import java.io.PrintStream;
public class SlayDragonQuest implements Quest {
private PrintStream stream;
public SlayDragonQuest(PrintStream stream) {
this.stream = stream;
}
public void embark() {
stream.println("Embarking on quest to slay the dragon!");
}
}

这里梳理一下,有两个接口Knight,Quest。BraveKnight实现了Knight,在构造方法中需要传入参数Quest,并调用了quest的embark方法。SlayDragonQuest实现了Quest,在构造方法中需要传入参数PrintStream,具有embark方法。

The big question here is:

1.how can you give SlayDragonQuest to BraveKnight?
2.And how can you give a PrintStream to SlayDragonQuest?

The act of creating associations between application components is commonly referred to as wiring. (绑定)

绑定有很多方法:
1.XML

<?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.xsd">
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest">
<constructor-arg value="#{T(System).out}" />
</bean>
</beans>

2.JAVA代码绑定

package com.springinaction.knights.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.springinaction.knights.BraveKnight;
import com.springinaction.knights.Knight;
import com.springinaction.knights.Quest;
import com.springinaction.knights.SlayDragonQuest;
@Configuration
public class KnightConfig {
@Bean
public Knight knight() {
return new BraveKnight(quest());
}
@Bean
public Quest quest() {
return new SlayDragonQuest(System.out);
}
}

总结:

Whether you use XML-based or Java-based configuration, the benefits of DI are the same. Although BraveKnight depends on a Quest, it doesn’t know what type of Quest it will be given or where that Quest will come from. Likewise, SlayDragonQuest depends on a PrintStream, but it isn’t coded with knowledge of how that PrintStream comes to be.

Only Spring, through its configuration, knows how all the pieces come together. This makes it possible to change those dependencies with no changes to the depending classes.

不要纠结于绑定,后面还有更多的绑定方法,包括自动绑定

seeing it work
如果用了XML进行绑定,需要将XML导入到代码中嘛。

package com.springinaction.knights;
import org.springframework.context.support.
ClassPathXmlApplicationContext;
public class KnightMain {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context =  //load spring context
new ClassPathXmlApplicationContext(
"META-INF/spring/knight.xml");
Knight knight = context.getBean(Knight.class);  //get Knight bean
knight.embarkOnQuest();  //use Knight
context.close();
}
}

For Java-based configurations, Spring offers AnnotationConfigApplicationContext.

关于DI就介绍到这。下面介绍Spring另一大杀器。
Now let’s look at another of Spring’s Java-simplifying strategies: declarative programming through aspects.

1.1.3Applying aspects

Although DI makes it possible to tie software components together loosely, aspectoriented programming (AOP) enables you to capture functionality that’s used throughout your application in reusable components.

AOP is often defined as a technique that promotes separation of concerns in a software system. Systems are composed of several components, each responsible for a specific piece of functionality. But often these components also carry additional responsibilities beyond their core functionality. System services such as logging, transaction management, and security often find their way into components whose core responsibilities is something else. These system services are commonly referred to as cross-cutting concerns(交叉业务) because they tend to cut across multiple components in a system.(因为它们总是分布在很多组件中)。

By spreading these concerns across multiple components, you introduce two levels
of complexity to your code:

1.实现系统级业务的代码在多个组件中都有重复的复制。这意味着如果你要改变这些业务逻辑,你就必须到各个模块中去修改。就算你把这些业务抽象成一个独立模块,其他模块只是调用它的一个方法,但是这个方法调用也还是分布在很多地方。

2.你的组件会因为那些与自己核心业务无关的代码而变得杂乱。
如图:

面向切面编程:

AOP in action
还是举上面Kight和Quest的例子。考虑这样一种需求,我需要一个logging服务,在每次Quest调用embark之前加上一句话(before),在调用之后又加上一句话(after)。这种场景需求应该是很多的。我们很自然地想到这样实现:

//A Minstrel is a musically inclined logging system from medieval times.

package com.springinaction.knights;
import java.io.PrintStream;
public class Minstrel {
private PrintStream stream;
public Minstrel(PrintStream stream) {
this.stream = stream;
}
public void singBeforeQuest() {  //called before quest
stream.println("Fa la la, the knight is so brave!");
}
public void singAfterQuest() {  //called after quest
stream.println("Tee hee hee, the brave knight " +
"did embark on a quest!");
}
}

然后在BraveKnight中inject,然后调用这些方法:

//A BraveKnight that must call Minstrel methods

package com.springinaction.knights;
public class BraveKnight implements Knight {
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest, Minstrel minstrel) {
this.quest = quest;
this.minstrel = minstrel;
}
public void embarkOnQuest() throws QuestException {
minstrel.singBeforeQuest();  //Should a knight manage his own minstrel?
quest.embark();
minstrel.singAfterQuest();
}
}

但是,这样真的对吗?

Is it really within the knight’s range of concern to manage his minstrel? It seems to me that minstrels should just do their job without having to be asked to do so. After all, that’s a minstrel’s job—to sing about the knight’s endeavors. Why should the knight have to keep reminding the minstrel?(不应该是minstrel自动添加上吗?为什么要knight去管理minstrel呢?所以上面的做法不对哦)

应该利用AOP的思想,将其看做一个service。然后在Spring中进行配置。

<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest">
<constructor-arg value="#{T(System).out}" />
</bean>
<bean id="minstrel" class="com.springinaction.knights.Minstrel"> //declare minstrel bean
<constructor-arg value="#{T(System).out}" />
</bean>
<aop:config>
<aop:aspect ref="minstrel">
<aop:pointcut id="embark"
expression="execution(* *.embarkOnQuest(..))"/>  //define pointcut
<aop:before pointcut-ref="embark"  //declare before advice
method="singBeforeQuest"/>
<aop:after pointcut-ref="embark"  //declare after advice
method="singAfterQuest"/>
</aop:aspect>
</aop:config>
</beans>

1.1.4Eliminating boilerplate code with templates

样板代码是简单的,繁琐的,但又不得不写的无聊的代码。简单理解是很多功能实现的基本“套路”。比如跟JDBC打交道:

public Employee getEmployeeById(long id) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(
"select id, firstname, lastname, salary from " +
"employee where id=?");  //select employee
stmt.setLong(1, id);
rs = stmt.executeQuery();
Employee employee = null;
if (rs.next()) {
employee = new Employee();  //create object from data
employee.setId(rs.getLong("id"));
employee.setFirstName(rs.getString("firstname"));
employee.setLastName(rs.getString("lastname"));
employee.setSalary(rs.getBigDecimal("salary"));
}
return employee;
} catch (SQLException e) {
} finally {
if(rs != null) {  //clean up mess
try {
rs.close();
} catch(SQLException e) {}
}
if(stmt != null) {
try {
stmt.close();
} catch(SQLException e) {}
}
if(conn != null) {
try {
conn.close();
} catch(SQLException e) {}
}
}
return null;
}

What’s most notable above is that much of it is the exact same code you’d write for pretty much any JDBC operation. Little of it has anything to do with querying for an employee, and much of it is JDBC boilerplate.

Spring seeks to eliminate boilerplate code by encapsulating it in templates.

Templates instance:
for JDBC,using Spring’s SimpleJdbcTemplate (a specialization of JdbcTemplate that takes advantage of Java 5 features)

//Templates let your code focus on the task at hand.
public Employee getEmployeeById(long id) {
return jdbcTemplate.queryForObject(
"select id, firstname, lastname, salary " +  //sql query
"from employee where id=?",
new RowMapper<Employee>() {
public Employee mapRow(ResultSet rs,
int rowNum) throws SQLException {  //map results to object
Employee employee = new Employee();
employee.setId(rs.getLong("id"));
employee.setFirstName(rs.getString("firstname"));
employee.setLastName(rs.getString("lastname"));
employee.setSalary(rs.getBigDecimal("salary"));
return employee;
}
},
id);  //specify query parameter
}

总结:
I’ve shown you how Spring attacks complexity in Java development using POJO-oriented development, DI, aspects, and templates. Along the way, I showed you how to configure beans and aspects in XML-based configuration files.

But how do those files get loaded? And what are they loaded into?

Let’s look at the Spring container, the place where your application’s beans reside.

1.2Containing your beans

In a Spring-based application, your application objects live in the Spring container.The container creates the objects, wires them together, configures them, and manages their complete lifecycle from cradle to grave (or new to finalize(), as the case may be).

There’s no single Spring container. Spring comes with several container implementations that can be categorized into two distinct types(分为两类). 1.Bean factories (defined by the org.springframework.beans.factory.BeanFactory interface) are the simplest of containers, providing basic support for DI. 2.Application contexts (defined by the org.springframework.context.ApplicationContext interface) build on the notion
of a bean factory by providing application-framework services, such as the ability to resolve textual messages from a properties file and the ability to publish application events to interested event listeners.

Although it’s possible to work with Spring using either bean factories or application contexts, bean factories are often too low-level for most applications. Therefore, application contexts are preferred over bean factories.
我们把主要精力放在application contexts上就可以了。

1.2.1Working with an application context

Spring comes with several flavors of application context. Here are a few that you’ll most likely encounter:

1.AnnotationConfigApplicationContext—Loads a Spring application context from one or more Java-based configuration classes

2.AnnotationConfigWebApplicationContext—Loads a Spring web application context from one or more Java-based configuration classes

3.ClassPathXmlApplicationContext—Loads a context definition from one or more XML files located in the classpath, treating context-definition files as classpath resources

4.FileSystemXmlApplicationContext—Loads a context definition from one or more XML files in the filesystem

5.XmlWebApplicationContext—Loads context definitions from one or more XML files contained in a web application

load XML:

ApplicationContext context = new
FileSystemXmlApplicationContext("c:/knight.xml");

ApplicationContext context = new
ClassPathXmlApplicationContext("knight.xml");

ApplicationContext context = new AnnotationConfigApplicationContext(
com.springinaction.knights.config.KnightConfig.class);

With an application context in hand, you can retrieve beans from the Spring container by calling the context’s getBean() method.

1.2.2A bean’s life

更详细的说明:

1.Spring instantiates the bean.

2.Spring injects values and bean references into the bean’s properties.

3.If the bean implements BeanNameAware, Spring passes the bean’s ID to the setBeanName() method.

4.If the bean implements BeanFactoryAware, Spring calls the setBeanFactory() method, passing in the bean factory itself.

5.If the bean implements ApplicationContextAware, Spring calls the setApplicationContext() method, passing in a reference to the enclosing application context.

6.If the bean implements the BeanPostProcessor interface, Spring calls its postProcessBeforeInitialization() method.

7.If the bean implements the InitializingBean interface, Spring calls its afterPropertiesSet() method. Similarly, if the bean was declared with an initmethod, then the specified initialization method is called.

8.If the bean implements BeanPostProcessor, Spring calls its postProcessAfterInitialization() method.

9.At this point, the bean is ready to be used by the application and remains in the application context until the application context is destroyed.

10.If the bean implements the DisposableBean interface, Spring calls its destroy() method. Likewise, if the bean was declared with a destroy-method, the specified method is called.

1.3Surveying the Spring landscape

1.3.1Spring modules

下面我将一个一个分析:
1.CORE SPRING CONTAINER
We’ll discuss the core module throughout this book, starting in chapter 2 where we’ll dig deep into Spring DI.
2.SPRING’S AOP MODULE
We’ll dig into Spring’s AOP support in chapter 4.
3.DATA ACCESS AND INTEGRATION
You’ll see how Spring’s template-based JDBC abstraction can greatly simplify JDBC code when we look at Spring data access in chapter 10.
4.WEB AND REMOTING
We’ll look at Spring’s MVC framework in chapters 5–7.
In chapter 15, we’ll check out Spring remoting. And you’ll learn how to create and consume REST APIs in chapter 16.
5.INSTRUMENTATION
Spring’s instrumentation module includes support for adding agents to the JVM. Specifically, it provides a weaving agent for Tomcat that transforms class files as they’re loaded by the classloader.
The instrumentation provided by this module has a narrow set of use cases and we won’t be dealing with this module at all in this book.
6.TESTING

1.3.2The Spring portfolio

The whole Spring portfolio includes several frameworks and libraries that build on the core Spring Framework and on each other.

比如:
SPRING WEB FLOW
Spring Web Flow builds on Spring’s core MVC framework to provide support for building conversational, flow-based web applications that guide users toward a goal (think wizards or shopping carts). We’ll talk more about Spring Web Flow in chapter 8.

SPRING WEB SERVICES
Spring Web Services offers a contract-first web services model where service implementations are written to satisfy the service contract.

SPRING SECURITY
You’ll see how to add Spring Security to an application’s web layer in chapter 9. We’ll
return to Spring Security again in chapter 14 to examine how to secure method invocations.

SPRING INTEGRATION
SPRING BATCH
SPRING SOCIAL
SPRING MOBILE
SPRING FOR ANDROID
SPRING DATA
Spring Data makes it easy to work with all kinds of databases in Spring.
Whether you’re using a document database like MongoDB, a graph database such as Neo4j, or even a traditional relational database, Spring Data offers a simplified programming model for persistence.

We’ll look at using Spring Data to simplify Java Persistence API (JPA) development in chapter 11 and then expand the discussion to include a few NoSQL databases in chapter 12.

SPRING BOOT
Spring Boot is an exciting new project that takes an opinionated view of developing with Spring to simplify Spring itself.
We’ll look at Spring Boot near the end of the book in chapter 21.

1.5Summary

You should now have a good idea of what Spring brings to the table. Spring aims to make enterprise Java development easier and to promote loosely coupled code. Vital to this are dependency injection and aspect-oriented programming.

Chapter2.Wiring beans

As you saw in chapter 1, the traditional approach to creating associations between application objects (via construction or lookup) leads to complicated code that’s difficult to reuse and unit-test.

In Spring, objects aren’t responsible for finding or creating the other objects that they need to do their jobs. Instead, the container gives them references to the objects that they collaborate with. An order-manager component, for example, may need a credit-card authorizer—but it doesn’t have to create the credit-card authorizer. It just needs to show up empty-handed, and it’s given a credit-card authorizer to work with.

When it comes to expressing a bean wiring specification,Spring is incredibly flexible, offering three primary wiring mechanisms:

1.Explicit configuration in XML(显式)
2.Explicit configuration in Java(显式)
3.Implicit bean discovery and automatic wiring(隐式)

Spring’s configuration styles are mix-and-match(三种可以混合使用), so you could choose XML to wire up some beans, use Spring’s Java-based configuration (JavaConfig) for other beans, and let other beans be automatically discovered by Spring.

Even so, my recommendation is to lean on automatic configuration(推荐使用自动绑定) as much as you can. The less configuration you have to do explicitly, the better.

2.2Automatically wiring beans

Spring attacks automatic wiring from two angles:

1.Component scanning—Spring automatically discovers beans to be created in the application context.
2.Autowiring—Spring automatically satisfies bean dependencies.

举例说明自动绑定

1.先定义接口:

//The CompactDisc interface defines the concept of a CD in Java.
package soundsystem;
public interface CompactDisc {
void play();
}

2.实现接口:

//@CompactDisc-annotated SgtPeppers implements CompactDisc
package soundsystem;
import org.springframework.stereotype.Component;
@Component
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}

What you should take note of is that SgtPeppers is annotated with @Component. This simple annotation identifies this class as a component class and serves as a clue to Spring that a bean should be created for the class. There’s no need to explicitly configure a SgtPeppers bean; Spring will do it for you because this class is annotated with @Component.

3.默认情况下,Component scanning是没有打开的。因此还得打开此功能,这样Spring才能发现Component嘛。

//@ComponentScan enables component scanning
package soundsystem;  //与定义的Component在同一个package下
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan  //寻找同package下及子目录下的所有Component
public class CDPlayerConfig {
}

4.最后我们可以写一个JUnitTest看是否发现了该Component。

package soundsystem;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
}
}

测试流程:

CDPlayerTest takes advantage of Spring’s SpringJUnit4ClassRunner to have a Spring application context automatically created when the test starts. And the @ContextConfiguration annotation tells it to load its configuration from the CDPlayerConfig class. Because that configuration class includes @ComponentScan, the resulting application context should include the CompactDisc bean.

To prove that, the test has a property of type CompactDisc that is annotated with @Autowired to inject the CompactDisc bean into the test. (I’ll talk more about @Autowired in a moment.) Finally, a simple test method asserts that the cd property isn’t null. If it’s not null, that means Spring was able to discover the CompactDisc class, automatically create it as a bean in the Spring application context, and inject it into the test.

2.2.2Naming a component-scanned bean

Spring中每个bean都有一个id,在自动绑定中如果没有特备指定,那么他的id是第一个字母小写后的单词,比如上面的SgtPeppers,它的bean id为sgtPeppers。也可以自己指定id:

@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}

2.2.3Setting a base package for component scanning

One common reason for explicitly setting the base package is so that you can keep all of your configuration code in a package of its own, separate from the rest of your application’s code. In that case, the default base package won’t do.

@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig {}

//或者指定多个basePackages
@Configuration
@ComponentScan(basePackages={"soundsystem", "video"})
public class CDPlayerConfig {}

因为用String可能不太安全,所以又提供了另一种方法。@ComponentScan also offers you the option of specifying them via classes or interfaces that are in the packages:

@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {}

上面的意思是将这两个class所在的package作为basePackages。Whatever packages those classes are in will be used as the base package for component scanning.Although I’ve specified component classes for basePackageClasses, you might consider creating an empty marker interface in the packages to be scanned(技巧).

If all the objects in your applications were standalone and had no dependencies, like the SgtPeppers bean, then component scanning would be everything you need. But many objects lean on other objects for help to get their job done. You need a way to wire up your component-scanned beans with any dependencies they have.

2.2.4Annotating beans to be automatically wired

Put succinctly, autowiring is a means of letting Spring automatically satisfy a bean’s dependencies by finding other beans in the application context that are a match to the bean’s needs. To indicate that autowiring should be performed, you can use Spring’s @Autowired annotation.

//Injecting a CompactDisc into a CDPlayer bean using autowiring
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}

对于没有beans情况:

If there are no matching beans, Spring will throw an exception as the application context is being created. To avoid that exception, you can set the required attribute on @Autowired to false:

@Autowired(required=false)
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}

但是这样做,要小心啊,如果不检查是否null,可能会出现空指针异常,因为没有绑定嘛。

对于有多beans情况:

In the event that multiple beans can satisfy the dependency, Spring will throw an exception indicating ambiguity in selecting a bean for autowiring. We’ll talk more about managing ambiguity in autowiring later, in chapter 3.

2.2.5Verifying automatic configuration

单元测试看是否都成功绑定了:

package soundsystem;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {
@Rule
public final StandardOutputStreamLog log =
new StandardOutputStreamLog();
@Autowired
private MediaPlayer player;
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
}
@Test
public void play() {
player.play();
assertEquals(
"Playing Sgt. Pepper's Lonely Hearts Club Band" +
" by The Beatles\n",
log.getLog());
}
}

2.6Summary

I’ve also strongly recommended that you favor automatic configuration(best) as much as possible to avoid the maintenance costs involved with explicit configuration. But when you must explicitly configure Spring, you should favor Java-based configuration(better)— which is more powerful, type-safe, and refactorable—over XML configuration(good).

Chapter3.Advanced wiring

3.1Environments and profiles

One of the most challenging things about developing software is transitioning an application from one environment to another. Certain environment-specific choices made for development aren’t appropriate or won’t work when the application transitions from development to production.

其中一种解决方法,比较传统:
One way of doing this is to configure each bean in a separate configuration class (or XML file) and then make a build-time decision (perhaps using Maven profiles) about which to compile into the deployable application. The problem with this solution is that it requires that the application be rebuilt for each environment. A rebuild might not be that big a problem when going from development to QA. But requiring a rebuild between QA and production has the potential to introduce bugs and cause an epidemic of ulcers among the members of your QA team.

Fortunately, Spring has a solution that doesn’t require a rebuild.

3.1.1Configuring profile beans

Spring’s solution for environment-specific beans isn’t much different from build-time solutions.

But rather than make that decision at build time, Spring waits to make the decision at runtime. Consequently, the same deployment unit (perhaps a WAR file) will work in all environments without being rebuilt.例如:

@Configuration
@Profile("dev")  //重点
public class DevelopmentProfileConfig {
@Bean(destroyMethod="shutdown")
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
}

The importance of @Profile(“dev”).It tells Spring that the beans in this configuration class should be created only if the dev profile is active. If the dev profile isn’t active, then the @Bean methods will be ignored.
你可能会有其他的configuration class:

@Configuration
@Profile("prod")  //重点
public class ProductionProfileConfig {
@Bean
public DataSource dataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean =
new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(
javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}

这样不同环境下,就能够选用不同的configuration啦。

Starting with Spring 3.2, however, you can use @Profile at the method level(3.1以前只能是class level,如上面), alongside the @Bean annotation.

例如,上面的可以综合下:

//The @Profile annotation wires beans based on active files
@Configuration
public class DataSourceConfig {
@Bean(destroyMethod="shutdown")
@Profile("dev")  //Wired for “dev” profile
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
@Bean
@Profile("prod")  //Wired for “prod” profile
public DataSource jndiDataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean =
new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}
}

补充:Any bean that isn’t given a profile will always be created, regardless of what profile is active.没有标记profile的bean肯定什么情况都会被创建呀。

CONFIGURING PROFILES IN XML(与上面功能一样)

<?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:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans profile="dev">  //“dev” profile beans
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
</beans>
<beans profile="qa">  //“qa” profile beans
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:url="jdbc:h2:tcp://dbserver/~/test"
p:driverClassName="org.h2.Driver"
p:username="sa"
p:password="password"
p:initialSize="20"
p:maxActive="30" />
</beans>
<beans profile="prod">  //“prod” profile beans
<jee:jndi-lookup id="dataSource"
jndi-name="jdbc/myDatabase"
resource-ref="true"
proxy-interface="javax.sql.DataSource" />
</beans>
</beans>

既然当profile is active的时候,对应的bean就会被创建,那么how do you make a profile active?

3.1.2Activating profiles

优先级:
Spring honors two separate properties when determining which profiles are active: spring.profiles.active and spring.profiles.default. 1.If spring.profiles.active is set, then its value determines which profiles are active. But if spring.profiles.active isn’t set, then Spring looks to 2.spring.profiles.default. If neither spring.profiles.active nor spring.profiles.default is set, then 3.there are no active profiles, and only those beans that aren’t defined as being in a profile are created.

作者偏好:
One approach that I like is to set spring.profiles.default to the development profile using parameters on DispatcherServlet and in the servlet context (for the sake of ContextLoaderListener). For example, a web application’s web.xml file might set spring.profiles.default as shown in the next listing.

//Setting default profiles in a web application’s web.xml file
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<context-param>
<param-name>spring.profiles.default</param-name>//Set default profile for context
<param-value>dev</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>spring.profiles.default</param-name>  //Set default profile for servlet
<param-value>dev</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

With spring.profiles.default set this way, any developer can retrieve the application code from source control and run it using development settings (such as an embedded database) without any additional configuration.(方便了开发人员,或者说是在开发环节

到了QA等环节:
Then, when the application is deployed in a QA, production, or other environment, the person responsible for deploying it can set spring.profiles.active using system properties, environment variables, or JNDI as appropriate. When spring.profiles.active is set, it doesn’t matter what spring.profiles.default is set to; the profiles set in spring.profiles.active take precedence.

补充:You can activate multiple profiles at the same time by listing the profile names, separated by commas. Of course, it probably doesn’t make much sense to enable both dev and prod profiles at the same time, but you could enable multiple orthogonal profiles simultaneously.

TESTING WITH PROFILES

//在测试中选择开启何种profile
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {
...
}

Spring profiles are a great way to conditionally define beans where the condition is based on which profile is active. But Spring 4 offers a more general-purpose mechanism for conditional bean definitions where the condition is up to you. Let’s see how to define conditional beans using Spring 4 and the @Conditional annotation.更加通用的conditional bean机制!

3.2Conditional beans

Suppose you want one or more beans to be configured if and only if some library is available in the application’s classpath. Or let’s say you want a bean to be created only if a certain other bean is also declared. Maybe you want a bean to be created if and only if a specific environment variable is set.更加细颗粒度的condition。

For example, suppose you have a class named MagicBean that you only want Spring to instantiate if a magic environment property has been set. If the environment has no such property, then the MagicBean should be ignored. The following listing shows a configuration that conditionally configures the MagicBean using @Conditional.

//Conditionally configuring a bean
@Bean
@Conditional(MagicExistsCondition.class)  //Conditionally create bean
public MagicBean magicBean() {
return new MagicBean();
}

//Condition是Spring系统写好的,只需要MagicExistsCondition实现该接口即可
public interface Condition {
boolean matches(ConditionContext ctxt,
AnnotatedTypeMetadata metadata);
}

The class given to @Conditional can be any type that implements the Condition interface.

For this example, you need to create an implementation of Condition that hinges its decision on the presence of a magic property in the environment. The next listing shows MagicExistsCondition, an implementation of Condition that does the trick.

//Checking for the presence of magic in a Condition
package com.habuma.restfun;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
public class MagicExistsCondition implements Condition {
public boolean matches(
ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.containsProperty("magic");  //Check for “magic” property
}
}

This results in true being returned from matches().Consequently, the condition is met, and any beans whose @Conditional annotation refers to MagicExistsCondition will be created.

其中,Condition中参数ConditionContext,AnnotatedTypeMetadata也是两个接口,部分方法如下:

//每个方法具体的作用可以再看书

public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
ClassLoader getClassLoader();
}


public interface AnnotatedTypeMetadata {
boolean isAnnotated(String annotationType);
Map<String, Object> getAnnotationAttributes(String annotationType);
Map<String, Object> getAnnotationAttributes(
String annotationType, boolean classValuesAsString);
MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationType);
MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationType, boolean classValuesAsString);
}

对于ConditionContext来说,可以:

1.Check for bean definitions via the BeanDefinitionRegistry returned from getRegistry().

2.Check for the presence of beans, and even dig into bean properties via the ConfigurableListableBeanFactory returned from getBeanFactory().

3.Check for the presence and values of environment variables via the Environment retrieved from getEnvironment().

4.Read and inspect the contents of resources loaded via the ResourceLoader returned from getResourceLoader().

5.Load and check for the presence of classes via the ClassLoader returned from getClassLoader().

Interestingly, starting with Spring 4, the @Profile annotation has been refactored to be based on @Conditional and the Condition interface.下面看如何通过Condition实现Profile。

The @Profile annotation looks like this:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)  //通过ProfileCondition来进行配置
public @interface Profile {
String[] value();
}

As shown next, ProfileCondition implements Condition and considers several factors from both ConditionContext and AnnotatedTypeMetadata in making its decision.

//ProfileCondition checking whether a bean profile is acceptable
class ProfileCondition implements Condition {
public boolean matches(
ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs =
metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment()
.acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}
}

3.3Addressing ambiguity in autowiring

For those times when ambiguity does happen, however, Spring offers a couple of options. You can declare one of the candidate beans as the primary choice, or you can use qualifiers to help Spring narrow its choices to a single candidate.

3.3.1Designating a primary bean

3种方法:
1.here’s how you might declare the @Component-annotated IceCream bean as the primary choice:

//自动绑定用法
@Component
@Primary
public class IceCream implements Dessert { ... }

2.if you’re declaring the IceCream bean explicitly in Java configuration, the @Bean method might look like this:

@Bean
@Primary
public Dessert iceCream() {
return new IceCream();
}

3.XML configuration:

<bean id="iceCream"
class="com.desserteater.IceCream"
primary="true" />

Just as Spring couldn’t choose among multiple candidate beans, it can’t choose among multiple primary beans. Clearly, when more than one bean is designated as primary, there are no primary candidates. For a more powerful ambiguity-busting mechanism, let’s look at qualifiers.但有多个Primary时,又没有办法选择了,悲剧。

3.3.2Qualifying autowired beans

In contrast, Spring’s qualifiers apply a narrowing operation to all candidate beans, ultimately arriving at the single bean that meets the prescribed qualifications. If ambiguity still exists after applying all qualifiers, you can always apply more qualifiers to narrow the choices further.

先来个简单的规则:

//在多个实现中匹配iceCream
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

@Qualifier(“iceCream”) refers to the bean created when component-scanning created an instance of the IceCream class.但是这样太紧耦合了!

The problem is that you specified a qualifier on setDessert() that is tightly coupled to the class name of the bean being injected. Any change to that class name will render the qualifier ineffective.

CREATING CUSTOM QUALIFIERS

Instead of relying on the bean ID as the qualifier, you can assign your own qualifier to
a bean. All you need to do is place the @Qualifier annotation on the bean declaration. For example, it can be applied alongside @Component like this:

@Component
@Qualifier("cold")
public class IceCream implements Dessert { ... }

Because it’s not coupled to the class name, you can refactor the name of the IceCream class all you want without worrying about breaking autowiring.

DEFINING CUSTOM QUALIFIER ANNOTATIONS

但是这样还是可能重复啊,比如两个实现类都是@Qualifier(“cold”)。但是Spring又不允许多个@Qualifier。There’s no way you can use @Qualifier (at least not directly) to narrow the list of autowiring candidates to a single choice.

那么我们需要自己制造一些像@Qualifier一样的注解,然后再进行分类,就行了。例如:

//cold
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold { }

//creamy
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy { }

然后我们就可以将其注解到具体的Component中:

//这样我们就能够更加细粒度的区分IceCream和Popsicle了
@Component
@Cold
@Creamy
public class IceCream implements Dessert { ... }

@Component
@Cold
@Fruity
public class Popsicle implements Dessert { ... }

最后,再自动绑定:

//这里传入的就是IceCream了
@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert) {  
this.dessert = dessert;
}

By defining custom qualifier annotations, you’re able to use multiple qualifiers together with no limitations or complaints from the Java compiler. Also, your custom annotations are more type-safe than using the raw @Qualifier annotation and specifying the qualifier as a String.

Take a closer look at the setDessert() method and how it’s annotated. Nowhere do you explicitly say that you want that method to be autowired with the IceCream bean. Instead, you identify the desired bean by its traits, @Cold and @Creamy. Thus setDessert() remains decoupled from any specific Dessert implementation.(在自动绑定的时候也有解耦) Any bean that satisfies those traits will do fine. It just so happens that in your current selection of Dessert implementations, the IceCream bean is the single matching candidate.

3.4Scoping beans

By default, all beans created in the Spring application context are created as singletons. That is to say, no matter how many times a given bean is injected into other beans, it’s always the same instance that is injected each time.

Spring defines several scopes under which a bean can be created, including the following:

1.Singleton—One instance of the bean is created for the entire application.
2.Prototype—One instance of the bean is created every time the bean is injected into or retrieved from the Spring application context.
3.Session—In a web application, one instance of the bean is created for each session.
4.Request—In a web application, one instance of the bean is created for each request.

To select an alternative type, you can use the @Scope annotation, either in conjunction with the @Component annotation or with the @Bean annotation.

for example:

//1.auto
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad { ... }

//2.java configuration
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad() {
return new Notepad();
}

//3.XML
<bean id="notepad"
class="com.myapp.Notepad"
scope="prototype" />

3.4.1Working with request and session scope

In a web application, it may be useful to instantiate a bean that’s shared within the scope of a given request or session. For instance, in a typical e-commerce application, you may have a bean that represents the user’s shopping cart. If the shopping cart bean is a singleton, then all users will be adding products to the same cart. On the other hand, if the shopping cart is prototype-scoped, then products added to the cart in one area of the application may not be available in another part of the application where a different prototype-scoped shopping cart was injected.

In the case of a shopping cart bean, session scope makes the most sense, because it’s most directly attached to a given user. To apply session scope, you can use the @Scope annotation in a way similar to how you specified prototype scope:

@Component
@Scope(
value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() { ... }

Notice that @Scope also has a proxyMode attribute set to ScopedProxyMode.INTERFACES.
This attribute addresses a problem encountered when injecting a session- or requestscoped bean into a singleton-scoped bean.

到底是什么问题呢?考虑下面代码:

@Component
public class StoreService {
@Autowired
public void setShoppingCart(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
...
}

**Because StoreService is a singleton bean, it will be created as the Spring application
context is loaded.** As it’s created, Spring will attempt to inject ShoppingCart into the setShoppingCart() method. But the ShoppingCart bean, being session scoped, doesn’t exist yet. There won’t be an instance of ShoppingCart until a user comes along and a session is created.一句话就是,在StoreService创建后,想要注入ShoppingCart的时候,ShoppingCart根本有可能不存在。

Moreover, there will be many instances of ShoppingCart: one per user. You don’t want Spring to inject just any single instance of ShoppingCart into StoreService. You want StoreService to work with the ShoppingCart instance for whichever session happens to be in play when StoreService needs to work with the shopping cart.而且,理想情况是,不盲目地注入,哪个ShoppingCart正在工作,就注入哪一个。

如果是interface,proxyMode就应该是ScopedProxyMode.INTERFACES。
如果是具体的class,proxyMode就应该是ScopedProxyMode.TARGET_CLASS。

原理如下图:

3.4.2Declaring scoped proxies in XML

<bean id="cart"
class="com.myapp.ShoppingCart"
scope="session">
<aop:scoped-proxy proxy-target-class="false" />  //interface
</bean>

By default, it uses CGLib to create a target class proxy.

3.5Runtime value injection

When we talk about dependency injection and wiring, we’re often talking about wiring a bean reference into a property or constructor argument of another bean. 前面讨论的,It’s often about associating one object with another object.

比如前面一般的套路是:

@Bean
public CompactDisc sgtPeppers() {
return new BlankDisc(
"Sgt. Pepper's Lonely Hearts Club Band",
"The Beatles");
}

Sometimes hard-coded values(我的理解是,直接在代码或者XML中写死了的,比如上面的代码) are fine. Other times, however, you may want to avoid hard-coded values and let the values be determined at runtime. For those cases, Spring offers two ways of evaluating values at runtime:

1.Property placeholders
2.The Spring Expression Language (SpEL)

You’ll soon see that the application of these two techniques is similar, although their purposes and behavior are different.

3.5.1Injecting external values

The simplest way to resolve external values in Spring is to declare a property source and retrieve the properties via the Spring Environment.
For example, the following listing shows a basic Spring configuration class that uses external properties to wire up a BlankDisc bean.

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")//Declare a property source
public class ExpressiveConfig {
@Autowired
Environment env;
@Bean
public BlankDisc disc() {
return new BlankDisc(
env.getProperty("disc.title"),  //Retrieve property values
env.getProperty("disc.artist"));
}
}

In this example, @PropertySource references a file named app.properties in the classpath. It might look something like this:

disc.title=Sgt. Peppers Lonely Hearts Club Band
disc.artist=The Beatles

应该很好理解,就是将值存入一个配置文件中,然后读取之。

DIGGING INTO SPRING’S ENVIRONMENT

Retrieving properties directly from Environment is handy, especially when you’re wiring beans in Java configuration. But Spring also offers the option of wiring properties with placeholder values that are resolved from a property source.

RESOLVING PROPERTY PLACEHOLDERS

感觉套路和实现的功能跟上面大同小异。

Resolving external properties is one way to defer value resolution until runtime, but its focus is finely tuned on resolving properties, by name, from Spring’s Environment and property sources.

Spring Expression Language, on the other hand, offers a more general way of calculating values for injection at runtime.

3.5.2Wiring with the Spring Expression Language

SpEL has a lot of tricks up its sleeves, including the following:

1.The ability to reference beans by their IDs
2.Invoking methods and accessing properties on objects
3.Mathematical, relational, and logical operations on values
4.Regular expression matching
5.Collection manipulation

As you’ll see later in this book, SpEL can also be used for purposes other than dependency injection. Spring Security, for example, supports defining security constraints using SpEL expressions. And if you’re using Thymeleaf templates as the views in your Spring MVC application, those templates can use SpEL expressions to reference model data.除了DI外,SpEl还有更多的实用功能,我的理解,它应该是一种描述语言,在很多地方都有用。

A FEW SPEL EXAMPLES

The first thing to know is that SpEL expressions are framed with #{ … }, much as property placeholders are framed with ${ … }.

举例:

#{T(System).currentTimeMillis()}

Ultimately this expression evaluates to the current time in milliseconds at the moment when the expression is evaluated. The T() operator evaluates java.lang.System as a type so that the staticcurrentTimeMillis() method can be invoked.

SpEL expressions can also refer to other beans or properties on those beans. For example, the following expression evaluates to the value of the artist property on a bean whose ID is sgtPeppers:

#{sgtPeppers.artist}

You can also refer to system properties via the systemProperties object:

#{systemProperties['disc.title']}

When injecting properties and constructor arguments on beans that are created via component-scanning, you can use the @Value annotation, much as you saw earlier with property placeholders. Rather than use a placeholder expression, however, you use a SpEL expression. For example, here’s what the BlankDisc constructor might look like, drawing the album title and artist from system properties:

public BlankDisc(
@Value("#{systemProperties['disc.title']}") String title,
@Value("#{systemProperties['disc.artist']}") String artist) {
this.title = title;
this.artist = artist;
}

let’s go over some of the primitive expressions supported in SpEL.

EXPRESSING LITERAL VALUES

//简单内置举例
#{3.14159}
#{9.87E4}  //等于98,700
#{'Hello'}
#{false}

REFERENCING BEANS, PROPERTIES, AND METHODS

Another basic thing that a SpEL expression can do is reference another bean by its ID.

//举例
#{sgtPeppers}  //wire one bean into another bean’s property
#{sgtPeppers.artist}  //refer to the artist property of the sgtPeppers bean
#{artistSelector.selectArtist()}  //call methods on a bean

#{artistSelector.selectArtist().toUpperCase()}
//more safe
#{artistSelector.selectArtist()?.toUpperCase()}

This operator makes sure the item to its left isn’t null before accessing the thing on its right. So, if selectArtist() returns null, then SpEL won’t even try to invoke toUpperCase(). The expression will evaluate to null.

WORKING WITH TYPES IN EXPRESSIONS

The real value of the T() operator is that it gives you access to static methods and constants on the evaluated type.
For example, suppose you need to wire the value of pi into a bean property. The following SpEL expression does the trick:T(java.lang.Math).PI静态方法:T(java.lang.Math).random()

SPEL OPERATORS


比较简单

EVALUATING REGULAR EXPRESSIONS
To demonstrate, suppose you want to check whether a String contains a valid email address. In that case, you can apply matches like this:

#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}

EVALUATING COLLECTIONS

Some of SpEL’s most amazing tricks involve working with collections and arrays. The most basic thing you can do is reference a single element from a list:#{jukebox.songs[4].title}

To spice things up a bit, I suppose you could randomly select a song from the jukebox:

#{jukebox.songs[T(java.lang.Math).random() *jukebox.songs.size()].title}

SpEL also offers a selection operator (.?[]) to filter a collection into a subset of the collection.

As a demonstration, suppose you want a list of all songs in the jukebox where the artist property is Aerosmith. The following expression uses the selection operator to arrive at the list of available Aerosmith songs:

#{jukebox.songs.?[artist eq 'Aerosmith']}

SpEL also offers two other selection operations: .^[] for selecting the first matching entry and .$[] for selecting the last matching entry. To demonstrate, consider this expression, which finds the first song in the list whose artist property is Aerosmith:

#{jukebox.songs.^[artist eq 'Aerosmith']}

Finally, SpEL offers a projection operator (.![]) to project properties from the elements in the collection onto a new collection.
As an example, suppose you don’t want a collection of the song objects, but a collection of all the song titles. The following expression projects the title property into a new collection of Strings:

#{jukebox.songs.![title]}

Naturally, the projection operator can be combined with any of SpEL’s other operators, including the selection operator. For example, you could use this expression to obtain a list of all of Aerosmith’s songs:

#{jukebox.songs.?[artist eq 'Aerosmith'].![title]}

忠告:Take care not to get too clever with your expressions. The more clever your expressions become, the more important it will be to test them.

3.6Summary

Chapter4.Aspect-oriented Spring

In chapter 2, you learned how to use dependency injection to manage and configure application objects. Whereas DI helps you decouple application objects from each other, AOP helps you decouple cross-cutting concerns from the objects they affect.

4.1What is aspect-oriented programming?


A common object-oriented technique for reusing common functionality is to apply inheritance or delegation. But inheritance can lead to a brittle object hierarchy if the same base class is used throughout an application, and delegation can be cumbersome because complicated calls to the delegate object may be required.

4.1.1Defining AOP terminology

Like most technologies, AOP has its own jargon. Aspects are often described in terms of advice, pointcuts, and join points. Figure 4.2 illustrates how these concepts are tied together.

ADVICE

Likewise, aspects have a purpose—a job they’re meant to do. In AOP terms, the job of an aspect is called advice.Advice defines both the what and the when of an aspect.

Spring aspects can work with five kinds of advice:

1.Before—The advice functionality takes place before the advised method is invoked.
2.After—The advice functionality takes place after the advised method completes,regardless of the outcome.
3.After-returning—The advice functionality takes place after the advised method successfully completes.
4.After-throwing—The advice functionality takes place after the advised method throws an exception.
5.Around—The advice wraps the advised method, providing some functionality before and after the advised method is invoked.

JOIN POINTS

Your application may have thousands of opportunities for advice to be applied. These opportunities are known as join points. A join point is a point in the execution of the application where an aspect can be plugged in. This point could be a method being called, an exception being thrown, or even a field being modified. These are the points where your aspect’s code can be inserted into the normal flow of your application to add new behavior.

POINTCUTS

It’s not possible for any one meter reader to visit all houses serviced by the electric company. Instead, each one is assigned a subset of all the houses to visit. Likewise, an aspect doesn’t necessarily advise all join points in an application. Pointcuts help narrow down the join points advised by an aspect.

If advice defines the what and when of aspects, then pointcuts define the where.

Often you specify these pointcuts using explicit class and method names or through regular expressions that define matching class and method name patterns. Some AOP frameworks allow you to create dynamic pointcuts that determine whether to apply advice based on runtime decisions, such as the value of method parameters.

ASPECTS

When a meter reader starts his day, he knows both what he’s supposed to do (report electricity usage) and which houses to collect that information from. Thus he knows everything he needs to know to get his job done.

An aspect is the merger of advice and pointcuts.

Taken together, advice and pointcuts define everything there is to know about an aspect—what it does and where and when it does it.

我的理解,ADVICE定义了一些动作(what)以及动作的时间(when),POINTCUTS定义了where(精确到具体的class的具体的method中)。所以,这两个一结合就定义了一个ASPECTS。

INTRODUCTIONS

An introduction allows you to add new methods or attributes to existing classes. For example, you could create an Auditable advice class that keeps the state of when an object was last modified.

WEAVING

Weaving is the process of applying aspects to a target object to create a new proxied object. The aspects are woven into the target object at the specified join points. The weaving can take place at several points in the target object’s lifetime:

1.Compile time—Aspects are woven in when the target class is compiled. This requires a special compiler. AspectJ’s weaving compiler weaves aspects this way.
2.Class load timeAspects are woven in when the target class is loaded into the JVM. This requires a special ClassLoader that enhances the target class’s bytecode before the class is introduced into the application. AspectJ 5’s load-time weaving (LTW) support weaves aspects this way.
3.Runtime—Aspects are woven in sometime during the execution of the application. Typically, an AOP container dynamically generates a proxy object that delegates to the target object while weaving in the aspects. This is how Spring AOP aspects are woven.

The key concept you should take from this is that pointcuts define which join points get advised.

4.1.2Spring’s AOP support

Spring’s support for AOP comes in four styles:

1.Classic Spring proxy-based AOP
2.Pure-POJO aspects
3.@AspectJ annotation-driven aspects
4.Injected AspectJ aspects (available in all versions of Spring)

When held up against simple declarative AOP and annotation-based AOP, Spring’s classic AOP seems bulky and overcomplicated. Therefore, I won’t be covering classic Spring AOP.

We’ll explore more of these Spring AOP techniques in this chapter. But before we get started, it’s important to understand a few key points of Spring’s AOP framework.

SPRING ADVICE IS WRITTEN IN JAVA

SPRING ADVISES OBJECTS AT RUNTIME

Spring doesn’t create a proxied object until that proxied bean is needed by the application. If you’re using an ApplicationContext, the proxied objects will be created when it loads all the beans from the BeanFactory. Because Spring creates proxies at runtime, you don’t need a special compiler to weave aspects in Spring’s AOP.

SPRING ONLY SUPPORTS METHOD JOIN POINTS

Method interception should suit most, if not all, of your needs. If you find yourself in need of more than method interception, you’ll want to complement Spring AOP with AspectJ.

Now you have a general idea of what AOP does and how it’s supported by Spring. It’s time to get your hands dirty creating aspects in Spring. Let’s start with Spring’s declarative AOP model.

4.2Selecting join points with pointcuts

In Spring AOP, pointcuts are defined using AspectJ’s pointcut expression language. If you’re already familiar with AspectJ, then defining pointcuts in Spring should feel natural.

The most important thing to know about AspectJ pointcuts as they pertain to Spring AOP is that Spring only supports a subset of the pointcut designators available in AspectJ. Table 4.1 lists the AspectJ pointcut designators that are supported in Spring AOP.

As you browse through the supported designators, note that the execution designator is the only one that actually performs matches. The other designators are used to limit those matches. This means execution is the primary designator you’ll use in every pointcut definition you write. You’ll use the other designators to constrain the pointcut’s reach.

4.2.1Writing pointcuts

To demonstrate aspects in Spring, you need something to be the subject of the aspect’s pointcuts. For that purpose, let’s define a Performance interface:

package concert;
public interface Performance {
public void perform();
}

Performance represents any kind of live performance, such as a stage play, a movie, or a concert. Let’s say that you want to write an aspect that triggers off Performance’s perform() method. Figure 4.4 shows a pointcut expression that can be used to apply advice whenever the perform() method is executed.

Now let’s suppose that you want to confine the reach of that pointcut to only the concert package. In that case, you can limit the match by tacking on a within() designator, as shown in figure 4.5.

4.2.2 Selecting beans in pointcuts

In addition to the designators listed in table 4.1, Spring adds a bean() designator that lets you identify beans by their ID in a pointcut expression. bean() takes a bean ID or name as an argument and limits the pointcut’s effect to that specific bean.
For example, consider the following pointcut:

execution(* concert.Performance.perform()) and bean('woodstock')

Here you’re saying that you want to apply aspect advice to the execution of Performance’s perform() method, but limited to the bean whose ID is woodstock.

Now that we’ve covered the basics of writing pointcuts, let’s see how to write the advice and declare the aspects that use those pointcuts.

4.3Creating annotated aspects

You’ve already defined the Performance interface as the subject of your aspect’s pointcuts. Now let’s use AspectJ annotations to create an aspect.

4.3.1Defining an aspect

//Audience class: an aspect that watches a performance
package concert;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class Audience {
@Before("execution(** concert.Performance.perform(..))")  //Before performance
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
}
@Before("execution(** concert.Performance.perform(..))")  //Before performance
public void takeSeats() {
System.out.println("Taking seats");
}
@AfterReturning("execution(** concert.Performance.perform(..))")  //After performance
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
@AfterThrowing("execution(** concert.Performance.perform(..))")//After bad performance
public void demandRefund() {
System.out.println("Demanding a refund");
}
}

Notice how the Audience class is annotated with @Aspect. This annotation indicates that Audience isn’t just any POJO—it’s an aspect.

As you can see, those methods are annotated with advice annotations to indicate when those methods should be called. AspectJ provides five annotations for defining advice, as listed in table 4.2.

It’s a shame that you had to repeat that same pointcut expression four times.It’d be nice if you could define the pointcut once and then reference it every time you need it.上面的相同的表达式重复了4次。
Fortunately, there’s a way: the @Pointcut annotation defines a reusable pointcut within an @AspectJ aspect. The next listing shows the Audience aspect, updated to use @Pointcut.

package concert;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Audience {
@Pointcut("execution(** concert.Performance.perform(..))")  //Define named pointcut
public void performance() {}
@Before("performance()")
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
}
@Before("performance()")
public void takeSeats() {
System.out.println("Taking seats");
}
@AfterReturning("performance()")
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
@AfterThrowing("performance()")
public void demandRefund() {
System.out.println("Demanding a refund");
}
}

The body of the performance() method is irrelevant and, in fact, should be empty. The method itself is just a marker, giving the @Pointcut annotation something to attach itself to.

Note that aside from the annotations and the no-op performance() method, the Audience class is essentially a POJO. Its methods can be called just like methods on any other Java class. Its methods can be individually unit-tested just as in any other Java class. Audience is just another Java class that happens to be annotated to be used as an aspect. And, just like any other Java class, it can be wired as a bean in Spring:

@Bean
public Audience audience() {
return new Audience();
}

If you were to stop here, Audience would only be a bean in the Spring container. Even though it’s annotated with AspectJ annotations, it wouldn’t be treated as an aspect without something that interpreted those annotations and created the proxies that turn it into an aspect.如果没有proxies,那么Audience就只会被当成一个bean。

If you’re using JavaConfig, you can turn on auto-proxying by applying the @EnableAspectJAutoProxy annotation at the class level of the configuration class. The following configuration class shows how to enable auto-proxying in JavaConfig.

package concert;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy  //Enable AspectJ auto-proxying
@ComponentScan
public class ConcertConfig {
@Bean
public Audience audience() {  //Declare Audience bean
return new Audience();
}
}

4.3.2Creating around advice

Around advice is the most powerful advice type. It allows you to write logic that completely wraps the advised method. It’s essentially like writing both before advice and after advice in a single advice method.
改写上面的例子:

package concert;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class Audience {
@Pointcut("execution(** concert.Performance.perform(..))")  //Declare named pointcut
public void performance() {}
@Around("performance()")  //Around advice method
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}
}

As you can see, the effect of this advice is identical to what you did earlier with before and after advice. But here it’s all in a single advice method, whereas before it was spread across four distinct advice methods.

4.3.3Handling parameters in advice

What if your aspect was to advise a method that does take parameters? Could the aspect access the parameters that are passed into the method and use them?有参数的情况

To illustrate, let’s revisit the BlankDisc class from section 2.4.4. As it is, the play() method cycles through all the tracks and calls playTrack() for each track. But you could call the playTrack() method directly to play an individual track.

Suppose you want to keep a count of how many times each track is played. One way to do this is to change the playTrack() method to directly keep track of that count each time it’s called. But track-counting logic is a separate concern from playing a track and therefore doesn’t belong in the playTrack() method. This looks like a job for an aspect.

下面是一个带参数的一个完整的例子,仔细看。
To keep a running count of how many times a track is played, let’s create TrackCounter, an aspect that advises playTrack(). The following listing shows just such an aspect.

package soundsystem;
import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class TrackCounter {
private Map<Integer, Integer> trackCounts =
new HashMap<Integer, Integer>();
@Pointcut(
"execution(* soundsystem.CompactDisc.playTrack(int)) " +//Advise the playTrack() method
"&& args(trackNumber)")
public void trackPlayed(int trackNumber) {}
@Before("trackPlayed(trackNumber)")  //Count a track before it’s played
public void countTrack(int trackNumber) {
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}
public int getPlayCount(int trackNumber) {
return trackCounts.containsKey(trackNumber)
? trackCounts.get(trackNumber) : 0;
}
}

通过上面的代码可以看出,aspect相当于一种服务,它实现了在一些方法的前后等时间节点上做出一些功能处理。例如,在演出前关闭手机,演出成功后鼓掌,以及在播放前统计播放次数然后加1。

The thing to focus on in the figure is the args(trackNumber) qualifier in the pointcut expression. This indicates that 1. any int argument that is passed into the execution of playTrack() should also be passed into the advice. 2. The parameter name, trackNumber, also matches the parameter in the pointcut method signature.

Now you can 1. configure BlankDisc and TrackCounter as beans in the Spring configuration and 2. enable AspectJ auto-proxying, as shown next.

package soundsystem;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy  //Enable AspectJ auto-proxying
public class TrackCounterConfig {
@Bean
public CompactDisc sgtPeppers() {  //CompactDisc bean
BlankDisc cd = new BlankDisc();
cd.setTitle("Sgt. Pepper's Lonely Hearts Club Band");
cd.setArtist("The Beatles");
List<String> tracks = new ArrayList<String>();
tracks.add("Sgt. Pepper's Lonely Hearts Club Band");
tracks.add("With a Little Help from My Friends");
tracks.add("Lucy in the Sky with Diamonds");
tracks.add("Getting Better");
tracks.add("Fixing a Hole");
// ...other tracks omitted for brevity...
cd.setTracks(tracks);
return cd;
}
@Bean
public TrackCounter trackCounter() {  //TrackCounter bean
return new TrackCounter();
}
}

Finally, to prove that this all works, you can write the following simple test. It plays a few tracks and then asserts the play count through the TrackCounter bean.

//Testing the TrackCounter aspect
package soundsystem;
import static org.junit.Assert.*;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=TrackCounterConfig.class)
public class TrackCounterTest {
@Rule
public final StandardOutputStreamLog log =
new StandardOutputStreamLog();
@Autowired
private CompactDisc cd;
@Autowired
private TrackCounter counter;
@Test
public void testTrackCounter() {
cd.playTrack(1);  //Play some tracks
cd.playTrack(2);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(7);
cd.playTrack(7);
assertEquals(1, counter.getPlayCount(1));  //Assert the expected counts
assertEquals(1, counter.getPlayCount(2));
assertEquals(4, counter.getPlayCount(3));
assertEquals(0, counter.getPlayCount(4));
assertEquals(0, counter.getPlayCount(5));
assertEquals(0, counter.getPlayCount(6));
assertEquals(2, counter.getPlayCount(7));
}
}

The aspects you’ve worked with thus far wrap existing methods on the advised object. But method wrapping is just one of the tricks that aspects can perform. Let’s see how to write aspects that introduce completely new functionality into an advised object.

4.3.4Annotating introductions

Some languages, such as Ruby and Groovy, have the notion of open classes. They make it possible to add new methods to an object or class without directly changing the definition of those objects or classes. Unfortunately, Java isn’t that dynamic. Once a class has been compiled, there’s little you can do to append new functionality to it.

In fact, using an AOP concept known as introduction, aspects can attach new methods to Spring beans.

Recall that in Spring, aspects are proxies that implement the same interfaces as the beans they wrap. What if, in addition to implementing those interfaces, the proxy is also exposed through some new interface? Then any bean that’s advised by the aspect will appear to implement the new interface, even if its underlying implementation class doesn’t. Figure 4.7 illustrates how this works.

Notice that when a method on the introduced interface is called, the proxy delegates the call to some other object that provides the implementation of the new interface. Effectively, this gives you one bean whose implementation is split across multiple classes.

Putting this idea to work, let’s say you want to introduce the following Encoreable interface to any implementation of Performance:

package concert;
public interface Encoreable {
void performEncore();
}

Fortunately, AOP introductions can help you without compromising design choices or requiring invasive changes to the existing implementations. To pull it off, you create a new aspect:

package concert;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value="concert.Performance+",
defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable;
}

As you can see, EncoreableIntroducer is an aspect. But unlike the aspects you’ve created so far, it doesn’t provide before, after, or around advice. Instead, it introduces the Encoreable interface to Performance beans using the @DeclareParents annotation.

The @DeclareParents annotation is made up of three parts:

1.The value attribute identifies the kinds of beans that should be introduced with the interface. In this case, that’s anything that implements the Performance interface. (The plus sign at the end specifies any subtype of Performance, as opposed to Performance itself.)
2.The defaultImpl attribute identifies the class that will provide the implementation for the introduction. Here you’re saying that DefaultEncoreable will provide that implementation.
3.The static property that is annotated by @DeclareParents specifies the interface that’s to be introduced. In this case, you’re introducing the Encoreable interface.

4.4Declaring aspects in XML

功能一样,换成XML实现方式而已。

4.5Injecting AspectJ aspects

Although Spring AOP is sufficient for many applications of aspects, it’s a weak AOP solution when contrasted with AspectJ. AspectJ offers many types of pointcuts that aren’t possible with Spring AOP.

Constructor pointcuts, for example, are convenient when you need to apply advice on the creation of an object. Unlike constructors in some other object-oriented languages, Java constructors are different from normal methods. This makes Spring’s proxy-based AOP woefully inadequate for advising the creation of an object.

For the most part, AspectJ aspects are independent of Spring. Although they can be woven into any Java-based application, including Spring applications, there’s little involvement on Spring’s part in applying AspectJ aspects.

But any well-designed and meaningful aspect will likely depend on other classes to assist in its work. If an aspect depends on one or more classes when executing its advice, you can instantiate those collaborating objects with the aspect itself. Or, better yet, you can use Spring’s dependency injection to inject beans into AspectJ aspects.

然后介绍了具体的做法,用到再看。

4.6Summary

AOP is a powerful complement to object-oriented programming.

第一部分完结!

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值