struts 是 web 框架 (jsp/action/actionfrom)
hibernate 是 orm框架,处于持久层.
那么,什么是Spring?
spring 是容器框架,用于配置bean,并维护bean之间关系的框架 。
spring中非常重要的概念:bean,ioc,di
spring实际上是一个容器框架,可以配置各种bean(action/service/domain/dao),并且可以维护bean与bean的关系,当我们需要使用某个bean的时候,我们可以getBean(id),使用即可.
什么是bean?
Bean在Spring和SpringMVC中无所不在,将这个概念内化很重要,下面分享一下我的想法:
一、Bean是啥
1、Java面向对象,对象有方法和属性,那么就需要对象实例来调用方法和属性(即实例化);
2、凡是有方法或属性的类都需要实例化,这样才能具象化去使用这些方法和属性;
3、规律:凡是子类及带有方法或属性的类都要加上 注册Bean到Spring IoC的注解;
(@Component , @Repository , @ Controller , @Service , @Configration)
4、把Bean理解为类的代理或代言人(实际上确实是通过反射、代理来实现的),这样它就能代表类拥有该拥有的东西了
5、我们都在微博上@过某某,对方会优先看到这条信息,并给你反馈,那么在Spring中,你标识一个@符号,那么Spring就会来看看,并且从这里拿到一个Bean(注册)或者给出一个Bean(使用)
二、注解分为两类:
1、一类是使用Bean,即是把已经在xml文件中配置好的Bean拿来用,完成属性、方法的组装;
比如@Autowired , @Resource,可以通过byTYPE(@Autowired)、byNAME(@Resource)的方式获取Bean;
2、一类是注册Bean,@Component , @Repository , @ Controller , @Service , @Configration这些注解都是把你要实例化的对象转化成一个Bean,放在IoC容器中,等你要用的时候,它会和上面的@Autowired , @Resource配合到一起,把对象、属性、方法完美组装。
三、@Bean是啥?
1、原理是什么?先看下源码中的部分内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Indicates that a method produces a bean to be managed by the Spring container.
<h3>Overview</h3>
<p>The names and semantics of the attributes to this annotation are intentionally similar to those of the {@code <bean/>} element in the Spring XML schema. For example:
<pre class="code"> @Bean public MyBean myBean() { // instantiate and configure MyBean obj return obj; }</pre> |
意思是@Bean明确地指示了一种方法,什么方法呢——产生一个bean的方法,并且交给Spring容器管理;从这我们就明白了为啥@Bean是放在方法的注释上了,因为它很明确地告诉被注释的方法,你给我产生一个Bean,然后交给Spring容器,剩下的你就别管了。
2、记住,@Bean就放在方法上,就是产生一个Bean,那你是不是又糊涂了,因为已经在你定义的类上加了@Configration等注册Bean的注解了,为啥还要用@Bean呢?这个我也不知道,下面我给个例子,一起探讨一下吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 3 | package com.edu.fruit; //定义一个接口 public interface Fruit<T>{ //没有方法 }
/* *定义两个子类 */ package com.edu.fruit; @Configuration public class Apple implements Fruit<Integer>{//将Apple类约束为Integer类型
}
package com.edu.fruit; @Configuration public class GinSeng implements Fruit<String>{//将GinSeng 类约束为String类型
} /* *业务逻辑类 */ package com.edu.service; @Configuration public class FruitService { @Autowired private Apple apple; @Autowired private GinSeng ginseng; //定义一个产生Bean的方法 @Bean(name="getApple") public Fruit<?> getApple(){ System.out.println(apple.getClass().getName().hashCode()); System.out.println(ginseng.getClass().getName().hashCode()); return new Apple(); } } /* *测试类 */ @RunWith(BlockJUnit4ClassRunner.class) public class Config { public Config(){ super("classpath:spring-fruit.xml"); } @Test public void test(){ super.getBean("getApple");//这个Bean从哪来,从上面的@Bean下面的方法中来,返回 的是一个Apple类实例对象
} } |
从上面的例子也印证了我上面的总结的内容:
1、凡是子类及带属性、方法的类都注册Bean到Spring中,交给它管理;
2、@Bean 用在方法上,告诉Spring容器,你可以从下面这个方法中拿到一个Bean
什么是IOC?
IOC,控制反转;所谓控制反转就是把创建对象和维护对象关系的权利转移到Spring容器(applicationContext.xml)中,而程序本身不在维护!
什么是DI?
依赖注入,容易维护好对象之间的依赖关系(引用等),就是当程序需要对象的时候,容器将对象注入到程序中,程序此时也依赖容器的对象注入。实际上di和ioc是同一个概念,spring设计者认为di更准确表示spring核心技术。
1.bean容器中获取bean对象的方法。
1)从ApplicationContext应用上下文容器中获取
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(".xml文件");
三种获取上下文的方法:
1.ClassPathXmlApplicationContext 从类路径加载
2.FileSystemXmlApplicationContext 从文件系统中加载
3.XmlWebApplicationContext 从web系统中加载
2)从bean工厂中获取(BeanFactory)
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource(".xml文件"));
两种方式的比较:
1)加载XML时,ApplicationContext实例化容器的时候,实例化所有对象。当我们要使用的时候,直接获取。即方便快捷,提前加载,但是浪费内存。(饿汉)
2)加载XML时,BeanFactory实例化容器,但不会实例化所配置的bean对象,只有当我们getBean的时候,才会创建。(懒汉)
在没有特殊要求的时候,一般使用ApplicationContext
Spring底层对象实例的创建是基于反射和HashMap机制
2.bean的scope
默认的生命周期是singleton 单例模式
3.配置bean ?如何给集合类型注入值.?
java中主要的集合有几种: map set list / 数组
Department类:
package com.hsp.collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Department {
private String name;
private String[] empName;
private List<Employee> empList;
private Set<Employee> empsets;
private Map<String,Employee> empMaps;
private properties pp;
public Set<Employee> getEmpsets() {
return empsets;
}
public void setEmpsets(Set<Employee> empsets) {
this.empsets = empsets;
}
public String[] getEmpName() {
return empName;
}
public void setEmpName(String[] empName) {
this.empName = empName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Employee> getEmpList() {
return empList;
}
public void setEmpList(List<Employee> empList) {
this.empList = empList;
}
public Map<String, Employee> getEmpMaps() {
return empMaps;
}
public void setEmpMaps(Map<String, Employee> empMaps) {
this.empMaps = empMaps;
}
}
Employeel类
package com.hsp.collection;
public class Employee {
private String name;
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
beans.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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="department" class="com.hsp.collection.Department">
<property name="name" value="财务部"/>
<!-- 给数组注入值 -->
<property name="empName">
<list>
<value>小明</value>
<value>小明小明</value>
<value>小明小明小明小明</value>
</list>
</property>
<!-- 给list注入值 list 中可以有相当的对象 -->
<property name="empList">
<list>
<ref bean="emp2" />
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
<ref bean="emp1"/>
</list>
</property>
<!-- 给set注入值 set不能有相同的对象 -->
<property name="empsets">
<set>
<ref bean="emp1" />
<ref bean="emp2"/>
<ref bean="emp2"/>
<ref bean="emp2"/>
<ref bean="emp2"/>
</set>
</property>
<!-- 给map注入值 map只有key不一样,就可以装配value -->
<property name="empMaps">
<map>
<entry key="11" value-ref="emp1" />
<entry key="22" value-ref="emp2"/>
<entry key="33" value-ref="emp1"/>
</map>
</property>
<!-- 给属性集合配置 【点http协议 referer 】-->
<property name="pp">
<props>
<prop key="pp1">abcd</prop>
<prop key="pp2">hello</prop>
</props>
</property>
</bean>
<bean id="emp1" class="com.hsp.collection.Employee">
<property name="name" value="北京"/>
<property name="id" value="1"/>
</bean>
<bean id="emp2" class="com.hsp.collection.Employee">
<property name="name" value="天津"/>
<property name="id" value="2"/>
</bean>
</beans>
4.内部bean,也就是bean的嵌套
<bean id=”foo” class=”....Foo”>
<property name=”属性”>
<!—第一方法引用-->
<ref bean=’bean对象名’/>
<!—内部bean-->
<bean>
<properyt></property>
</bean>
</property>
</bean>
5.继承配置
public class Student
public class Gradate extends Student
在beans.xml文件中体现配置
<!-- 配置一个学生对象 -->
<bean id="student" class="com.hsp.inherit.Student">
<property name="name" value="顺平" />
<property name="age" value="30"/>
</bean>
<!-- 配置Grdate对象 -->
<bean id="grdate" parent="student" class="com.hsp.inherit.Gradate">
<!-- 如果自己配置属性name,age,则会替换从父对象继承的数据 -->
<property name="name" value="小明"/>
<property name="degree" value="学士"/>
</bean>
6.通过properties取数据(接上面的例子)
properties pp = department.getPp();
for(Entry<Object,Object>entry:pp.entrySet()){
System.out.println(entry.getKey().toString()+" "+entry.getValue());}
7.按照构造函数方法注入bean (类中必须要有构造函数)
beans.xml 关键代码:
<!-- 配置一个雇员对象 -->
<bean id="employee" class="com.hsp.constructor.Employee">
<!-- 通过构造函数来注入属性值 -->
<constructor-arg index="0" type="java.lang.String" value="大明" />
</bean>
8.自动装配
1.byName
<!-- 配置一个master对象 -->
<bean id="master" class="com.hsp.autowire.Master" autowire="byName">
<property name="name">
<value>顺平</value>
</property>
</bean>
<!-- 配置dog对象 -->
<bean id="dog" class="com.hsp.autowire.Dog">
<property name="name" value="小黄"/>
<property name="age" value="3"/>
</bean>
2.byType: byType:寻找和属性类型相同的bean,找不到,装不上,找到多个抛异常。
3.constructor: autowire="constructor" 查找和bean的构造参数一致的一个或多个bean,若找不到或找到多个,抛异常。
按照参数的类型装配 。
4. autodetect (3)和(2)之间选一个方式。不确定性的处理与(3)和(2)一致。 (construct优先级要高)
5.default
这个需要在<beans defualt-autorwire=“指定” />
当你在<beans >指定了 default-atuowrite后, 所有的bean的 默认的autowire就是 指定的装配方法;
如果没有在<beans defualt-autorwire=“指定” /> 没有 defualt-autorwire=“指定” ,则默认是
defualt-autorwire=”no”
6.no 不自动装配
9.使用spring的特殊bean,完成分散配置
创建properties文件,将所有的属性值按照key-value的形式放入文件中。
<context:property-placeholder location=".properties文件路径"/>
<!-- 配置一DBUtil对象 $占位符号 -->
<bean id="dbutil" class="com.hsp.dispatch.DBUtil">
<property name="name" value="${name}" />
<property name="drivername" value="${drivername}" />
<property name="url" value="${url}" />
<property name="pwd" value="${pwd}" />
</bean>
<!-- 配置一DBUtil对象 -->
<bean id="dbutil2" class="com.hsp.dispatch.DBUtil">
<property name="name" value="${db2.name}" />
<property name="drivername" value="${db2.drivername}" />
<property name="url" value="${db2.url}" />
<property name="pwd" value="${db2.pwd}" />
</bean>
db.properties:
name=scott
drivername=oracle:jdbc:driver:OracleDirver
url=jdbc:oracle:thin:@127.0.0.1:1521:hsp
pwd=tiger
启用类中的注解
<context:annotation-config/>
Bean的作用域
Spring3中为Bean定义了5中作用域,
分别为singleton(单例)、prototype(原型)、request、session和global session,5种作用域说明如下:
1. singleton:单例模式,Spring IoC容器中只会存在一个共享的Bean实例,无论有多少个Bean引用它,始终指向同一对象。Singleton作用域是Spring中的缺省作用域,也可以显示的将Bean定义为singleton模式,配置为:
· <bean id="userDao"class="com.ioc.UserDaoImpl" scope="singleton"/>
2. prototype:原型模式,每次通过Spring容器获取prototype定义的bean时,容器都将创建一个新的Bean实例,每个Bean实例都有自己的属性和状态,而singleton全局只有一个对象。
根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。
3. request:在一次Http请求中,容器会返回该Bean的同一实例。而对不同的Http请求则会产生新的Bean,而且该bean仅在当前Http Request内有效。
· <bean id="loginAction" class="com.cnblogs.Login "scope="request"/>,针对每一次Http请求,Spring容器根据该bean的定义创建一个全新的实例,且该实例仅在当前Http请求内有效,而其它请求无法看到当前请求中状态的变化,当当前Http请求结束,该bean实例也将会被销毁。
4. session:在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。
· <beanid="userPreference" class="com.ioc.UserPreference"scope="session"/>,同Http请求相同,每一次session请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的session请求内有效,请求结束,则实例将被销毁。
5. global Session:在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效。
Bean的生命周期
经过如上对Bean作用域的介绍,接下来将在Bean作用域的基础上讲解Bean的生命周期。
Spring容器可以管理singleton作用域下Bean的生命周期,在此作用域下,Spring能够精确地知道Bean何时被创建,何时初始化完成,以及何时被销毁。而对于prototype作用域的Bean,Spring只负责创建,当容器创建了Bean的实例后,Bean的实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的Bean的生命周期。Spring中Bean的生命周期的执行是一个很复杂的过程,读者可以利用Spring提供的方法来定制Bean的创建过程。Spring容器在保证一个bean实例能够使用之前会做很多工作:
1.Spring对Bean进行实例化(相当于程序中的new Xx())
2.Spring将值和Bean的引用注入进Bean对应的属性中,也就是IOC注入
3.如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()方法
(实现BeanNameAware主要是为了通过Bean的引用来获得Bean的ID,一般业务中是很少有用到Bean的ID的)
4.如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanDactory(BeanFactory)方法并把BeanFactory容器实例作为参数传入。
(实现BeanFactoryAware 主要目的是为了获取Spring容器(为了获得容器,从而调用容器里的东西),如Bean通过Spring容器发布事件等)传递的是Spring工厂本身(可以用这个方法获取到其他Bean)
5.(可以用这个方法获取到其他Bean,项目中用到了,传回class)如果Bean实现了ApplicationContextAwaer接口,Spring容器将调用setApplicationContext(ApplicationContext)方法, (作用与BeanFactory类似都是为了获取Spring容器,如Bean通过Spring容器发布事件等,项目是调用容器里为Handler的bean。applicationContext.getBeansOfType(EventHandler.class);
取得所有的事件处理的classbean做进一步的消费者事件处理2),不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入,而Spring容器在调用setBeanDactory前需要程序员自己指定(注入)setBeanDactory里的参数BeanFactory )传入Spring上下文,该方式同样可以实现步骤4,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法.(如果应用Spring的工厂也就是BeanFactory的话去掉这一步就Ok了
)
6.如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessBeforeInitialization(预初始化)方法
(作用是在Bean实例创建成功后对进行增强处理,如对Bean进行修改,增加某个功能)
7.如果Bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet方法,作用与在配置文件中对Bean使用init-method声明初始化的作用一样,都是在Bean的全部属性设置成功后执行的初始化方法。
8.如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessAfterInitialization(后初始化)方法
(作用与6的一样,只不过6是在Bean初始化前执行的,而这个是在Bean初始化后执行的,时机不同 )
9.经过以上的工作后,Bean准备就绪,将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁.
当Bean不再需要时,会经过清理阶段。
10.如果Bean实现了DispostbleBean接口,Spring将调用它的destory方法,
11.如果在配置文件中对Bean使用destory-method属性,作用跟10是都一样,都是在Bean实例销毁前执行的方法。
1. 实例化Bean
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
对于ApplicationContext容器,当容器启动结束后,便实例化所有的bean。
容器通过获取BeanDefinition对象中的信息进行实例化。并且这一步仅仅是简单的实例化,并未进行依赖注入。
实例化对象被包装在BeanWrapper对象中,BeanWrapper提供了设置对象属性的接口,从而避免了使用反射机制设置属性。
2. 设置对象属性(依赖注入)
实例化后的对象被封装在BeanWrapper对象中,并且此时对象仍然是一个原生的状态,并没有进行依赖注入。
紧接着,Spring根据BeanDefinition中的信息进行依赖注入。
并且通过BeanWrapper提供的设置属性的接口完成依赖注入。
3. 注入Aware接口
紧接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。(自动装配过程)
4. BeanPostProcessor
当经过上述几个步骤后,bean对象已经被正确构造,但如果你想要对象被使用前再进行一些自定义的处理,就可以通过BeanPostProcessor接口实现。
该接口提供了两个函数:
- postProcessBeforeInitialzation( Object bean, String beanName )
当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。
这个函数会先于InitialzationBean执行,因此称为前置处理。
所有Aware接口的注入就是在这一步完成的。 - postProcessAfterInitialzation( Object bean, String beanName )
当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。
这个函数会在InitialzationBean完成后执行,因此称为后置处理。
5. InitializingBean与init-method
当BeanPostProcessor的前置处理完成后就会进入本阶段。
InitializingBean接口只有一个函数:
- afterPropertiesSet()//项目中用到这个。则在注册bean时,同时执行
这一阶段也可以在bean正式构造完成前增加我们自定义的逻辑,但它与前置处理不同,由于该函数并不会把当前bean对象传进来,因此在这一步没办法处理对象本身,只能增加一些额外的逻辑。
若要使用它,我们需要让bean实现该接口,并把要增加的逻辑写在该函数中。然后Spring会在前置处理完成后检测当前bean是否实现了该接口,并执行afterPropertiesSet函数。
当然,Spring为了降低对客户代码的侵入性,给bean的配置提供了init-method属性,该属性指定了在这一阶段需要执行的函数名。Spring便会在初始化阶段执行我们设置的函数。init-method本质上仍然使用了InitializingBean接口(执行的初始化方法)。
6. DisposableBean和destroy-method
和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑。
Spring的单例实现
下面我们来看看Spring中的单例实现,当我们试图从Spring容器中取得某个类的实例时,默认情况下,Spring会才用单例模式进行创建。
<bean id="date" class="java.util.Date"/> |
<bean id="date" class="java.util.Date" scope="singleton"/> (仅为Spring2.0支持) |
<bean id="date" class="java.util.Date" singleton="true"/> |
以上三种创建对象的方式是完全相同的,容器都会向客户返回Date类的单例引用。那么如果我不想使用默认的单例模式,每次请求我都希望获得一个新的对象怎么办呢?很简单,将scope属性值设置为prototype(原型)就可以了
<bean id="date" class="java.util.Date" scope="prototype"/> |
通过以上配置信息,Spring就会每次给客户端返回一个新的对象实例。
那么Spring对单例的底层实现,到底是饿汉式单例还是懒汉式单例呢?
Spring框架对单例的支持是采用单例注册表的方式进行实现的,源码如下:
1. public abstract class AbstractBeanFactory implements ConfigurableBeanFactory{
2. /**
3. * 充当了Bean实例的缓存,实现方式和单例注册表相同
4. */
5. private final Map singletonCache=new HashMap();
6. public Object getBean(String name)throws BeansException{
7. return getBean(name,null,null);
8. }
9. ...
10. public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{
11. //对传入的Bean name稍做处理,防止传入的Bean name名有非法字符(或则做转码)
12. String beanName=transformedBeanName(name);
13. Object bean=null;
14. //手工检测单例注册表
15. Object sharedInstance=null;
16. //使用了代码锁定同步块,原理和同步方法相似,但是这种写法效率更高
17. synchronized(this.singletonCache){
18. sharedInstance=this.singletonCache.get(beanName);
19. }
20. if(sharedInstance!=null){
21. ...
22. //返回合适的缓存Bean实例
23. bean=getObjectForSharedInstance(name,sharedInstance);
24. }else{
25. ...
26. //取得Bean的定义
27. RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);
28. ...
29. //根据Bean定义判断,此判断依据通常来自于组件配置文件的单例属性开关
30. //<bean id="date" class="java.util.Date" scope="singleton"/>
31. //如果是单例,做如下处理
32. if(mergedBeanDefinition.isSingleton()){
33. synchronized(this.singletonCache){
34. //再次检测单例注册表
35. sharedInstance=this.singletonCache.get(beanName);
36. if(sharedInstance==null){
37. ...
38. try {
39. //真正创建Bean实例
40. sharedInstance=createBean(beanName,mergedBeanDefinition,args);
41. //向单例注册表注册Bean实例
42. addSingleton(beanName,sharedInstance);
43. }catch (Exception ex) {
44. ...
45. }finally{
46. ...
47. }
48. }
49. }
50. bean=getObjectForSharedInstance(name,sharedInstance);
51. }
52. //如果是非单例,即prototpye,每次都要新创建一个Bean实例
53. //<bean id="date" class="java.util.Date" scope="prototype"/>
54. else{
55. bean=createBean(beanName,mergedBeanDefinition,args);
56. }
57. }
58. ...
59. return bean;
60. }
61. }
刚才的源码中,大家真正要记住的是Spring对bean实例的创建是采用单例注册表的方式进行实现的,而这个注册表的缓存是HashMap对象,如果配置文件中的配置信息不要求使用单例,Spring会采用新建实例的方式返回对象实例.
后面一段是判定是否为socpe为singleton,是的话执行单例模式的过程,只创建一个新的对象,否的话则可以创建多个对象。