Spring容器提供了两种配置Bean的方式,1.使用XML文件作为配置bean对象, 2.基于Java注解的配置bean对象。
以下是一个典型的Spring XML配置文件:
在标签内可以放置相关的Spring配置信息.
//定义一个老师类
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文件中配置一下标签:
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属性值的。
我们知道,一个类的依赖的对象不止是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");
}
}
它依赖了一个老师类对象。
这配置文件修改如下:
先配置一个Teacher类,然后在构造方法中使用ref标签指向Teachker类的id。可知
1、value:注入一般属性。
2、ref(引用一个对象): 注入对象类型属性。
对于其他集合的构造器注入,配置文件大致如下,当集合中存储的是对象时,将value改成ref
1
2
1
2
其他对象、集合的依赖注入与构造器注入同理。只是标签不一样了。
P名称空间、C名称空间
首先它们不是真正的名称空间,是虚拟的。它是嵌入到spring内核中的。
使用p名称空间可以解决我们setter注入时
使用c名称空间可以解决我们构造器注入时
p:<属性名>="xxx" 引入常量值
p:<属性名>-ref="xxx" 引用其它Bean对象
使用名称空间后配置文件简化如下:
字面值
比如
如上配置,代表学生的名称引用teacher的名称,age是teacher的age-10
表达式还可以调用其他Bean的方法:
操作类:可以使用T()运算符调用类作用域的方法和常量。比如:
创建一个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注解等效的三个注解:
但配置了这个注解还不够,因为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配置文件开启扫描,可以配置多个开启多个路径
>
这样就可以创建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,以免冲突。
此时无法在类上添加@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实现依赖注入,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标签。
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类
Bean的作用域
可以通过注解@Scope去设置,或者在配置文件中设置。默认时单例。
@Component
@Scope("prototype")
@ComponentScan("teacher")
public class Teacher
或者
当使用会话和请求作用域时,除了设置作用域,还要设置代理,解决将会话或请求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动态代理模式
//开启CGLib代理
//开启JDK代理
参考资料:《Spring实战》