1实例化SpringIoC容器
问题:
你必须实例化Spring IoC容器,读取配置来创建bean实例。然后,你可以从Spring IoC容器中得到可用的bean实例。
解决方案:
Spring提供了两种IoC容器实现类型。基本的一种称为Bean工厂(Bean Factory)。另一种称为应用程序上下文(Application Context),这是对bean工厂的一种兼容扩展。
这两种IoC容器类型和Bean配置文件相同。
ApplicationContext接口是用于保持兼容性的BeanFactory的子接口。
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>3.1.1.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies>
2配置Spring IoC容器中的bean
问题:
Spring提供了一个强大的IoC容器来管理组成应用的Bean。为了使用,你必须配置。
解决方案:
你可以通过XML配置文件、属性文件等方式进行配置。
在简单应用中你可以使用单个配置文件,但是在庞大的系统中一般都会分模块存在多个配置文件或者按照架构层次进行分割。
工作原理:
helloworld:
1、两种基本的简单IoC
public class Love { private String name; private int age; /*public Love() { super(); }*/ public Love(String name, int age) { super(); this.name = name; this.age = age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public String toString() { return "Love [name=" + name + ", age=" + age + "]"; } } <bean id="loveSet" class="com.partner4java.Love"> <property name="name" value="gaofumei"></property> <property name="age" value="16"></property> </bean> <bean id="loveConstructor" class="com.partner4java.Love"> <constructor-arg name="name" value="gaofumei"/> <constructor-arg name="age" value="18"/> </bean> /** * 对两种注入类型进行测试,如果是方法注入,Love类中如果包含有构造器,还必须包含无参数的构造器<br/> * (这个错误不是在第一次获取bean的时候报出来的,是在初始化之后报的,因为单例类型的bean会在初始化时期完成,你可以加上 scope="prototype"证明这一点) * * @author partner4java * */ public class LoveTest { private ApplicationContext applicationContext; @Before public void setUp() throws Exception { applicationContext = new ClassPathXmlApplicationContext( "/spring/beans1.xml"); } @Test public void testSet() { Love love = (Love) applicationContext.getBean("loveSet"); System.out.println(love); } @Test public void testConstructor() { Love love = (Love) applicationContext.getBean("loveConstructor"); System.out.println(love); } } spring2.0之后又新增了缩写的配置方式: xmlns:p="http://www.springframework.org/schema/p" <bean id="loveShort" class="com.partner4java.Love" p:name="gaofumei" p:age="20"/> @Test public void testShort() { Love love = (Love) applicationContext.getBean("loveShort"); System.out.println(love); } 比着前面,我们就更加简洁了
public class Wife { private List<String> wifeNames; public void setWifeNames(List<String> wifeNames) { this.wifeNames = wifeNames; } @Override public String toString() { return "Wife [wifeNames=" + wifeNames + "]"; } } <bean id="wife" class="com.partner4java.Wife"> <property name="wifeNames"> <list> <value>gaofumei</value> <value>xiaoneinv</value> </list> </property> </bean> public class WifeTest { private ApplicationContext applicationContext; @Before public void setUp() throws Exception { applicationContext = new ClassPathXmlApplicationContext( "/spring/beans2.xml"); } @Test public void testSetWifeNames() { Wife wife = (Wife) applicationContext.getBean("wife"); System.out.println(wife); } }
3调用构造程序创建bean
问题:
你想要调用构造程序在Spring IoC容器中创建 一个bean,这是创建Bean最常见和直接的方法。
解决方案:
通常,当你为一个Bean指定class属性,就将要求Spring IoC容器调用构造程序创建Bean实例。
工作原理:
执行过程 FileSystemXmlApplicationContext ↓ org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:464) 刷新入口 ↓ org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:913) 实例化所有的(non-lazy-init)单例 ↓ org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:585) Instantiate all remaining (non-lazy-init) singletons,进行遍历 ↓ org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193) 调用获取动作doGetBean ↓ org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291) 获取共享实例sharedInstance ↓ org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225) 创建了一个内部的ObjectFactory,定义了一个内部getObject方法,然后 ↓ org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294) 调用父类实现的createBean方法 ↓ org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456) 判断一些条件后调用执行方法doCreateBean ↓ org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517) 大部分实例动作的包装类(包括485的createBeanInstance--构建实例、) ↓ org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1118) 填充数据(下面就是判断、遍历填充数据) ↓ org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1360) ↓ org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:153) ↓ org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveManagedList(BeanDefinitionValueResolver.java:353) ↓ org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:194) AbstractAutowireCapableBeanFactory的:createBeanInstance 方法最后面 // Need to determine the constructor... Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } // No special handling: simply use no-arg constructor. return instantiateBean(beanName, mbd);
4解决构造程序歧义
问题:
当你为Bean指定一个或者多个构造程序参数时,Spring将视图在Bean类中查找对应的构造程序,并且传递用于Bean实例化的参数。
解决方案:
你可以为<constructor-arg>元素指定type和index属性,帮助Spring查找预期的构造程序。
工作原理:
从下面的地方开始说
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517) ↓ createBeanInstance方法中 // Need to determine the constructor... Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } 判断了,如果有构造IoC,那么进行... ↓ 获取BeanWrapper return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs); ↓ autowireConstructor进行了具体的index和type匹配
5指定Bean引用
问题: 组成应用程序的Bean往往需要互相协作完成应用功能。为了Bean之间的互相访问,你必须在Bean配置文件中指定Bean引用。 解决方案: 在Bean配置文件中,你可以用<ref>元素为Bean属性或者构造程序参数指定Bean引用。 只需要用<value>元素指定一个值。 工作原理: <bean id="user" class="com.partner4java.ref.User"> <property name="username" value="gaolumei"></property> <property name="age" value="18"></property> </bean> <bean id="buy" class="com.partner4java.ref.Buy"> <property name="user" ref="user"></property> </bean> public class RefTest { private ApplicationContext applicationContext; @Before public void setUp() throws Exception { applicationContext = new ClassPathXmlApplicationContext( "/spring/beans3.xml"); } @Test public void testBuy(){ System.out.println(applicationContext.getBean("buy")); } } AbstractBeanFactory.doGetBean调用获取bean --> AbstractAutowireCapableBeanFactory.applyPropertyValues加载参数(IoC操作) --> BeanDefinitionValueResolver.resolveReference(获取ref的对应的bean)
6为集合元素指定数据类型
问题:
默认情况下,Spring将集合中所有元素作为字符串对待。
如果你不打算将集合元素作为字符串使用,就必须为他们指定数据类型。
解决方案:
可以使用<value>的type指定,也可以在集合标记中指定value-type
工作原理:
public class Cache { private List<String> wifes; public List<String> getWifes() { return wifes; } public void setWifes(List<String> wifes) { this.wifes = wifes; } @Override public String toString() { return "Cache [wifes=" + wifes + "]"; } } <bean id="cache" class="com.partner4java.type.Cache"> <property name="wifes"> <list value-type="java.lang.String"> <value>gaofumei</value> <value>dachangtui</value> </list> </property> </bean>
7使用Spring的FactoryBean创建bean
问题:
你可能希望用Spring的工厂Bean在Spring IoC容器中创建Bean。
工厂Bean(Factory Bean)是作为创建IoC容器中其他Bean的工厂的一个FactoryBean。概念上,工厂Bean与工厂方法非常类似,但是他是Bean构造期间可以Spring IoC容器识别为Spring专用Bean。
解决方案:
工厂Bean的要求是实现FactoryBean接口。为了方便,提供了抽象模板类AbstractFactoryBean供你扩展。
工厂Bean主要用于实现框架机制。如:
·在JNDI中查找对象(例如一个数据源)时,你可以使用JndiObjectFactoryBean。
·使用经典Spring AOP为一个Bean创建代理时,可以使用ProxyFactoryBean。
·在IoC容器中创建一个Hibernate会话工厂时,可以使用LocalSessionFactoryBean。
工作原理:
尽管你很少有必要编写自定义的工厂Bean,但是会发现通过一个实例来理解其内部机制很有帮助。
通过扩展AbstractFactoryBean类,你的工厂bean能够重载createInstance()方法以创建目标Bean实例。
此外,你必须getObjectType()方法中返回目标Bean的类型,是自动装配(Auto-wiring)功能正常工作。
名称之前添加&,可以得到工厂Bean的实例。
public class DiscountFactoryBean extends AbstractFactoryBean<User> { private User user; public void setUser(User user) { this.user = user; } @Override public Class<?> getObjectType() { return user.getClass(); } @Override protected User createInstance() throws Exception { return user; } } <bean id="xiaomei" class="com.partner4java.DiscountFactoryBean"> <property name="user"> <bean class="com.partner4java.ref.User"> <property value="xiaomeimei" name="username"></property> <property value="18" name="age"></property> </bean> </property> </bean> @Test public void testFactoryBean() throws Exception { User user = (User) applicationContext .getBean("xiaomei"); System.out.println(user); } @Test public void testNewFactoryBean() throws Exception { DiscountFactoryBean discountFactoryBean = new DiscountFactoryBean(); User user = new User(); user.setUsername("gaofumei"); user.setAge(16); discountFactoryBean.setUser(user); System.out.println(discountFactoryBean.createInstance()); } @Test public void testGetFactoryBean() throws Exception { DiscountFactoryBean discountFactoryBean = (DiscountFactoryBean) applicationContext.getBean("&xiaomei"); System.out.println(discountFactoryBean.createInstance()); }
8使用工厂Bean和Utility Schema定义集合
问题:
使用基本集合标记定义集合时,你不能指定集合的实体类,例如LinkedList、TreeSet或TreeMap,而且,你不能通过将集合定义为可供其他Bean引用的单独Bean在不同的Bean中共享集合。
解决方案:
两种方式
1、使用对应的集合工厂Bean,如ListFactoryBean、SetFactoryBean和MapFactoryBean。
2、引入util schema中使用集合标记,如<util:list>、<util:set>和<util:map>。
工作原理:
public class SetBean { private Set<String> name; public Set<String> getName() { return name; } public void setName(Set<String> name) { this.name = name; } @Override public String toString() { return "SetBean [name=" + name + "]"; } } <bean id="names1" class="org.springframework.beans.factory.config.SetFactoryBean"> <property name="sourceSet"> <set> <value>gaofumei</value> <value>小甜甜</value> </set> </property> </bean> <bean id="setBean1" class="com.partner4java.SetBean"> <property name="name" ref="names1"></property> </bean> <util:set id="names2"> <value>清纯</value> <value>小甜甜</value> </util:set> <bean id="setBean2" class="com.partner4java.SetBean"> <property name="name" ref="names2"></property> </bean> @Test public void testGet1() throws Exception { System.out.println(applicationContext.getBean("setBean1")); } @Test public void testGet2() throws Exception { System.out.println(applicationContext.getBean("setBean2")); }
9用依赖检查属性
问题:
在大规模的应用中,IoC容器中可能声明了几百个甚至几千上万个Bean,这些Bean之间的依赖往往非常复杂。
设置方法注入的不足之一是无法确定一个属性将会被注入。
检查所有必要的属性是否已经设置是非常困难的。
解决方案:
dependency-check --
none* 不执行任何依赖检查,任何属性都可以保持未设置状态
simple 如果任何简单类型(原始和集合类型)的属性未设置,将抛出UnsatisfiedDependencyException异常
objects 如果任何对象类型属性没有设置,将抛出UnsatisfiedDependencyException异常
all 如果任何类型的属性为设置,将抛出UnsatisfiedDependencyException异常
工作原理:
不过在新的版本中已经废弃
10用@Required注解检查属性
问题: Spring的依赖检查功能仅能检查某些类型的所有属性。他的灵活性不够,不能仅检查特定的属性。 解决方案: RequiredAnnotationBeanPostProcessor是一个Spring bean后处理器,检查带有@Required注解的所有bean属性是否设置。 bean后处理器是一类特殊的Spring bean,能够在每个Bean初始化之前执行附加的工作。 为了启用这个Bean后处理器进行属性检查,必须在Spring IoC容器中注册他。 工作原理: Caused by: org.springframework.beans.factory.BeanInitializationException: Property 'username' is required for bean 'user' public class User { private String username; private int age; public String getUsername() { return username; } @Required public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [username=" + username + ", age=" + age + "]"; } } <context:annotation-config /> <bean id="user" class="com.partner4java.ref.User"> <property value="18" name="age"></property> </bean> 还可以自定义注解: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Mandatory { } public class User { private String username; private int age; public String getUsername() { return username; } @Mandatory public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [username=" + username + ", age=" + age + "]"; } } <context:annotation-config/> <!-- 不需要额外再通知谁 --> <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"> <property name="requiredAnnotationType"> <value>com.partner4java.Mandatory</value> </property> </bean> <bean id="user" class="com.partner4java.ref.User"> <property value="18" name="age"></property> </bean>
11用XML配置自动装配Bean
问题:
当一个Bean需要访问另一个Bean时,你可以显示指定引用装配他。但是,如果你的容器能够自动装配bean,就可以免去手工手工配置装配的麻烦。
解决方案:
autowire属性--
no* 不执行自动装配。你必须显示的装配依赖
byName 对于每个Bean属性,装配一个同名的bean
byType 对于每个Beam属性,装配类型与之兼容的Bean。如果超过一个,将抛出UnsatisfiedDependencyException异常。
Constructor 对于每个构造程序参数,首先寻找与参数兼容的Bean。然后,选择具有最多参数的构造程序。对于存在歧义的情况,将抛出UnsatisfiedDependencyException异常。
autoetect 如果找到一个没有参数的默认构造程序,依赖讲按照类型自动装配。否则,将由构造程序自动装配。
(不建议用自动装配)
工作原理:
public class User { private String username; private int age; public String getUsername() { return username; } @Mandatory public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [username=" + username + ", age=" + age + "]"; } } public class Teacher { private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String toString() { return "Teacher [user=" + user + "]"; } } <bean id="user" class="com.partner4java.ref.User"> <property name="username" value="xiaomeimei"></property> <property value="18" name="age"/> </bean> <bean id="teacher" class="com.partner4java.Teacher" autowire="byName"> </bean> @Test public void testCheck(){ System.out.println(applicationContext.getBean("teacher")); } 后台打印: Teacher [user=User [username=xiaomeimei, age=18]]
12用@Autowired和@Resource自动装配Bean
问题:
在Bean配置文件中设置autowire属性进行的自动装配将装配一个Bean的所有属性。这样的灵活性不足以紧紧装配特定的属性。
而且,你只能通过类型或者名称自动装配Bean。
如果这两种策略都不能满足你的需求,就必须明确的装配Bean。
解决方案:
你可以通过@Autowired或者@Resource注解一个设置方法、构造程序、字段甚至任意方法自动装配特定的属性。
这意味着你除了设置autowire属性之外,还有一个能够满足需求的选择。
工作原理:
<context:annotation-config />
@Autowired、@Qualifier("mainCatalog")、@Resource(name="myMovieFinder")
13继承Bean配置
问题: 在Spring IoC容器中配置Bean时,你可能拥有超过一个共享某些公用配置的Bean,比如属性和<bean>元素中的属性。你常常必须为多个Bean重复这些配置。 解决方案: 只作为模板而不能检索,必须将abstract设置为true,要求spring不实例化这个bean。 并不是所有在父<bean>元素中定义的属性都将被继承,例如,autowire和dependency-check属性不会从父bean中继承。 工作原理: public abstract class Water { private int weight; private int depth; public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public int getDepth() { return depth; } public void setDepth(int depth) { this.depth = depth; } @Override public String toString() { return "Water [weight=" + weight + ", depth=" + depth + "]"; } } public class Coke extends Water { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Coke [name=" + name + ", toString()=" + super.toString() + "]"; } } public class Energy extends Water { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Energy [name=" + name + ", toString()=" + super.toString() + "]"; } } <!-- 第一种:常用的模板模式,抽象接口里面放了基本的资源,子类放了具体的资源 --> <bean id="water" class="com.partner4java.parent.Water" abstract="true"> <property name="weight" value="100"></property> <property name="depth" value="10"></property> </bean> <bean id="coke" class="com.partner4java.parent.Coke" parent="water"> <property name="name" value="coke"></property> </bean> <bean id="energy" class="com.partner4java.parent.Energy" parent="water"> <property name="name" value="Energy"></property> </bean> <!-- 子bean没有指定class也可以 --> <bean id="mineralWater" parent="energy"> <property name="weight" value="1000"></property> </bean> <!-- 第二种:只定义一个抽象资源,然后让bean去继承 --> <bean id="noWater" abstract="true"> <property name="weight" value="100"></property> <property name="depth" value="10"></property> </bean> <bean id="noEnergy" class="com.partner4java.parent.Energy" parent="noWater"> <property name="name" value="Energy"></property> </bean> public class ParentTest { private ApplicationContext applicationContext; @Before public void setUp() throws Exception { applicationContext = new ClassPathXmlApplicationContext( "/spring/beans13_parent.xml"); } @Test public void testCoke(){ System.out.println(applicationContext.getBean("coke")); } @Test public void testEnergy(){ System.out.println(applicationContext.getBean("energy")); } @Test public void testMineralWater(){ System.out.println(applicationContext.getBean("mineralWater")); } @Test public void testNoEnergy(){ System.out.println(applicationContext.getBean("noEnergy")); } }
14从Classpath中扫描组件
问题: 为了便于Spring IoC容器对组件的管理,你需要在Bean配置中逐个声明他们。 但是,如果Spring能够自动地检测你的组件而不需要手工配置,将大大节省你的工作量。 解决方案: Spring提供了一个强大的功能--组件扫描@Component. @Repository\@Service\@Controller:持久层、服务层和表现层。 工作原理: <?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:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <context:annotation-config /> <context:component-scan base-package="com.partner4java" /> </beans> public interface UserDao { public void save(Integer id); public void delete(Integer id); public boolean get(Integer id); } @Scope("singleton") @Repository public class UserDaoImpl implements UserDao { private List<Integer> users = new LinkedList<Integer>(); @Override public void save(Integer id) { users.add(id); } @Override public void delete(Integer id) { users.remove(id); } @Override public boolean get(Integer id) { return users.contains(id); } } public interface UserService { public void save(Integer id); public void delete(Integer id); public boolean get(Integer id); } @Service public class UserServiceImpl implements UserService { private UserDao userDao; @Autowired public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void save(Integer id) { userDao.save(id); } @Override public void delete(Integer id) { userDao.delete(id); } @Override public boolean get(Integer id) { return userDao.get(id); } } public class ScanTest { private ApplicationContext applicationContext; @Before public void setUp() throws Exception { applicationContext = new ClassPathXmlApplicationContext( "/spring/beans14_scan.xml"); } @Test public void testAll(){ UserServiceImpl userServiceImpl = (UserServiceImpl) applicationContext.getBean("userServiceImpl"); userServiceImpl.save(new Integer(1)); userServiceImpl.save(new Integer(2)); userServiceImpl.delete(new Integer(2)); System.out.println(userServiceImpl.get(1)); System.out.println(userServiceImpl.get(2)); } }