声明Bean
Spring容器提供了两种配置Bean的方式,1.使用XML文件作为配置bean对象, 2.基于Java注解的配置bean对象。
使用XML文件配置bean对象:
以下是一个典型的Spring 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 -->
</beans>
在标签内可以放置相关的Spring配置信息.
声明一个简单的<bean>
//定义一个老师类
package com.demo.spring;
public class Teacher {
private String name;
private String age;
public Teacher(){
}
public Teacher(String name,String age){
this.name = name;
this.age = age;
}
void work(){
System.out.println("jiaoshu");
}
}
在xml文件中配置一下标签:
<bean id="student" class="com.demo.spring.Teacher"></bean>
<bean>元素是Spring中最基本的配置单元,通过该元素Spring将创建一个对象。当Spring容器加载该Bean时,Spring将使用默认的构造器来实例化该Bean,实际上,student会使用如下代码来创建:
new com.demo.spring.Teacher();
测试代码:
public class TeacherTest {
@Test
public void test(){
//使用应用上下文创建bean
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("config/application.xml");
Teacher teacher = (Teacher) context.getBean("teacher");
teacher.work();
}
}
测试可知,Teacher对象创建成功。这就是控制反转(IOC),将创建对象的权利交给了Spring容器。原理就是 通过配置文件获取全类名,然后通过反射和工厂模式创建对象。
我们知道Teacher类有两个私有成员属性,控制反转虽然创建了对象,但没有对属性赋值。怎么对属性赋值就是所谓的依赖注入(DI)。
依赖注入方式有两种:
通过构造器注入:实际调用的是有参构造。
让bean使用另外一个构造方法创建对象,需修改配置文件如下:此时创建出来的teacher对象是有name和age属性值的。
<bean id="teacher" class="com.demo.spring.Teacher">
<constructor-arg name="name" value="数学"></constructor-arg>
<constructor-arg name="age" value="30"></constructor-arg>
</bean>
我们知道,一个类的依赖的对象不止是String,基础数据类型,还可能是数组,集合,其他类对象等,注入方式有所不同。
如存在一个学生类:
package com.demo.spring;
public class Student {
private String name;
private String age;
private Teacher teacher;
public Student(String name, String age,Teacher teacher ) {
this.name = name;
this.age = age;
this.teacher = teacher;
}
public void study(){
System.out.println("xuexi");
}
}
它依赖了一个老师类对象。
这配置文件修改如下:
<bean id="teacher" class="com.demo.spring.Teacher">
<constructor-arg name="name" value="数学"></constructor-arg>
<constructor-arg name="age" value="30"></constructor-arg>
</bean>
<bean id="student" class="com.demo.spring.Student">
<constructor-arg name="name" value="小学"></constructor-arg>
<constructor-arg name="age" value="12"></constructor-arg>
<constructor-arg name="teacher" ref="teacher"></constructor-arg>
</bean>
先配置一个Teacher类,然后在构造方法中使用ref标签指向Teachker类的id。可知
1、value:注入一般属性。
2、ref(引用一个对象): 注入对象类型属性。
对于其他集合的构造器注入,配置文件大致如下,当集合中存储的是对象时,将value改成ref
<constructor-arg>
<list>
<value>1</value>
<value>2</value>
</list>
</constructor-arg>
<constructor-arg>
<set>
<value>1</value>
<value>2</value>
</set>
</constructor-arg>
<constructor-arg>
<map>
<entry key="name" value="xxx"></entry>
<entry key="age" value="12"></entry>
</map>
</constructor-arg>
通过设置属性完成依赖注入:实际调用的是set方法,所以对应属性必须存在对应的set方法。
<bean id="teacher" class="com.demo.spring.Teacher">
<property name="name" value="xx"></property>
<property name="age" value="12"></property>
</bean>
其他对象、集合的依赖注入与构造器注入同理。只是标签不一样了。
使用名称空间
P名称空间、C名称空间
首先它们不是真正的名称空间,是虚拟的。它是嵌入到spring内核中的。
使用p名称空间可以解决我们setter注入时<property>简化
使用c名称空间可以解决我们构造器注入时<constructor-arg>简化
p:<属性名>="xxx" 引入常量值
p:<属性名>-ref="xxx" 引用其它Bean对象
使用名称空间后配置文件简化如下:
<beans xmlns="http://www.springframework.org/schema/beans"
//引入p名称空间
xmlns:p="http://www.springframework.org/schema/p"
//引入cm名称空间
xmlns:c="http://www.springframework.org/schema/c"
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="teacher" class="com.demo.spring.Teacher" p:age="12" p:name="xx">
</bean>
</beans>
SpEL基本用法
字面值
比如<property name="count" value="#{5}"/>,#{ }标记会提示Spring这是个SpEL表达式。
<bean id="teacher" class="com.demo.spring.Teacher">
<property name="name" value="xx"></property>
<property name="age" value="30"></property>
</bean>
<bean id="student" class="com.demo.spring.Student">
<property name="name" value="#{teacher.name}"></property>
<property name="age" value="#{teacher.age}-10"></property>
<property name="teacher" ref="teacher"></property>
</bean>
如上配置,代表学生的名称引用teacher的名称,age是teacher的age-10
表达式还可以调用其他Bean的方法:
<property name="name" value="#{teacher.work()}"></property>
操作类:可以使用T()运算符调用类作用域的方法和常量。比如:
<property name="multiplier" value="#{T(java.lang.Math).PI}"/>
<property name="randomNumber" value="#{T(java.lang.Math).random()}"/>
使用注解配置bean对象:
1.当需要创建的bean的类是自己创建的时
创建一个Teacher类如下:在类上使用了@Componet
package com.demo.spring;
import org.springframework.stereotype.Component;
@Component
public class Teacher {
private String name;
private String age;
void work(){
System.out.println("jiaoshu");
}
}
@Componet:表明该类会作为组件类,并告知Spring要为这个类创建bean实例。默认名字是类名首字母小写,也可以自己指定
@ComponentScan("teacher")
Spring的框架中提供了与@Component注解等效的三个注解:
- @Repository 用于对DAO实现类进行标注
- @Service 用于对Service实现类进行标注
- @Controller 用于对Controller实现类进行标注
但配置了这个注解还不够,因为spring的组件扫描是默认关闭的,还必须显式配置一下Spring,命令它开启扫描,寻找带有@Componet的类。开启方式有两种:
1.使用注解@ComponentScan开启扫描。我们可以直接在需要创建bean的类上添加该注解,但是为了方便,一般会创建一个配置类
@Configuration
@ComponentScan
public class Config {
}
@ComponentScan默认是扫描当前类所在路径及其路径。可以通过value属性指定单个包名或者basePackages指定多个包名
@Configuration
@ComponentScan("com.demo.spring")
public class Config {
}
@Configuration
@ComponentScan(basePackages = {"com.demo.spring","com.demo"})
public class Config {
}
2.使用xml配置文件开启扫描,可以配置多个开启多个路径
<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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.demo.spring"/>
<context:component-scan base-package="com.demo"/>
</beans>>
这样就可以创建bean了,测试代码如下:
package com.demo.spring;
import com.demo.Config;
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 = Config.class)
public class TeacherTest {
@Autowired
private Teacher teacher;
@Test
public void test(){
teacher.work();
}
}
这样也只是完成了控制反转(IOC),还没有实现依赖注入(DI).
@Value: 简单属性注入
@Autowired:复杂属性注入,
@Component
public class Student {
@Value("xx")
private String name;
@Value("12")
private String age;
@Autowired
private Teacher teacher;
public void study(){
System.out.println("xuexi");
}
}
当有多个bean满足依赖关系时,
1.使用@Primary注解指定首选bean,这只能指定一个。
@Component
@Primary
public class English implements Course {
}
2.使用@Qualifier指定唯一的bean。在扫描的时候可以通过@Qualifier或@Component指定创建的bean的名称,注入时通过@Qualifier指定
@Component("test")
public class English implements Course {
}
或者
@Component
@Qualifier("test")
public class English implements Course {
}
@Autowired
@Qualifier("test")
private Course course ;
3.自定义注解。
使用注解进行依赖注入时不需要设置set方法。猜测底层是利用反射进行暴力访问得到私有属性进行赋值。
使用注解进行控制反转时,默认的bean名字就是类名的首字母小写。
所以这时候在配置文件中配置必要的bean时不要配置名字为类名首字母小写的bean,以免冲突。
2.当使用的是第三方的类时
此时无法在类上添加@Componet注解,所以无法进行扫描,也无法使用@Autowried完成依赖注入,这时就需要使用@Bean标签。
@Bean标签作用于方法上,使用如下:我们假设Teacher这个类是第三方类,为了获取它的bean,创建一个方法返回一个Teacher实例,然后在方法上使用@Bean注解。
public class Config {
@Bean
public Teacher getTeacher(){
return new Teacher();
}
}
Spring会将配置了@Bean的方法生成一个bean放入容器,之后不管这个方法调用多少次,都会返回那一个bean,当然可以使用@Scope配置作用域,与以下使用xml配置文件作用相同。生成的bean默认id为类名小写,可以自己指定@bean(name="")。
<bean id="student" class="com.demo.spring.Teacher"></bean>
@Bean实现依赖注入,Student类依赖一个Teacher类。
public class Config {
@Bean
public Teacher getTeacher(){
return new Teacher();
}
@Bean
public Student getStudent1(Teacher teacher){
//构造方法注入
return new Student(getTeacher());
}
@Bean
public Student getStudent2(Teacher teacher){
Student student = new Student();
//属性注入
student.setTeacher(getTeacher());
return student;
}
为了方便管理,将使用@Bean生成bean的方法都集中在同一个包下的不同Config类中。
导入和混合配置
1.当一个XML配置文件过大时,我们可以将它拆成多个小的配置文件,然后在一个配置文件中引入另一个配置文件,或者在最后新建一个配置文件,然后引入所有其他配置文件。使用方法如下:使用import标签。
<?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">
<import resource="application1.xml"></import>
</beans>
2.当使用Config类配置时,如果一个Config类过大,也可以拆分成多个Config类,然后在一个Config类中引入另一个Config类,或者在最后新建一个Config类,然后引入所有Config类。使用方法如下:使用@Import。
//批量引入
@Import({Config2.class, Config1.class})
public class Config3 {
}
//单个引入
@Import(Config1.class)
public class Config2 {
}
3.Config类中引入XML配置文件,使用注解ImportResource
@ImportResource("classpath:application.xml")
public class Config2 {
}
4.XML配置文件中引入Config类
<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 class="com.demo.spring.Config2"></bean>
</beans>
Bean的作用域
可以通过注解@Scope去设置,或者在配置文件中设置。默认时单例。
@Component
@Scope("prototype")
@ComponentScan("teacher")
public class Teacher
或者
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
当使用会话和请求作用域时,除了设置作用域,还要设置代理,解决将会话或请求bean注入到单例bean中的问题
@Scope(value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.INTERFACES)
public class Math implements Course {...}
如有一个StoreService要处理ShoppingCar. StoreService是单例的,而ShoopingCar是会话的。怎么保证StoreService每次处理的ShoopingCar就是当前会话的?
@Component
public class StoreService {
@Autowired
private void setShoppingCart(ShoppingCart shoppingCart){
this.shoppingCart = shoppingCart;
}
}
实现原理如下图:Spring会注入一个到ShoppingCart bean的代理,这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。
当代理的是接口时使用 proxyMode = ScopedProxyMode.INTERFACES,使用的是JDK动态代理
当代理的是接口时使用 proxyMode = ScopedProxyMode.TARGET_CLASS,使用的是CGLib动态代理
当然也可以使用xml配置代理方式:配置文件默认使用的是CGLib动态代理模式
<bean id="cart" class="com.demo.ShoppingCart">
<aop:scoped-proxy/>//开启CGLib代理
</bean>
<bean id="cart" class="com.demo.ShoppingCart">
<aop:scoped-proxy proxy-target-class="false"/>//开启JDK代理
</bean>
参考资料:《Spring实战》