Spring DI
Spring容器
Spring容器负责创建、装配、配置对象并管理对象的整个生命周期,从生存到死亡(new —> finalize)。
Spring容器有多种实现方式,总体可归纳为两类:
1. bean工厂:最简单的容器,提供基本的DI支持
2. 应用上下文:给予bean工厂构建,提供应用框架级别的服务
1. AnnotationConfigApplicationContext:基于java的配置类加载Spring应用的上下文
2. AnnotationConfigWebApplicationContext:基于java的配置文件加载SpringWeb应用的上下文
3. ClassPathXmlApplicationContext:从xml文件中加载上下文(xml文件是应用的资源文件)
4. FileSystemXmlApplicationContext:从文件系统中的xml中加载上下文
5. XmlWebApplicationContext:从Web应用下的xml文件中加载上下文
ApplicationContext cnt = new FileSystemXmlApplicationContext("c:/contxt.xml");
ApplicationContext cnt = new ClassPathXmlApplicationContext("context.xml");
ApplicationContext cnt = new AnnotationConfigApplicationContext(package.to.config.Java.class);
应用的上下文准备就绪之后,就可以调用上下文的getBean()方法从Spring容器中获取所需的Bean
bean的生命周期
传统java应用中,使用关键字new完成bean的实例化,然后改bean就可以被使用。一旦该bean不再被使用时,JVM会自动回收。
而Spring容器中Bean的生命周期则复杂得多:
1. 实例化
2. 填充属性
3. 调用BeanNameAware的setBeanName方法;
4. 调用BeanFactoryAware的setBeanFactory方法;
5. 调用ApplicationContextAware的setApplicationContext方法;
6. 调用BeanPostProcessor的预初始化方法;
7. 调用InitializeingBean的afterPropertiesSet方法
8. 调用自定义的初始化方法
9. 调用BeanPostProcessor的初始化后方法
10. bean被应用使用
11. spring容器被关闭
12. 调用DisposableBean的destory方法
13. 调用自定义的销毁方法
装配Bean
Spring提供了三种装配bean的方式:
(1)利用xml文件进行显示装配 (2)在java文件中进行显示装配 (3)利用注解进行隐式自动化装配
自动化装配Bean
Spring从两个角度来实现自动化装配:1. 组件扫描 2. 自动装配
1. 创建可被发现的bean
一般会定义接口,并在接口的实现类上修饰Component注解(也可用Named注解)
2. 可对bean进行命名
在修饰bean的Component注解指定bean的名称: @Component("bean_name")
注入bean
在某个bean的某个 字段、 方法、 构造 方法上,用Autowired注解修饰
启动组件扫描,设置组件扫描的基础包
创建Config配置类,并用Configuration和ComponentScan注解修饰该类
@Component("package") @Component(basePackages={"package1","package2","package3"}) @Component(basePackageClasses={Bean1.class,Bean2.class,Bean3.class})//此时会分别扫描这三个类所在的包
也可以通过xml文件来启动组件扫描
通过JavaConfig文件进行显示装配
当遇到需要将第三方库中的组件装配到自己的应用中的时候,自动化装配方法就行不通,这时候需要使用Java文件或者XML文件进行显示装配
1. 创建配置类
在利用Configuration注解修饰配置类
2. 在配置类中声明Bean
利用Bean注解
以下是一个简单的利用java配置文件进行显示装配的例子:
public class JavaConfig{
@Bean
public Bean1 createBean1(){
return new Bean1();
}
@Bean
public Bean2 createBean2(){
Bean1 bean1 = new Bean1();//Spring会对此进行拦截,这里生成的bean1其实和上面方法成的是同一个对象
Bean2 bean2 = new Bean2(bean1);
return bean2;
}
}
通过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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="bean0" class="package.to.bean0"/>
<bean id="bean1" class="package.to.bean1">
<property name="field1" ref="bean0" />
<property name="field2" value="Tom" />
</bean>
<bean id="bean2" class="package.to.bean2">
<constructor-arg ref="bean1"/>
<constructor-arg>
<list>
<value>value1</value>
<value>value2</value>
</list>
</constructor-arg>
</bean>
</beans>
混合配置
在一个项目中,完全可以同时存在以上三种配置
1. 在JavaConfig配置文件中引用另外一个JavaConfig配置wenjian
@Configuration
@Import({AnotherJavaConfig1.class,AnotherJavaConfig2.class})
public class JavaConfig{
@Bean
public Bean createBean(){return new Bean();}
}
- 在JavaConfig文件中引入XML配置文件
@Configuration
@ImportResource("classpath:config.xml")
public class JavaConfig{
@Bean
public Bean createBean(){return new Bean();}
}
- 在XML文件中引入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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="bean0" class="package.to.bean0"/>
<import resource = "anotherXMLConfig.xml"/>
</beans>
- 在XML文件中引入JavaConfig配置
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="bean0" class="package.to.bean0"/>
<bean class="package.to.JavaConfig"/>
</beans>
Spring高级装配
Profile
Spring在构建的时候保留所有信息,在运行时根据环境决定哪些bean应该被创建。因此,一份构建好的单元可以被部署到不同的环境。 \
Spring通过spring.profiles.active和spring.profiles.default属性值来确定被激活的Profile。spring.profiles.active属性值优先,如果其值没有被设定的话,则依据spring.profiles.default的值。如果两个都没有被设定的话,则只会创建没有被Profile注解修饰的Bean
@Configuration
public class JavaConfig{
@Bean
@Profile("prd") //只在prd环境才会被创建
public Bean createBean(){return new Bean();}
@Bean
@Profile("dev") //只在dev环境才会被创建
public Bean1 createBean1(){return new Bean1();}
@Bean //任何环境都会被创建
public Bean2 createBean2(){return new Bean2();}
}
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<beans profile="dev">
<bean id="bean0" class="package.to.bean0"/>
</beans>
</beans>
条件化的Bean
依据条件来决定是否创建Bean
@Bean
@Conditional(MagicExistsCondition.class)
public Bean createBean(){return new Bean();}
public class MagicExistsCondition implements Condition{
public boolean matches(ConditionContext context,AnnotatedTypeMetadata metedata){
Environment env = context.getEnvironment();
return env.containsProperty("magic");
}
}
自动装配的歧义性
Primary注解
当有两个或更多的Bean可以被同时注入到属性或者方法中时,Spring做出选择到底装配哪一个,会抛出NoUniqueBeanDefinitionException异常。此时,可以在其中的某个Bean上使用@Primary注解修饰。Spring会优先选择被Primary注解修饰的Bean用来注入。
限定自动装配的Bean
运行时注入
在上面的JavaConfig/XML文件中,我们通过硬编码的方式注入了某些值。但是有时候我们希望在运行时再确定这些值。可以通过以下两种方法:(1)属性占位符 (2)Spring表达式语言
注入外部值
@Configuration
@PropertySource("classpath:app.properties")
public class JavaConfig{
@Autowired
Environment env;
@Bean
public Bean1 createBean1(){
return new Bean1(
env.getProperty("user.name"),
env.getProperty("user.age")
);
}
}
/*app.properties文件中定义了值和属性,内容如下:
user.name = Tom
user.age = 19
这些值会被加载到Environment中,所以可以从中进行检索
*/
属性占位符
在使用属性占位符的时候,必须配置一个PropertyPlaceholderConfigurer Bean或者PropertySourcesPlaceholderConfigurer Bean。
@Configuration
@PropertySource("classpath:app.properties")
public class JavaConfig{
@Autowired
Environment env;
@Bean
public Bean1 createBean1(@Value("${user.name}")String userName,
@Value("${user.age}")int userAge){
return new Bean1(userName,userAge);
}
}
<bean id="bean1" class="package.to.Bean1" c:_name="${user.name}" c:_age="${user.aget}"/>
Spring表达式语言
Spring表达式语言的形式为:#{......}
Bean的作用域
- 单例(Singleton):整个应用中,某类的bean只创建一个实例
- 原型(Prototype):每次注入或者每次从Spring的上下文中获取的时候,都会生成一个新的bean
- 会话(Session):在Web应用中,为每个会话创建一个bean实例
请求(Request):在Web应用中,为每个请求创建一个bean实例
spring中,bean默认是为单例的
//自动扫描与隐式装配
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Bean{}
//显示装配
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Bean1 createBean1(){return new Bean1();}
//xml中
<bean id="bean1" class="package.to.Bean1" scope="prototype"/>
Spring AOP
术语
- 通知
- 连接点
- 切点
- 切面
假设我们在某个方法调用的时候,需要在其调用之前进行权限校验,在方法调用之后进行日志记录。则该方法可以被称为连接点,权限校验和日志记录称为前置通知和后置通知,切点定义了通知被具体应用的位置,通知和切点组成切面。
Spring对AOP的支持
- Spring通知是基于java编写的
Spring在运行时通知对象
代理对象包裹目标对象,当代理对象拦截到对目标对象的方法调用时,会在目标对象的方法被调用之前先调用切面逻辑。由于Spring在运行时才会创建代理对象,因此不需要特殊的编译器。
- Spring只支持方法级别的连接点
Spring AOP的使用
编写切点
//该切点会匹配com包下的Performace类的perform方法,
//无论该方法的参数与返回值类型
execution(* com.Performance.perform(..))
execution(* com.Performace.perform(..)) and within(com.*)
在切点中选择Bean
//在上面的条件上,再次限定bean的id
execution(* com.Performance.perform(..)) and bean("my_bean_name")
使用注解创建切面
@Aspect
public class MyAspect{
//连接点方法调用之前被调用
@Before("execution(* com.Performance.perform(..))")
public befreMethodInvoke(){
}
//连接点方法返回之后被调用
@AfterReturning("execution(* com.Performance.perform(..))")
public afterMethodInvoke(){
}
//连接点方法抛出错误之后被调用
@AfterThrowing("execution(* com.Performance.perform(..))")
public afterMethodInvokeThrowException(){
}
//连接点方法抛出错误之后被调用,或者连接点方法正常返回之后被调用
@After("execution(* com.Performance.perform(..))")
public afterMethodInvoke(){
}
}