ioc配置
ioc容器是spring的核心之一,那么什么是ioc呢,在传统的程序开发中,需要获取对象时,通常由开发者来手动创建实例化对象,但是在 Spring 框架中创建对象的工作不再由开发者完成,而是交给 IoC 容器来创建,我们直接获取即可,整个流程完成反转,因此是控制反转。
ApplicationContext接口代表Spring IoC容器,并负责实例化,配置和组装Bean
那么什么是Bean呢,可以理解为一个对象就是一个Bean,那么spring是怎么知道有这个bean的呢,那就需要我们去配置matedata,之前写的xml就可以代表matedata,传统开发中都是使用xml来定义matadata的格式,现在开发中可以使用少量XML配置来声明性地启用对这些其他元数据格式的支持,从而指示容器将Java注释或代码用作元数据格式
以下是springbean的基本配置
1 | <?xml version="1.0" encoding="UTF-8"?> |
用过spirng的应该不会陌生
1 | 该id属性是标识单个bean定义的字符串。 |
当然如果要给bean赋值的话,可以使用如下的标签,对应的是一些java的基本对象和一些引用关系
1 |
|
下面通过一个例子来开启spring吧
xml配置
首先是定义了一个Stu的对象,当然这就是一个javabean
1 | public class Stu { |
然后是写xml了,xml写起来比较繁琐,后面会有别的替代方案
1 | <bean id="stu" class="cn.lzl.Stu" > |
当然还有比较简单的方法,使用p命名标签
1 | xmlns:p="http://www.springframework.org/schema/p |
直接给bean赋值
1 | <bean name="p-namespace" class="com.example.ExampleBean" |
下面就开始创建spring容器吧,创建spring容器的方法比较多
图片
这里使用的是ClassPathXmlApplicationContext()来创建上下文容器,然后通过上面的id来获取Stu实例
1 | ApplicationContext context = new ClassPathXmlApplicationContext("server.xml"); |
这里ClassPathXmlApplicationContext(),通过传入classpath下的server.xml作为参数,但如果项目过于庞大的话,全写在一个xml中并不好管理,spring提供了一个重载方法来解决这个问题
1 | public ClassPathXmlApplicationContext(String... configLocations) throws BeansException { |
直接传入多个xml即可
当然这里还有一个解决办法,通过import导入别的xml文件
1 | <beans> |
spring 中的继承
指定parent属性即可,这里的两个bean是来自于同一个类的
Spring 的依赖
依赖也是 bean 和 bean 之间的一种关联方式,配置依赖关系后,被依赖的 bean 一定先创建,再创建依赖的 bean,通过属性 depends-on来指定依赖的bean
Spring 读取外部资源
创建 jdbc.properties
1
2
3
4driverName = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/myTest?useUnicode=true&characterEncoding=UTF-8
user = root
pwd = rootspring.xml 中配置
1
2
3
4
5
6
7
8
9<!-- 导入外部的资源文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 创建 C3P0 数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${user}"></property>
<property name="password" value="${pwd}"></property>
<property name="driverClass" value="${driverName}"></property>
<property name="jdbcUrl" value="${url}"></property>
</bean>
bean的定义
bean的别名
如果不想使用id来获取一个bean,那么可以试试给bean定义一个别名,使用alias标签
1 | <bean id="stu" class="cn.lzl.Stu" > |
bean的实例化
- 使用构造函数实例化bean
这里还必须引入依赖注入(Dependency Injection)的概念,简称DI,通过这个过程,对象仅提供构造所需要的参数就可以实例化一个bean,而不用通过new来创建,也解决了bean复杂的依赖关系,降低了程序的耦合性,因此这个过程还要控制反转(Control Inversion)的说法
1.基于构造函数的依赖注入
这个也比较简单,在bean写好构造函数,然后在xml中配置,通过constructor-arg来赋值,也可以使用type=”int”来显示指定属性的类型,当然你可能不知道index是干什么的,index属性用来指定参数的序号,当然也可以使用name=来代替,使用name是记得在构造函数加上 @ConstructorProperties({“id”,”name”})注解
1 | <bean id="stu" class="cn.lzl.Stu" > |
2.基于Setter的依赖注入
这种方法之前的实例已经使用过了
这里说下@Required这个注解,在set方法上使用,表示这个方法是必须的,也就是该属性必须赋值,否则就会报错,但比较新的spring版本不建议这么做,这个看需求吧。setter注入的一个好处是,setter方法使该类的对象在以后可以重新配置或重新注入。
下面说一下seter注入的过程吧
- 通过setValue方法给bean赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void setValue(final @Nullable Object value) throws Exception {
final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
this.pd.getWriteMethod());
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
ReflectionUtils.makeAccessible(writeMethod);
return null;
});
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
writeMethod.invoke(getWrappedInstance(), value), acc);
}
catch (PrivilegedActionException ex) {
throw ex.getException();
}
}
else {
ReflectionUtils.makeAccessible(writeMethod);
writeMethod.invoke(getWrappedInstance(), value);
}
}
先看this.pd这个内部属性
1 | private final PropertyDescriptor pd;org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=id] |
从字面上的解释是一个属性描述器,他有个子类GenericTypeAwarePropertyDescriptor
1 | private final Class<?> beanClass; |
里面也包含了一些和方法有关的属性
接着往下看,拿到method之后System.getSecurityManager() ,这是java的安全管理器,
最后获取实例并进行赋值操作
1 | writeMethod.invoke(getWrappedInstance(), value); |
- 用静态工厂方法实例化
1
2
3<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
1 | public class ClientService { |
- 使用实例工厂方法实例化
1
2
3<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
1 | public class DefaultServiceLocator { |
bean的懒加载 Lazy-initialized Beans
默认情况下,spring容器加载过程中会初始化所有的单例bean,如果不希望出现这种情况,可以在xml配置lazy-init=”true”
1 | <bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/> |
也可以在beans标签中加入,控制上级容器
1 | <beans default-lazy-init="true"> |
自动装配
自动装配是spring中比较好用的功能,可以很好的解决bean之间的依赖关系
自动装配的模式
模式 | info |
---|---|
no | 默认为no,可以通过ref来指定bean的id |
byName | spring自动寻找与属性名相同的bean |
byType | spring自动寻找与属性类型相同的bean |
constructor | 与byType相似,不过使用的是构造方法 |
通过加入来 autowire=”byType”
如果要在自动装配中排除bean可以加入autowire-candidate=”false”
spring方法注入
spring中的bean默认都是以单例形式存在的,但如果不想使用重复的呢,这里可以使用spring的方法注入
继承ApplicationContextAware
1 | public class FUn implements ApplicationContextAware { |
xml中配置
1 | <bean id="stu2" class="cn.lzl.FUn"> |
最后main方法
1 | Stu stu = context.getBean("stu",Stu.class); |
运行结果
1 | Stu{id=44, name='swiftr'} |
将xml改为
1 | <bean id="stulzl" class="cn.lzl.Stu" scope="prototype"> |
通过 lookup-method 元素标签为 FUn 的 getstu()提供动态实现,返回 prototype 类型的 car Bean,这样 Spring 将在运行期为 MagicBoss 接口提供动态实现,由于stulzl的scope为prototype,所以得到的Stu实例都是不同的引用,hashcode都是不同的
bean的scope
- singleton 单例类
spring中的bean默认就是singleton,每次从spring容器中根据id获取bean时,都会返回一个相同的引用 - prototype 原型类
与singleton相反,都会返回不同的引用 - request,请求,表示在一次 HTTP 请求内有效;
- session,会话,表示在一个用户会话内有效。
bean的生命周期
初始化回调
如果使用的是注解,在要调用的方法上加上@PostConstruct注解,如果是xml的话加上init-method属性即可,获取实现InitializingBean接口中的afterPropertiesSet方法1
2
3
4
5
6
7public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}销毁回调
当bean容器被销毁时执行的方法,与初始化一样同样是三种方法
xml加上destroy-method属性,注解加上@PreDestroy,
获取实现org.springframework.beans.factory.DisposableBean接口
默认回调方法
xml中配置spring容器的关闭
之前定义的销毁回调并没有执行,是应为spring容器并没有正常的关闭,这里调用registerShutdownHook()即可1
ctx.registerShutdownHook();
BeanPostProcessor接口
BeanPostProcessor是spring中比较重要的接口,通过他可以实现自定义bean,
postProcessBeforeInitialization方法代表的是初始化前,postProcessAfterInitialization代表的是初始化后的操作,
写个例子看看吧
1
2
3
4
5
6<bean id="stu" class="cn.lzl.Stu" init-method="init" destroy-method="init">
<constructor-arg name="id" value="123"/>
<constructor-arg name="name" value="swiftr"/>
<!--<property name="id" value="44"></property>-->
<!--<property name="name" value="swiftr"/>-->
</bean>
这是在xml配置的一个bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 public class BeanPostProcessorTest implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("before start");
bean = new Stu(23,"test");
System.out.println("before end");
System.out.println(bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("after start");
bean = new Stu(23,"test2");
System.out.println(bean);
System.out.println("agter end");
return bean;
}
}
通过过BeanPostProcessor对初始化的bean进行修改
1 | <bean class="cn.lzl.BeanPostProcessorTest"/> |
将BeanPostProcessorTest配置到spring环境中,因为它是bean,所以可以像其他任何bean一样依赖注入
org.springframework.beans.factory.FactoryBean接口
Spring通过反射机制利用bean的class属性指定实现类来实例化bean 。在某些情况下,实例化bean过程比较复杂,如果按照传统的方式,则需要在
1 | public class CarFactoryBean implements FactoryBean<Car> { |
xml配置
1 | <bean id="car" class="com.test.factorybean.CarFactoryBean" carInfo="大众SUV,180,180000"/> |
注解
@Autowired对于那些众所周知的解析依赖接口:BeanFactory,ApplicationContext,Environment,ResourceLoader, ApplicationEventPublisher,和MessageSource。这些接口及其扩展接口(例如ConfigurableApplicationContext或ResourcePatternResolver)将自动解析,而无需进行特殊设置。以下示例自动装配ApplicationContext对象
@Primary
表示优先级,当多个bean需要自动装配时,谁优先就加入@Primary1
2
3
4
5
6
7
8
9
10
11
12@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}@Qualifier
@Value()
通常被用来注入外部属性1
2
3public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
写个例子
1 | @Component |
1 | @Configuration |
1 | ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); |
基于java代码的配置
这里主要使用两个注解@Configuration和@Bean
一个简单的例子
1 | @Configuration |
这里相当于xml中的
1 | <bean id="server" class="ServerImp"/> |
与xml的创建方式不同,这里使用AnnotationConfigApplicationContext来创建一个spring容器
1 | ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); |
那么如何使用@Autowired注解来进行注入呢,可以直接将bean放入构造方法中
1 | ApplicationContext context = new AnnotationConfigApplicationContext(UserService.class, UserServiceImpl.class, ServiceTest.class); |
1 | @Component |
除了使用构造方法外,还可以使用register来注册bean
1 | ctx.register(AppConfig.class, OtherConfig.class); |
当然上面的方法都比较繁琐,这里可以使用组件扫描的注解来构建
1 | @Configuration |
相当于xml中的
1 | <beans> |
当然注解中是可以配置bean的name和生命周期方法的
或者在返回bean时执行init方法
1 | @Configuration |
bean的作用域
使用@Scope注解即可
1 | * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE |
bean的别名
1 | @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"}) |
块化配置
与xml一样,java的注解配置也可以进行模块化配置
1 | @Configuration |
java注解配置类中使用xml
@ImportResource(“classpath:/com/acme/properties-config.xml”)
spring环境配置
使用spring开发时,可能会有生产环境和开发不一致的情况
这里可以加入@Profile注解来解决这个问题
1 | @Configuration |
当然可以用Profile做元注解也是ok的,
如果是xml的话可以在beans的属性中加入
1 | <beans profile="development" |
最后启动容器
1 | AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); |
当然这可以不用通过编程试的方法来激活这个特性,
1 | -Dspring.profiles.active =“ profile1,profile2” |
如果是默认配置文件,可以这样
1 | @Profile("default") |
引入环境配置
在spring环境中是可以加载配置文件的,那么怎么来读配置文件呢
1 | @Configuration |