spring有名的就是控制反转和依赖注入了,而其真正的意思我并未真正了解过。
官网开篇说:控制反转就是依赖注入
IoC is also known as dependency injection (DI)
翻译过来就是:控制反转(IOC)也称依赖注入(DI)。
这里涉及到几个概念:
容器:在spring应用中,对象生存在spring容器中,由容器负责创建、装配对象和管理它们的生命周期。
bean:属于应用程序的一部分,且由容器管理的对象成为bean。bean一定是java对象,但java对象不一定是bean。
控制反转: 对象仅通过构造函数参数、工厂方法的参数或从工厂方法构造或返回对象后在对象实例上设置的属性来定义其依赖关系(即它们使用的其他对象) ,而容器在创建bean时,注入这些依赖项,从人为手动控制,变成由容器控制,从过程上来说是bean的反向,故称控制反转。
这里有一个知识点:bean
标签里的property
标签里的name并不是对应bean对象里的属性名称,它对应的是set
方法的名称,就如下面的例子一样,property
里的name属性值跟着set
方法改变而改变。
public class AService {
}
public class BService {
private AService aService333;
public AService getaService() {
return aService333;
}
public void setaService(AService aService) {
this.aService333 = aService;
}
}
这种方式是手动注入:显示的将bean设置到某一个bean单中。
<?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 id="aService" class="com.lry.spring.service.AService">
</bean>
<bean id="bService" class="com.lry.spring.service.BService">
<property name="aService" ref="aService"/>
</bean>
</beans>
在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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
<!--这里指定装配类型为byType-->
default-autowire="byType"
>
<bean id="aService" class="com.lry.spring.service.AService">
</bean>
<bean id="bService" class="com.lry.spring.service.BService">
<!-- <property name="aService" ref="aService"/>-->
</bean>
</beans>
使用byType、byName模型,都需要设置setter方法。
我们常使用的@Autowired
这个注解,从严格意义上说不应该叫做自动注入,为什么这么说?
如果,我们将它的autowireModel改成2(可以到AutowireCapableBeanFactory查看对应的值),上面代码增加下面这一行:
bService.setAutowireMode(2);
结果已经注入了。
这里要区分注入模型,和注入方式,两种方式在找bean和注入bean的技术不一样,一般说的注入方式指的是手动注入,那么手动注入的方式有两种:构造器和setter方法。
setter方法注入方式很简单,就需要注入的属性必须创建set方法。
xml方式注入和上面的例一样,通过property
标签里的ref
链接bean对象。
<property name="aService" ref="aService"/>
注解的方式使用Resource和Autowired,两者区别在于Resource是JDK实现,他会先按名称查找,然后再按类型查找,而Autowired有Spring实现,先按类型查找,再按名称查找,而且找不到时会抛出异常。
@Autowired
private AService aService;
或者是:
@Autowired
public void setaServicesss(AService aService) {
this.aService = aService;
}
基于构造器注入的方式和setter方式的注入会有些不同;构造器的方式支持索引。
定义构造器了参数对象,如果有多个构造器,每个构造器有多个参数,多个类型,很难控制注入的构造器时哪一个,所以会提供了这些功能。
public BService(AService aService) {
this.aService = aService;
}
<bean id="aService" class="com.lry.spring.service.AService">
</bean>
<bean id="bService" class="com.lry.spring.service.BService">
<constructor-arg ref="aService"/>
</bean>
使用构造器注入他会匹配参数,使用基本类型作为参数,无法确定值的类型,所以应该想下面设置类型(type)去匹配参数:
public BService(int a, AService aService) {
this.a = a;
this.aService = aService;
}
<bean id="bService" class="com.lry.spring.service.BService">
<constructor-arg ref="aService"/>
<constructor-arg type="int" value="1"/>
</bean>
相同类型参数时无法确定参数位置,可以使用索引方式注入
public BService(int a, int c){
}
<bean id="bService" class="com.lry.spring.service.BService">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="2"/>
</bean>
<bean id="bService" class="com.lry.spring.service.BService">
<constructor-arg name="a" value="1"/>
<constructor-arg name="c" value="2"/>
</bean>
官网上说需要ConstructorProperties
注解显示指定参数名称,我测试的例子,在没有这个注解时也成功注入了
@ConstructorProperties({"c", "a"})
public BService(int a, int c){
System.out.println("a : " + a);
System.out.println("c : " + c);
}
构造参数为基本类型的注入方式,我想不出这有什么实际应用场景,但作为了解也好。
spring创建bean的方式:
bean有6个作用域:
单例和原型模式,要在bean上加上注解@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
,request、session这些模式,有针对的注解@RequestScope
、@SessionScope
,当然也可以自定义一个作用域,
一般使用@Autowire、@Resource、@Component等这些注解都是单例的signleton
。
如果在单例bean中注入一个设定了prototype的bean,那么它也成了单例的,要实现在单例bean中多次获取不同的实例,可以通过方法注入实现;
ApplicationContextAware
接口,或是注入ApplicationContext
对象,然后通过它调用getBean方法去创建一个bean,@Component
public class SpringUtils {
@Autowired
private ApplicationContext applicationContext;
public <T> T getBean(String beanName, Class<T> tClass){
return applicationContext.getBean(beanName, tClass);
}
}
@Component
public class SpringUtils implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public <T> T getBean(String beanName, Class<T> tClass){
return applicationContext.getBean(beanName, tClass);
}
}
首先要获取的bean要设置scope为prototype
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class AService implements IService {
public AService() {
System.out.println("this aService hascode" + this);
}
}
@Component
public class CService {
@Lookup
public AService getAService() {return null;}
}
官网给的格式是这样的:
<public|protected> [abstract] theMethodName(no-arguments);
也可以定义为抽象方法:
@Component
public abstract class CService {
@Lookup
public abstract AService getAService();
}
xml的配置方式:
<bean id="aService" class="com.lry.spring2.service.Impl.AService" scope="prototype"/>
<bean id="cService" class="com.lry.spring2.service.Impl.CService">
<lookup-method name="getAService" bean="aService"/>
</bean>
有3种回调:初始化回调、销毁回调和启动关闭回调。
bean 的生命周期回调有两个:初始化回调和销毁回调;容器的回调有启动和销毁回调。
InitializingBean
类的afterPropertiesSet
方法;public class DService implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
//...
}
}
@PostConstruct
注解在任意方法上,在初始化后就会执行该方法。
<bean id="dService" class="com.lry.spring2.service.Impl.DService" init-method="post"/>
Lifecycle
或者SmartLifecycle
Lifecycle
有3个方法
// isRunning 返回false时执行
void start();
// isRunning 返回true时执行
void stop();
// 当返回true是表示启动中,可以调用stop方法,
// 当放回false是表示没有启动,可以调用start方法
boolean isRunning();
当容器启动完成后,会调用Lifecycle
的start方法,关闭时会调用stop方法,但是如果程序退出,容器也跟着退出,监听不到容器,就不会执行stop方法。
在执行start和stop方法之前,会先调用isRunning判别bean是否启动。
它不好的地方在于,需要显示调用start和stop方法,但一般的web项目都是使用了springboot、springMvc等,容器入口得被封装起来了,无法去调用这些方法,这时候就扩展出了SmartLifecycle
,它不需要显示调用start方法,可以控制多个smartLifecycle实例的调用顺序。
SmartLifecycle
有6个方法,3个是新增的,其他3个是lifeCycle里的。
// 返回true表示会自动执行start方法
boolean isAutoStartup();
// 容器关闭是调用;使用这个方法的时候,需要调用callback.run() 去结束程序;
// 只调用stop(),默认会等待30秒,等待所有任务结束才会关闭,调用callback会立刻退出
void stop(Runnable callback);
// 多个smartlifecycle实例存在,会按照该方法返回的值进行优先级调用;
// 值越小,执行start方法的优先级越高,值越大,执行stop方法的优先级越高
int getPhase();
// 在容器初始化完成,会调用finishRefresh方法,里面通过Lifecycle处理器,获取到所有的smartLifecycle,然后调用start方法。
void start();
void stop();
boolean isRunning();
同一个bean中用了不同方式配置,那么它的执行顺序应该是:
1.注解@PostConstruct
2.InitializingBean
类的afterPropertiesSet
方法
3.xml配置的方法
销毁也是相同顺序。
大型应用会创建静态索引,是扫描更快,但需要加一个content-index jar包
inject jar包可以替代spring的注解,可能会有人不喜欢Autowired注解,可以换成inject里的,也可以自定义注入注解(反射)
注解的使用可以让我们省掉很多代码,并且代码看起来很更加简洁干净,spring中就用了很多注解,像@Autowired、@Service、@Controller、@Component等等,然后通过componentScan去扫描这些类,或是使用xml方式配置扫描。
@ComponentScan(basePackages = "com.lry.spring2",
// 包含的指定的类
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = AService.class),
// 根据正则排除某些类
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.lry.*"))
类路径扫描的方式很快,但是在大型项目中,需要扫描的bean比较多,可以引用spring-context-indexer
包,它会生成静态索引使得应用启动加载更快。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.2.9.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
我们在使用spring的最常用到的就是Autowired、Resource、Service等等注解,其实也可以替换成其他注解,相同的功能。需要引入jar包,它属于JSR 330规范。
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
之前使用的@Autowired
就可以替换成@Inject
,可以通过@Named
指定要注入的bean的名称,@Component
等效于@Named
、@ManagedBean
。
// 可以不指定名称
@Named
public class EService {
@Inject
// 这个named可以不用,不过根据名称来指定注入需要
@Named("ffService")
private FService fService;
public void print() {
System.out.println("fService :" + fService);
}
}
@Named("ffService")
// 作为bean的注解 @ManageBean("ffService")也有同样的效果
public class FService {
}
扫描bean的注解都是@ComponentScan
。
spring | javax.* | 说明 |
---|---|---|
@Autowired | @Inject | @Inject按类型装配,如果需要指定名称需要配合@Named使用,而@Autowired是先按类型装配,找不到就按名称。 |
@Component | @Named/@ManagedBean | 指定bean。 |
@Scope(“singleton”) | @Singleton | JSR 330规范中默认是Singleton,若要设置其他类型应该使用spring中的@Scope。 |
@Qualifier | @Qualifier / @Named | 确定注入的具体类。 |
@Value | - | |
@Required | - | |
@Lazy | - |
把业务对象放到spring容器中的方式:
我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2x3a8wy49pwkc