《Spring4实战 第4版》2016年4月新出版的,之前的第三版看起来还是不错的,所以看到新版就直接买下来。
英文版源码地址:Spring in Action, Fourth Edition Covers Spring 4
1.装配Bean
参考【Spring实战4 2.2】
1.1接口只有一个现实类
可以自动装配
public interface CompactDisc { void play(); }
import org.springframework.stereotype.Component; @Component public class SgtPeppers implements CompactDisc { private String title = "Sgt. Pepper's Lonely Hearts Club Band"; private String artist = "http://blog.csdn.net/unix21"; public void play() { System.out.println("【非常醒目SgtPeppers 】>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist); } }
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class CDPlayerConfig { }
单元测试
import static org.junit.Assert.*; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; 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 play() { cd.play(); } }
【参考 Spring实战4 3.3】
故意再写一个实现类
import org.springframework.stereotype.Component; @Component public class SgtPeppersNew implements CompactDisc { private String title = "Sgt. Pepper's Lonely Hearts Club Band"; private String artist = "http://blog.csdn.net/unix21"; public void play() { System.out.println("【非常醒目 SgtPeppersNew】>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist); } }
如果这个时候运行肯定会报错NoUniqueBeanDefinitionException: No qualifying bean of type
解决方法有两种
第一种 在实现类上 标识首选的bean,使用@Primary
import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component @Primary public class SgtPeppers implements CompactDisc { private String title = "Sgt. Pepper's Lonely Hearts Club Band"; private String artist = "http://blog.csdn.net/unix21"; public void play() { System.out.println("【非常醒目SgtPeppers 】>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist); } }
第二种 使用@Qualifier注解
import static org.junit.Assert.*; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = CDPlayerConfig.class) public class CDPlayerTest { @Autowired @Qualifier("sgtPeppersNew") private CompactDisc cd; @Test public void play() { cd.play(); } }
需要注意的是bean id的首字母是类名小写。
spring @Qualifier注解
1.3 为组件扫描的bean命名
【参考 Spring实战4 2.2.2】
import org.springframework.stereotype.Component; @Component("spn") public class SgtPeppersNew implements CompactDisc {
@Autowired @Qualifier("spn") private CompactDisc cd;
import javax.inject.Named; @Named("spn") public class SgtPeppersNew implements CompactDisc {
1.4 设定组件扫描的指定包
【参考 Spring实战4 2.2.3】
如果@ComponentScan默认不设置只扫描配置类所在的包作为基础包。
@Configuration @ComponentScan("blog.csdn.net.unix21") public class CDPlayerConfigTest {设置@ComponentScan的value属性就可以指明包名称。
如果想更清晰的表明设置的是基础包
@ComponentScan(basePackages="指定包")
指定多个
@ComponentScan(basePackages={"指定包1","指定包2"})
也可以将其指定为包中所包含的类或者接口
@ComponentScan(basePackages={"XXX.class","XX.class"})
1.5 自动装配
【参考 Spring实战4 2.2.4】
声明自动装配需要@Autowired注解
1.5.1 在构造方法上使用自动装配
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = CDPlayerConfigTest.class) public class CDPlayerFunTest { private CompactDisc cd; @Autowired @Qualifier("spn") public void CDPlayer(CompactDisc cd) { this.cd = cd; } @Test public void play() { cd.play(); System.out.println("【占位符】CDPlayerFunTest"); } }
另一种写法
@Component public class CDPlayer implements MediaPlayer { private CompactDisc cd; @Autowired public CDPlayer(@Qualifier("spn")CompactDisc cd) { this.cd = cd; } public void play() { cd.play(); } }
1.5.2 在属性Setter方法上使用自动装配
@Component public class CDPlayer implements MediaPlayer { private CompactDisc cd; @Autowired @Qualifier("spn") public void setCompactDisc(CompactDisc cd) { this.cd = cd; } public void play() { cd.play(); } }
@Autowired是Spring特有的注解,可以替换为@Inject,@Inject来源自Jave依赖注入规范。
1.6 创建自定义的限定符
【参考 Spring实战4 3.3.2】
@Component @Qualifier("cold") public class IceCream implements CompactDisc { private String title = "Sgt. Pepper's Lonely Hearts Club Band"; private String artist = "The Beatles"; public void play() { System.out.println("【非常醒目 IceCream】>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist); } }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=CDPlayerConfigTest.class) public class CDPlayerLogTest { @Autowired private MediaPlayer player; @Autowired @Qualifier("sp") private CompactDisc cd; @Autowired @Qualifier("cold") private CompactDisc cd2; @Test public void cdShouldNotBeNull() { assertNotNull(cd); } @Test public void play() { player.play(); cd.play(); cd2.play(); } }
好处:这样做的好处限定符不耦合类名,所以可以随意重构类名。
问题:重复的限定符出现在多个类上这是不允许的,因为Java不允许同一个条目上重复出现相同类型的多个注解。
1.7 使用自定义限定符注解
针对上述问题可以创建自定义的限定符注解。
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到 @Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})//定义注解的作用目标**作用范围字段、枚举的常量/方法 @Qualifier public @interface Cold {}
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到 @Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})//定义注解的作用目标**作用范围字段、枚举的常量/方法 @Qualifier public @interface Creamy {}
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到 @Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})//定义注解的作用目标**作用范围字段、枚举的常量/方法 @Qualifier public @interface Fruity {}
@Component @Cold @Creamy public class IceCream implements CompactDisc { private String title = "Spring 实现 第4版 读书笔记"; private String artist = "http://blog.csdn.net/unix21"; public void play() { System.out.println("【非常醒目 IceCream】>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist); } }
@Component @Cold @Fruity public class Popsicle implements CompactDisc { private String title = "Spring 实现 第4版 读书笔记"; private String artist = "http://blog.csdn.net/unix21"; public void play() { System.out.println("【非常醒目 Popsicle】>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist); } }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = CDPlayerConfigTest.class) public class CDPlayerLogTest { @Autowired private MediaPlayer player; @Autowired @Qualifier("sp") private CompactDisc cd; @Autowired @Cold @Creamy private CompactDisc cd2; @Autowired @Cold @Fruity private CompactDisc cd3; @Test public void cdShouldNotBeNull() { assertNotNull(cd); } @Test public void play() { player.play(); cd.play(); cd2.play(); cd3.play(); } }
1.8 bean的作用域
Spring定义了多重作用域,singleton单例,prototype原型等
参考:spring中scope作用域
singleton单例:整个应用中,只创建bean的一个实例,默认Spring上下文中所有的bean都是单例。
prototype原型:每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
@Component public class Add implements AddI { public int a=0; public void Add() { a++; } public void getA() { System.out.println("【非常醒目 Add】>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>a= " +a+""); } }
public interface AddI { void Add(); void getA(); }
@Component public class CDPlayer implements MediaPlayer { @Autowired @Qualifier("sp") private CompactDisc cd; @Autowired private AddI a; public void play() { System.out.println("【非常醒目 CDPlayer】>>>"); cd.play(); a.Add(); a.getA(); a.Add(); a.getA(); System.out.println("【非常醒目 CDPlayer】<<<"); } }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = CDPlayerConfigTest.class) public class CDPlayerLogTest { @Autowired private MediaPlayer player; @Autowired @Qualifier("sp") private CompactDisc cd; @Autowired @Cold @Creamy private CompactDisc cd2; @Autowired @Cold @Fruity private CompactDisc cd3; @Test public void cdShouldNotBeNull() { assertNotNull(cd); } @Autowired private AddI a; @Test public void play() { player.play(); cd.play(); cd2.play(); cd3.play(); a.getA(); } }
再写一个多线程
public class ClientThread extends Thread { @Autowired private AddI a; @Autowired public ClientThread(AddI a) { this.a = a; } public void run() { a.Add(); a.getA(); } }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = CDPlayerConfigTest.class) public class SpringScopeTest { @Autowired private AddI a; @Test public void Scope() { for (int i = 0; i < 10; i++) { ClientThread t = new ClientThread(a); t.start(); } } }
改为SCOPE_PROTOTYPE
@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) public class Add implements AddI { public int a=0; public void Add() { a++; } public void getA() { System.out.println("【非常醒目 Add】>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>a= " +a+""); } }
看到差异了吧。
补充说明:@Repository、@Service、@Controller 和 @Component 将类标识为Bean,都是一样的,用在不同的地方而已。