~~定义一个切面,在切面的纵向定义处理方法,处理完成之后,回到横向业务流。
依赖注入是指程序运行过程中,如果需要另一个对象写作时,无须在代码中创建被调用者,而是依赖于外部容器的注入。
依赖注入通常有两种:
1、设值注入:IOC容器使用属性的Setter方法来注入被依赖的实例。
2、构造注入:IOC容器使用构造器来注入被依赖的实例。
由于大量的构造器参数可能使程序变得笨拙,特别是当某些属性是可选的时候。因此通常情况下,Spring开发团队提倡使用setter注入。
主要使用构造器注入的方式配置bean时,很有可能会产生循环依赖的情况。对于此问题,一个可能的解决方法就是修改源代码,将构造器注入改为setter注入。另一个解决方法就是完全放弃使用构造器注入,只使用setter注入。
Spring容器最直接的接口就是BeanFactory
实例化Spring容器BeanFactory
Resource resource = new FileSystemResource("beans.xml"); BeanFactory factory = new XmlBeanFactory(resource);
ClassPathResource resource = new ClassPathResource("beans.xml"); BeanFactory factory = new XmlBeanFactory(resource);
ApplicationContext context = new ClassPathXmlApplicationContext( new String[] {"applicationContext.xml", "applicationContext-part2.xml"}); // of course, an ApplicationContext is just a BeanFactory BeanFactory factory = (BeanFactory) context;
ApplicationContext接口
它由BeanFactory
接口派生而来,因而提供了BeanFactory
所有的功能。为了以一种更向面向框架的方式工作以及对上下文进行分层和实现继承,context包还提供了以下的功能:
MessageSource
, 提供国际化的消息访问
资源访问,如URL和文件
事件传播,实现了ApplicationListener
接口的bean
载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
由于ApplicationContext
包括了BeanFactory
所有的功能,所以通常建议优先采用ApplicationContext
。
Bean可以获取Spring容器
实现BeanFactoryAware的Bean可以访问创建这个Bean的BeanFactory。
实现ApplicationContextAware的Bean可以访问创建这个Bean的Spring容器。
但是实现ApplicationContextAware接口让Bean拥有了访问容器的能力,污染了代码,导致代码与Spring接口耦合在一起。所以不是特别必要,建议不要直接访问容器。
Spring中的Bean
对于开发者来说使用Spring框架所做的就是两件事:1、开发Bean;2、配置Bean
对于Spring框架来说,就是根据配置文件创建Bean实例,并调用Bean实例的方法完成依赖注入,这就是IOC的本质。
配置文件中通过<bean id="xxx" class="lee.xxxClass">方法配置一个Bean时,这样要求该Bean实现类必须有一个无参数的构造器,底层相当于调用了xxx=new lee.xxxclasss()
Bean作用域有singleton单例模式、prototype原型模式、request、session、global session,不指定scope时默认使用singleton。对于Singleton作用域的Bean,如果没有强行取消其预初始化行为,系统会在创建Spring容器的时候预初始化所有Singleton Bean,该Bean所依赖的Bean也被一起初始化。
BeanFactory在需要Bean的时候创建Bean,ApplicationContext在创建ApplicationContext实例的时候就会初始化所有的Bean。所以后者实例化过程会比前者实例化过程的时间和内存开销大,但可以再容器初始化阶段就检验处配置错误。
lazy-init="true"会强制取消Bean实例预初始化。
Spring的作用是管理JavaEE组件,Spring把所有的Java对象都成为Bean,所以可以把任何Java类都部署在Spring容器中,只有该Java类有相应的构造器即可。例如下面的片段:
<bean id="id" class="lee.AClass"> <property name="aaa" value="aVal"></property> <property name="bbb" value="bVal"></property> </bean>这个时候Spring采用类似于这样的方法创建Java实例:
Class targetClass=Class.forName("lee.AClass"); Object bean=targetClass.newInstance();然后又这样注入属性:
String setName1="set"+"Aaa"; Method setMethod1=targetClass.getMethod(setName1,aVal.getClass()); setMethod1.invoke(bean,aVal);
可以用autowire属性自动装配注入bean元素,autowire有四种属性:byName,byType,constructor,autodetect.
byName:根据属性名自动装配
byType:根据属性类型自动装配
constructor:与byType相似,但是是用于构造器注入的参数
autodetect:根据Bean内部结构决定是constructor还是byType,如果找到一个默认的构造函数,就会使用byType。
给bean注入嵌套bean和使用ref引用容器中另一个bean本质是一样的。
public Object getBean(String name, Class requiredType) throws BeansException { assertBeanFactoryActive(); return getBeanFactory().getBean(name, requiredType); } public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;getBeanFactory()方法是一个抽象方法,所以必定在AbstractApplicationContext的子类中实现getBeanFactory方法,然后看其子类,子类AbstractRefreshableApplicationContext中有getBeanFactory方法。
public final ConfigurableListableBeanFactory getBeanFactory() { Object obj = beanFactoryMonitor; JVM INSTR monitorenter ; if(beanFactory == null) throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext"); return beanFactory; Exception exception; exception; throw exception; }
private DefaultListableBeanFactory beanFactory;返回的beanFactory是DefaultListableBeanFactory的实例,然后到这个实例中去看,就可以找到public transient Object getBean(Class requiredType, Object args[])
抽象bean用于继承,可以不指定class。ApplicationContext预初始化会忽略所有的抽象Bean定义。
<bean id="steelAxe" class="org.crazyit.app5.service.impl.SteelAxe"/> <bean id="stoneAxe" class="org.crazyit.app5.service.impl.StoneAxe"/> <!-- 通过abstract属性定义该Bean 是抽象Bean --> <bean id="chineseTemplate" class="org.crazyit.app5.service.impl.Chinese" abstract="true"> <property name="axe" ref="steelAxe"/> </bean> <!-- 通过parent属性定义子bean --> <bean id="chinese" parent="chineseTemplate"> <!-- 覆盖父Bean中依赖关系的配置 --> <property name="axe" ref="stoneAxe"/> </bean>Spring实例化依赖bean:SteelAxe实例...
Spring执行依赖关系注入...
抽象bean,指定abstract="true",它的价值在于被继承,Spring容器会忽略所有的抽象Bean定义,与初始化时不初始化抽象bean。
当有一批Bean配置的大量配置信息完全相同,只有少量配置不同,那么我们可以先为这批bean配置一个bean模板,将这批bean中相同的信息配置成bean模板,因为spring容器无须配置bean模板的实例,所以通常将这个bean模板配成抽象bean。
java中的继承,子类是一种特殊的父类,是类之间的关系,表现为方法,属性的延续。
//配置文件 <?xml version="1.0" encoding="GBK"?> <!-- Spring配置文件的根元素,使用spring-beans-3.0.xsd语义约束 --> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- 通过abstract属性定义该Bean 是抽象Bean --> <bean id="chineseTemplate" class="org.crazyit.app7_8.service.impl.Chinese" abstract="true"> <property name="axe" ref="steelAxe"/> </bean> <!-- 通过parent属性定义子bean --> <bean id="chinese" parent="chineseTemplate"> <!-- 覆盖父Bean中依赖关系的配置 --> <!-- <property name="axe" ref="stoneAxe"/> --> </bean> <!-- 定义两个Axe实例 --> <bean id="steelAxe" class="org.crazyit.app7_8.service.impl.SteelAxe"/> <bean id="stoneAxe" class="org.crazyit.app7_8.service.impl.StoneAxe"/> </beans> //测试代码 public class BeanTest7_8 { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("com/ccnu/xml/bean7_8.xml"); Person p1 = ctx.getBean("chinese" , Person.class); Person p2 = ctx.getBean("chinese" , Person.class); p1.useAxe(); System.out.println(p1 == p2); } } //结果 Spring实例化主调bean:Chinese实例 Spring实例化依赖bean:SteelAxe实例 Spring执行依赖关系注入 Spring实例化依赖bean:StoneAxe实例 钢斧砍柴真快 true在上面代码的执行顺序可以看出,spring容器先按照bean.xml中的singleton顺序开始实例化,ChineseTemplate是抽象类,不实例化,所以先实例化Chinese,然后实例化依赖对象SteelAxe,然后注入依赖对象,最后实例化StoneAxe。
可以通过实现FactoryBean接口来创建容器工厂,当用getBean()方法来获取工厂Bean时,容器不会返回FactoryBean实例,而是返回FactoryBean的产品,如果要返回FactoryBean实例的时候则在Bean id上加上"&"符号。
2、实现Disposable接口(实现destroy()方法)
Sping在容器关闭之前,会先调用destroy()方法,然后调用destroy-method属性对应的方法。
协调作用域不同步的bean:
当Singleton作用域bean依赖prototype作用域bean时,Spring容器初始化时会初始化所有的Singleton bean,此时prototype bean被创建,并注入到Singleton bean,当Singleton bean被创建完后,就持有了一个prototype bean,之后就不会再为Singleton bean执行注入了。所以当客户端多次请求Singleton bean,并调用prototype bean的方法是,始终是调用同一个prototype bean实例,就违背了设置prototype bean的初衷:本来希望它具有prototype行为,但是表现出Singleton行为了。
这个时候一般我们采用方法注入:
方法注入通常使用lookup方法注入,利用lookup方法注入可以让Spring容器重写容器中Bean的抽象或具体方法,返回查找容器中其他Bean的结果,被查找的bean通常是一个non-singleton bean。
例如:
public abstract class Chinese implements Person{ public Chinese() { System.out.println("Spring实例化主调bean:Chinese实例..."); } //定义一个抽象方法,该方法将由Spring负责实现 public abstract Axe getAxe(); public void useAxe() { System.out.println("正在使用 " + getAxe() + "砍柴!"); System.out.println(getAxe().chop()); } } <bean id="steelAxe" class="org.crazyit.app7_9.service.impl.SteelAxe" scope="prototype"/> <bean id="chinese" class="org.crazyit.app7_9.service.impl.Chinese"> <!-- 指定getAxe方法返回steelAxe 每次调用getAxe方法将获取新的steelAxe对象 --> <lookup-method name="getAxe" bean="steelAxe"/> </bean>所以使用lookup方法注入后,系统每次调用getAxe()方法都将生成一个新的SteelAxe实例,这样可以保证当Singleton作用于的Bean需要全新的Bean实例时,直接调用getAxe()方法即可,从而避免一直使用最早注入的bean实例。
在Spring配置文件中使用xml元素进行配置,实际上是让spring执行相应的代码:
1、使用<bean.../>元素,实际上是让Spring执行无参数构造器,或有参数的构造器。
2、使用<property.../>元素,实际上是让Spring执行一次setter方法。
除此之外,spring还对java程序的其他方法提供了对应的配置语法:
1、调用getter方法:使用PropertyPathFactoryBean
2、访问类或对象的Field值:使用FieldRetrievingFactoryBean
3、调用普通方法:使用MethodInvokingFactoryBean
PropertyPathFactoryBean用来获得目标Bean的属性值(实际上就是它的getter方法的返回值),获得的值可注入其他bean,也可直接定义成新的bean。
将bean实例的属性值注给另一个bean:
<bean id="person" class="org.crazyit.app7_10.service.Person"> <!-- 为age属性指定值 --> <property name="age" value="30"/> <property name="son"> <!-- 使用嵌套Bean定义属性值 --> <bean class="org.crazyit.app7_10.service.Son"> <property name="age" value="11" /> </bean> </property> </bean> <bean id="son2" class="org.crazyit.app7_10.service.Son"> <property name="age"> <!-- 以下是访问Bean属性的简单方式,这样可以将person Bean的son属性的、 age属性赋值给son2这个bean的age属性--> <bean id="person.son.age" class= "org.springframework.beans.factory.config.PropertyPathFactoryBean"/> </property> </bean>
将bean实例的属性值直接定义成bean实例:
此时要指定2个属性值:targetBeanName或targetObject、propertyPath
<bean id="son1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"> <!-- 确定目标Bean,表明son1 Bean来自哪个Bean的属性 --> <property name="targetBeanName" value="person"/> <!-- 确定属性表达式,表明son1 Bean来自目标bean的哪个属性 --> <property name="propertyPath" value="son"/> </bean>
将bean的field值直接指定成bean实例,需要指定2个属性:targetClass或targetObject、targetField
MethodInvokingFactoryBean可以将获得的方法返回值注入到指定bean实例的指定属性,也可以直接定义成bean实例。
实例方法指定targetObject、targetMethod。
静态方法指定targetClass、targetMethod。
如果要确定调用目标方法的参数,就如下这样指定:
<bean id="javaVersion" class= "org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <!-- targetObject确定目标Bean,确定调用哪个Bean --> <property name="targetObject" ref="sysProps"/> <!-- targetMethod确定目标方法,确定调用目标Bean的哪个方法 --> <property name="targetMethod" value="getProperty"/> <!-- 确定调用目标方法的参数 --> <property name="arguments"> <!-- list元素列出调用方法多个参数值 --> <list> <value>java.version</value> </list> </property> </bean>
bean后处理器
bean后处理器负责对容器中其他的bean执行后处理,它必须实现BeanPostProcessor接口,实现postProcessBeforeInitialization、postProcessAfterInitialization方法,这两个方法分别在目标bean初始化之前、初始化之后被回调,用于对容器中的bean实例进行增强处理。
bean后处理器两个方法的回调时机:
容器后处理器
Spring还提供了容器后处理器,此处理器必须实现BeanFactoryPostProcessor接口,实现postProcessBeanFactory(configurablelistablebeanfactory)方法,只对容器本身进行处理。