Spring Ioc应用详解
文章目录
1.什么是spring Ioc
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)
问:Spring Ioc 和 DI 有什么区别
答:Ioc 为目标即控制反转这个目标,DI是实现Ioc的一种技术手段。
实现Ioc 的还有一种技术,叫依赖查找。
依赖查找使用:
例如tomcat jndi数据源配置
DataSource ds = (DataSource)ctx.lookup("java:comp/env/oracleDataSource");
该代码就是根据java:comp/env/oracleDataSource 服务名称查找到 DataSource 实例赋值值给ds 。
摘抄: https://blog.csdn.net/qq_30336433/article/details/82867721
2.为什么要使用 Spring Ioc
在日常程序开发过程当中,我们推荐面向抽象编程,面向抽象编程会产生类的依赖,当然如果你够强大可以自己写一个管理的容器,但是既然spring以及实现了,并且spring如此优秀,我们仅仅需要学习spring框架便可。
当我们有了一个管理对象的容器之后,类的产生过程也交给了容器,至于我们自己的app则可以不需要去关系这些对象的产生了。
问:什么是面向抽象编程
答:请参考该博客:https://www.cnblogs.com/chiweiming/p/9229457.html
3.spring实现IOC的思路和方法
spring实现IOC的思路是提供一些配置信息用来描述类之间的依赖关系,然后由容器去解析这些配置信息,继而维护好对象之间的依赖关系,前提是对象之间的依赖关系必须在类中定义好,比如A.class中有一个B.class的属性,那么我们可以理解为A依赖了B。
问:既然我们在类中已经定义了他们之间的依赖关系那么为什么还需要在配置文件中去描述和定义呢?
如下代码A 类中已经定义了B 的属性 既定义了依赖关系,为什么还需要在xml通过property 描述下A和B的依赖关系 是不是有点多余。
package com.Ioc;
public class A {
private B b;
private C c = new C();
public void printA(){
System.out.println(b.getBname());
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
public C getC() {
return c;
}
}
package com.Ioc;
public class B {
public String getBname(){
return "this is B";
}
}
package com.Ioc;
public class C {
public void printC(){
System.out.println("this is C");
}
}
package com;
import com.Ioc.A;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringIoc {
public static void main(String[] args) {
ClassPathXmlApplicationContext cpa = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
A a = (A) cpa.getBean("a");
a.printA();
a.getC().printC();
}
}
执行结果: this is B
this is C
配置文件:applicationContext.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd ">
<bean name="a" class="com.Ioc.A">
<!--A类中b必须提供set 属性才能注入B的实体类-->
<property name="b" ref="b"></property>
</bean>
<bean name="b" class="com.Ioc.B"></bean>
</beans>
答:
Spring Ioc 的实现思路:
1.应用程序中提供类,提供依赖关系(属性或者构造方法)
2.把需要交给容器管理的对象通过配置信息告诉容器(xml、annotation,javaconfig)
3.把各个类之间的依赖关系通过配置信息告诉容器
从上面实现思路可知,哪些对象 或对象中的依赖关系需要交给spring 容器或上下文去管理,需要开发者自己来通过配置信息描述告诉spring 容器。比如上述代码 A 类中的 c 属性 ,我不需要spring容器的管理, 并没有在xml中描述依赖关系。
4.spring编程的风格
schemal-based-------xml
annotation-based-----annotation
java-based----java Configuration
spring编程风格可以用xml 和 annotation 组合 : ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(“spring.xml”);
开启注解和扫描:
<context:annotation-config /> //开启注解
<context:component-scan base-package=“com”/>//开启扫描包
在spring 3 之后 只需要配置
<context:component-scan base-package=“com”/> //开启注解和扫描包功能
spring编程风格 Java Configuration 和 annotation组合: AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Appconfig.class);
@Configuration 注解表示 一个配置java类 类似于xml文件
@CompoentScan(“com”) 扫描 包 类型与 <context:component-scan base-package=“com”/>
5.注入的两种方法
5.1 构造函数注入
如下代码:
package com.Ioc;
public class Car {
public void printCar(){
System.out.println("this is car");
}
}
package com.Ioc;
public class Student {
private Car carTest;
public Student(Car carTest) {
this.carTest = carTest;
}
public Car getCar() {
return carTest;
}
}
package com;
import com.Ioc.Student;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringIoc {
public static void main(String[] args) {
ClassPathXmlApplicationContext cpa = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Student student = (Student) cpa.getBean("student");
student.getCar().printCar();
}
}
执行结果:this is car
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd ">
<bean name="student" class="com.Ioc.Student">
<constructor-arg ref="car"/>
</bean>
<bean name="car" class="com.Ioc.Car"></bean>
</beans>
5.2 set 属性注入
如下代码:
package com.Ioc;
public class Car {
public void printCar(){
System.out.println("this is car");
}
}
package com.Ioc;
public class Student {
private Car carTest;
public void setCar(Car car) {
this.carTest = car;
}
public Car getCar() {
return carTest;
}
}
import com.Ioc.Student;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringIoc {
public static void main(String[] args) {
ClassPathXmlApplicationContext cpa = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Student student = (Student) cpa.getBean("student");
student.getCar().printCar();
}
}
执行结果:this is car
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd ">
<bean name="student" class="com.Ioc.Student">
<!--注意:这个地方其实不是属性名称 而是 setCar 中car的名称-->
<property name="car" ref="car"></property>
</bean>
<bean name="car" class="com.Ioc.Car"></bean>
</beans>
注释:通过set方法,注入实体类,property 标签中的名称,实际spring 是根据 set+name属性值 去匹配set方法去注入的,并不是真正的student 中的属性名称car。上述代码已经证明。
5.3 接口注入,在spring 4中 spring取消了该注入方式。
6.自动装配的方法
上面说过,IOC的注入有两个地方需要提供依赖关系,一是类的定义中,二是在spring的配置中需要去描述。自动装配则把第二个取消了,即我们仅仅需要在类中提供依赖,继而把对象交给容器管理即可完成注入。
自动装配的方法
6.1 byType 通过类型自动装配(以下示例基于xml编码风格)
可使用 default-autowire=“byType” 全局配置使用类型自动装配。
如下代码:
package com.Ioc;
public class Car {
public void printCar(){
System.out.println("this is car");
}
}
package com.Ioc;
public class Student {
private Car carTest;
public Car getCar() {
return carTest;
}
public void setCarTest(Car carTest) {
this.carTest = carTest;
}
}
package com;
import com.Ioc.Student;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringIoc {
public static void main(String[] args) {
ClassPathXmlApplicationContext cpa = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Student student = (Student) cpa.getBean("student");
student.getCar().printCar();
}
}
执行结果:this is car
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
"
default-autowire="byType"> <!--全局配置通过类型注入 (使用的是 set 注入器)-->
<bean name="student" class="com.Ioc.Student">
</bean>
<bean name="car" class="com.Ioc.Car"></bean>
</beans>
注释:这里 Student 中必须提供set 注入器。
6.2 byName 通过名称自动装配(以下示例基于xml编码风格)
可使用 default-autowire=“byName” 全局配置 使用名称自动装配
如下代码:
package com.Ioc;
public class Car {
public void printCar(){
System.out.println("this is car");
}
}
package com.Ioc;
public class Student {
private Car carTest;
public Car getCar() {
return carTest;
}
public void setCarTestOne(Car carTest) {
this.carTest = carTest;
}
}
package com;
import com.Ioc.Student;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringIoc {
public static void main(String[] args) {
ClassPathXmlApplicationContext cpa = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Student student = (Student) cpa.getBean("student");
student.getCar().printCar();
}
}
执行结果:this is car
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
"
default-autowire="byName"><!--全局配置通过名称注入 (使用的是 set 注入器)-->
<bean name="student" class="com.Ioc.Student">
</bean>
<bean name="carTestOne" class="com.Ioc.Car"></bean>
</beans>
注释:这里Student 中必须提供set 方法。spring 会根据Student 中 private Car carTest; 找到 set属性方法,只要 set 方法入参类型为 Car 就是 该carTest 属性的set 属性构造器 (setCarTestOne)。然后 spring 会根据 setCarTestOne,提取出carTestOne(第一个字母小写),去查找 name 为 carTestOne 实体自动注入。
6.3 构造方法自动注入
这里其实 不是自动装配,还是要在xml 中 描述构造器
注释 :局部配置
局部配置 通过名称:<bean name="student" class="com.Ioc.Student" autowire="byName"><`/bean>
局部配置 通过类型:<bean name="student" class="com.Ioc.Student" autowire="byType"><`/bean>
6.4 @Autowired 和 @Resource 自动装配(基于注解的编码风格)
区别:
@Resource(这个注解属于J2EE的)装配顺序
1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
默认使用 byName匹配
@Autowired(这个注解属于spring注解)
1.默认使用byType 自动装配策略注入。
2. @Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用,如下:
@Autowired()@Qualifier(“baseDao”)
privateBaseDao baseDao;
7.怎么修改自动装配中bean默认的命名规则
其实如果项目稍微大时就会出现问题,大家都知道spring的bean的id必须唯一,如果两个人同事写代码就有可以造成写同样的bean名称。
解决这个问题的一个思路是把bean的名称修改为 类的全路径,例如org.sdp.A 和com.bey.A 。
只要修改spring默认的bean命名策略就可以了。
java Configuration 编码风格:
AnnotationBeanNameGenerator是bean的默认命名策略,他实现了BeanNameGenerator接口。
如下代码:继承AnnotationBeanNameGenerator 重写generateBeanName方法。
package com.ioc;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
/**
* 继承AnnotationBeanNameGenerator 重写 generateBeanName 方法
*/
public class MyAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
/*return super.generateBeanName(definition, registry);*/
//全限定类名
return definition.getBeanClassName();
}
}
package com.ioc;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
/*配置 扫描包 nameGenerator*/
@ComponentScan(value = "com",nameGenerator=MyAnnotationBeanNameGenerator.class)
public class AppConfig {
}
/*测试*/
public class Testmain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println(ac.getBean("com.ioc.entry.Student"));
}
}
xml编码风格:
package com.ioc;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
public class MyDefaultBeanNameGenerator extends DefaultBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
/*return super.generateBeanName(definition, registry);*/
//全限定类名
return definition.getBeanClassName();
}
}
spring.xml 配置为 :<context:component-scan base-package="org.sdp" name-generator="com.ioc.MyDefaultBeanNameGenerator" />
8.spring懒加载
9.springbean的作用域
xml定义方式
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
annotation的定义方式
@Scope(“prototype”)
10.单利对象引用一个原形对象
如下代码 :Car 虽然是原型模式,但是打印的对象地址来看是同一个对象,并没有重新生成对象。
@Component
@Scope("prototype")
public class Car {
}
@Component
@Scope("singleton")
public class Student {
@Autowired
private Car car;
public Car getCar() {
System.out.println("Student 对象引用地址"+this);
System.out.println("car 对象引用地址"+car);
return car;
}
}
/*测试*/
public class Testmain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
Student student = (Student) ac.getBean("com.ioc.entry.Student");
student.getCar();
student.getCar();
student.getCar();
}
}
执行结果:
Student 对象引用地址com.ioc.entry.Student@5d12a356
car 对象引用地址com.ioc.entry.Car@134d26af
Student 对象引用地址com.ioc.entry.Student@5d12a356
car 对象引用地址com.ioc.entry.Car@134d26af
Student 对象引用地址com.ioc.entry.Student@5d12a356
car 对象引用地址com.ioc.entry.Car@134d26af
解决方案:
第一种:
从ApplicationContext 中重新拿去对象。
@Component
@Scope("singleton")
public class Student {
@Autowired
private ApplicationContext context;
private Car car;
public Car getCar() {
this.car = context.getBean("com.ioc.entry.Car",Car.class);
System.out.println("Student 对象引用地址"+this);
System.out.println("car 对象引用地址"+car);
return car;
}
}
/*测试*/
public class Testmain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
Student student = (Student) ac.getBean("com.ioc.entry.Student");
student.getCar();
student.getCar();
student.getCar();
}
}
执行结果:
Student 对象引用地址com.ioc.entry.Student@4748a0f9
car 对象引用地址com.ioc.entry.Car@4b14918a
Student 对象引用地址com.ioc.entry.Student@4748a0f9
car 对象引用地址com.ioc.entry.Car@6d1ef78d
Student 对象引用地址com.ioc.entry.Student@4748a0f9
car 对象引用地址com.ioc.entry.Car@1a6c1270
第二种:
```cpp
@Component
@Scope("singleton")
public class Student {
private Car car;
@Lookup
protected Car methodInject() {return null;}
public Car getCar() {
this.car = methodInject();
System.out.println("Student 对象引用地址"+this);
System.out.println("car 对象引用地址"+car);
return car;
}
}
执行结果:
Student 对象引用地址com.ioc.entry.Student$$EnhancerBySpringCGLIB$$772dd384@38831718
car 对象引用地址com.ioc.entry.Car@2c1156a7
Student 对象引用地址com.ioc.entry.Student$$EnhancerBySpringCGLIB$$772dd384@38831718
car 对象引用地址com.ioc.entry.Car@33fe57a9
Student 对象引用地址com.ioc.entry.Student$$EnhancerBySpringCGLIB$$772dd384@38831718
car 对象引用地址com.ioc.entry.Car@4982cc36
11.bean的生命周期回调
11.1初始生命周期回调:
第一种:实现 InitializingBean 接口
@Component
public class Student implements InitializingBean {
public Student() {
System.out.println("构造函数");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("生命周期回调.....");
}
}
public class Testmain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
Student student = (Student) ac.getBean("com.ioc.entry.Student");
}
}
执行结果:
构造函数
生命周期回调.....
第二种:xml配置init-method(xml编程风格)
<bean id="student" class="com.ioc.entry.Student" init-method="init"/>
@Component
public class Student {
public Student() {
System.out.println("构造函数");
}
public void init(){
System.out.println("生命周期回调.....");
}
}
第三种:使用JSP-250规范中的注解@PostConstruct
@Component
public class Student {
public Student() {
System.out.println("构造函数");
}
@PostConstruct
public void afterPropertiesSet() throws Exception {
System.out.println("生命周期回调.....");
}
}
第四种:使用java config注解
@Configuration
@ComponentScan(value = "com",nameGenerator= MyAnnotationBeanNameGenerator.class)
public class AppConfig {
@Bean(initMethod = "init")
public Student student1(){
return new Student();
}
}
public class Student {
public Student() {
System.out.println("构造函数");
}
public Student init(){
System.out.println("生命周期......");
return this;
}
}
public class Testmain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
Student student = (Student) ac.getBean("student1");
}
}
执行结果:
构造函数
生命周期......
注释:在一个bean中,配置多种生命周期回调机制,会按照下列次序调用:
@PostConstruct注解的方法 ——> InitializingBean回调接口中的afterPropertiesSet()方法 ——>自定义的init()方法
11.1生命周期销毁回调:
第一种:实现DisposableBean接口
@Repository
public class Student implements DisposableBean {
public Student() {
System.out.println("构造函数");
}
@Override
public void destroy() throws Exception {
System.out.println("生命周期销毁回调........");
}
}
public class Testmain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
Student student = (Student) ac.getBean("com.ioc.entry.Student");
ac.close();
}
}
执行结果:
构造函数
生命周期销毁回调.....
第二种:使用xml注解,配置destory-method元素
<bean id="student" class="com.ioc.entry.Student" destroy-method="cleanup"/>
@Repository
public class Student {
public Student() {
System.out.println("构造函数");
}
public void cleanup() throws Exception {
System.out.println("生命周期销毁回调........");
}
}
第三种:JSR-250注解的方式 @PreDestroy
@Repository
public class Student {
public Student() {
System.out.println("构造函数");
}
@PreDestroy
public void PreDestory () throws Exception {
System.out.println("生命周期销毁回调........");
}
}
第四种:使用java Config,要在@Bean
public class Student {
public Student() {
System.out.println("构造函数");
}
@PreDestroy
public void preDestory () throws Exception {
System.out.println("生命周期销毁回调........");
}
}
@Configuration
@ComponentScan(value = "com",nameGenerator= MyAnnotationBeanNameGenerator.class)
public class AppConfig {
@Bean(destroyMethod = "preDestory")
public Student student1(){
return new Student();
}
}
public class Testmain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
ac.close();
}
}
执行结果:
构造函数
生命周期销毁回调........
注释:@PreDestroy注解的方法 ——> DisposableBean回调接口中的destroy()方法 ——>自定义的destroy()方法
总结:不管是通过实现 InitializingBean/DisposableBean 接口,还是通过 元素的init-method/destroy-method 属性进行配置,都只能为 Bean 指定一个初始化 / 销毁的方法。但是使用 @PostConstruct 和 @PreDestroy 注释却可以指定多个初始化 / 销毁方法,那些被标注 @PostConstruct 或 @PreDestroy 注释的方法都会在初始化 / 销毁时被执行。