IOC的本质理解:
IOC(Inversion of Control),本质是一种设计思想,并不是Spring框架所独有的,IOC是Spring框架的核心内容之一。
在没有IOC程序之前,我们使用面向对象编程,对于对象的创建和对象间的依赖关系完全是在程序中使用编码所实现的,对象的创建是由程序员所决定的,有程序控制。
而控制反转,是将对象的创建转移给第三方,个人认为所谓控制反转就是获得依赖对象的方式反转了。
使用IOC的最终目的是解除多个类之间的耦合性,降低代码的耦合度。
传统的对于对象创建是程序员在类体里面进行类的实现,如
new User();诸如此类的操作,
而通过IOC的思想可以在需要此类别时,直接调用方法即可,降低了耦合性,使得代码之间的联系越来越疏远。
开发人员再也不用去new一个对象来实现对实例的创建,只需知道类名和指定的xml文件即可。
ApplicationContext context =new AnnotationConfigApplicationContext(XXX.class);
//或者
ApplicationContext context = new ClassPathXmlApplicationContext("XXX.xml");
使用Spring的IOC功能,必须先引入Spring的核心依赖包(使用Maven作为构建工具):
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
IOC的底层原理:
IOC的底层原理实际就是通过xml解析,外加工厂原理和反射。
IOC的使用过程:
IOC的实现有两个(通过xml文件,通过注解)
1.通过xml配置文件的方式进行对象的创建和属性的注入
示例代码:
a.实现对User类的对象创建(默认是使用无参构造器进行创建)
<bean id="user" class="com.guo.User"></bean>
b.实现对user对象属性的注入
<property name="name" value="郭帅"></property>>
c.这段代码展现了IOC解耦合的效果
通过注入外部bean类,对类内部有关联的对象进行填充
通过注入外部bean类,对类内部有关联的对象进行填充,userService声明中多出了一个property的标签,这个标签指向了我们刚才创建的userDao对象,它的作用是把userDao对象传递给userService实现类中的userDao属性,该属性必须拥有set方法才能注入成功,我们把这种往类userService对象中注入其他对象(userDao)的操作称为依赖注入,这个后面会分析到,其中的name必须与userService实现类中变量名称相同,到此我们就完成对需要创建的对象声明。接着看看如何使用它们。
<bean id="userDao" class="com.guo.dao.UserDaoImpl"></bean>
<bean id="userService" class="com.guo.service.UserService">
<property name="userDao" ref="userDao"></property>
</bean>
d.IOC操作对Bean管理(xml自动装配):
自动装配:根据指定的装配规则(属性名称或属性类型),spring自动将匹配的属性值进行注入
在bean标签中有一个autowire属性,有两个值
autowire="byName" // 根据属性名称注入,要注入值bean的id值和类属性名称一样
autowire="byType" // 根据属性类型注入
e.IOC操作Bean管理(外部属性文件)
首先看一下传统的对配置文件的处理:
这里是使用数据库连接池的配置
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root"></property>
<property name="password" value="000000"></property>
<property name="url" value="jdbc:mysql://localhost:3306/book?serverTimezone=GMT%2B8"></property>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
</bean>
传入外部文件进行配置操作:
e.1创建外部配置资源文件
e.2引入空间名称
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
e.3先引入配置文件,在把对应的properties文件中的key通过表达式传入
<context:property-placeholder location="classpath*:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${prop.username}"></property>
<property name="password" value="${prop.password}"></property>
<property name="url" value="${prop.url}"></property>
<property name="driverClassName" value="${prop.driverClassName}"></property>
</bean>
1.通过注解的方式进行对象的创建和属性的注入
Spring针对Bean管理中创建对象提供注解:
a)@Component
b)@Service
c)@Controller
d)@Repositroy
*上面的四个注解的功能是一样的,都可以用来创建bean对象实例
a.基于注解方式实现对象创建
①引入依赖,jar包
②开启组件扫描
还需要添加context名称空间
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
③添加扫描配置
<context:component-scan base-package=""></context:component-scan>
对包内的文件进行扫描,如果有找到相应的注解,就会生成该对象
在base-package中添加要扫描的包名
如果要添加多个包进行扫描,可以用逗号隔开
④创建类,在类的上面添加创建对象注解
//注解value属性值可以省略不写,默认值为类名首字母小写
@Component(value = "userService")//等同于<bean id="userService">
public class UserService {
public void add(){
System.out.println("add......");
}
}
b.开启组件扫描的一些细节配置
①以下示例为只会去com.guo的包下带有@Controller注解的进行创建
<context:component-scan base-package="com.guo" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
②以下示例是在com.guo包下的除了@Controller注解的进行创建
<context:component-scan base-package="com.guo">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
c.基于注解方式进行属性注入
①@AutoWired:根据属性类型进行注入
代码演示:
把service和dao对象创建,在service和dao类添加创建对象的注解
@Repository
public class UserDaoImpl implements UserDao {
}
在service里面注入dao对象
@Autowired
private UserDao userDao;
②@Qualifier:根据属性的名称进行注入
这个要和@AutoWired一起使用
代码演示:
a.在daoImpl文件中设置value值
@Repository(value = "userDaoImpl")
public class UserDaoImpl implements UserDao {
b.在service文件中配置注解的value值
@Autowired
@Qualifier(value = "userDaoImpl")//默认为首字母小写,为获取名称为userDaoImpl1的对象
private UserDao userDao;
d.完全注解开发
创建配置类,替代xml配置文件
@Configuration //作为配置类,替代xml文件
@ComponentScan(basePackages = {"com.guo"})
public class SpringConfig {
}
至此,以上代码就可以完成对对象的全部创建和扫描等工作。
===================================================================
再来看一个实例:
@Configuration
public class BeanConfiguration {
@Bean
public AccountDao accountDao(){
return new AccountDaoImpl();
}
@Bean
public AccountService accountService(){
AccountServiceImpl bean=new AccountServiceImpl();
//注入dao
bean.setAccountDao(accountDao());
return bean;
}
}
上述代码中使用了@Configuration注解标明BeanConfiguration类,使得BeanConfiguration类替代了xml文件,也就是说注解@Configuration等价于<beans>标签。在该类中,每个使用注解@Bean的公共方法对应着一个<bean>标签的定义,即@Bean等价于<bean>标签。这种基于java的注解配置方式是在spring3.0中引入的,此时使用到注解,因此必须确保spring-context包已引入。
这里我们需要明白的是,在大部分情况下更倾向于使用xml来配置Bean的相关信息,这样会更加方便我们对代码进行管理,因此后面的分析都会基于xml的配置,除了前面通过在xml中使用<bean>标签为每个类声明实例外,Spring容器还为我们提供了基于注解的声明方式,这点放在后面分析,接着再来解Spring提供的依赖注入功能。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
Spring的依赖注入
所谓的依赖注入,其实是当一个bean实例引用到了另外一个bean实例时spring容器帮助我们创建依赖bean实例并注入(传递)到另一个bean中,如上述案例中的AccountService依赖于AccountDao,Spring容器会在创建AccountService的实现类和AccountDao的实现类后,把AccountDao的实现类注入AccountService实例中,下面分别介绍setter注入和构造函数注入。
Setter注入
Setter注入顾名思义,被注入的属性需要有set方法,Setter注入支持简单类型和引用类型,Setter注入时在bean实例创建完成后执行的。直接观察前面的案例,对象注入使用<property>的ref属性。
<!-- 声明accountDao对象并交给spring创建 -->
<bean name="accountDao" class="com.spring.springIoc.dao.impl.AccountDaoImpl"/>
<!-- 声明accountService对象,交给spring创建 -->
<bean name="accountService" class="com.spring.springIoc.service.impl.AccountServiceImpl">
<!-- 通过setter注入accountDao对象,对象注入使用ref-->
<property name="accountDao" ref="accountDao"/>
</bean>
除了上述的对象注入同时也可以注入简单值和map、set、list、数组,简单值注入使用<property>的value属性:
public class Account {
private String name;
private String pwd;
private List<String> citys;
private Set<String> friends;
private Map<Integer,String> books;
public void setName(String name) {
this.name = name;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public void setCitys(List<String> citys) {
this.citys = citys;
}
public void setFriends(Set<String> friends) {
this.friends = friends;
}
public void setBooks(Map<Integer, String> books) {
this.books = books;
}
}
注入代码如下:
<!-- setter通过property 注入属性值,普通类型使用value -->
<bean id="account" scope="prototype" class="com.spring.springIoc.pojo.Account" >
<property name="name" value="I am SpringIOC1" />
<property name="pwd" value="123" />
<!-- 注入map -->
<property name="books">
<map>
<entry key="10" value="CoreJava">
</entry>
<entry key="11" value="JavaWeb">
</entry>
<entry key="12" value="SSH2">
</entry>
</map>
</property>
<!-- 注入set -->
<property name="friends">
<set>
<value>张龙</value>
<value>老王</value>
<value>王五</value>
</set>
</property>
<!-- 注入list -->
<property name="citys">
<list>
<value>北京</value>
<value>上海</value>
<value>深圳</value>
<value>广州</value>
</list>
</property>
</bean>
构造函数入
构造注入也就是通过构造方法注入依赖,构造函数的参数一般情况下就是依赖项,spring容器会根据bean中指定的构造函数参数来决定调用那个构造函数,同样看一个案例:
public class AccountServiceImpl implements AccountService{
/**
* 需要注入的对象Dao层对象
*/
private AccountDao accountDao;
/**
* 构造注入
* @param accountDao
*/
public AccountServiceImpl(AccountDao accountDao){
this.accountDao=accountDao;
}
//........
}
<bean name="accountDao" class="com.spring.springIoc.dao.impl.AccountDaoImpl"/>
<!-- 通过构造注入依赖 -->
<bean name="accountService" class="com.spring.springIoc.service.impl.AccountServiceImpl">
<!-- 构造方法方式注入accountDao对象,-->
<constructor-arg ref="accountDao"/>
</bean>
当然跟setter注入一样,构造注入也可传入简单值类型和集合类型,这个比较简单,不啰嗦。需要注意的是,当一个bean定义中有多个<constructor-arg>标签时,它们的放置顺序并不重要,因为Spring容器会通过传入的依赖参数与类中的构造函数的参数进行比较,尝试找到合适的构造函数。可惜的是,在某些情况下可能会出现问题,如下的User类,带有两个构造函数,参数类型和个数都是一样的,只是顺序不同,这在class的定义中是允许的,但对于Spring容器来说却是一种灾难。
public class User {
private String name;
private int age;
//第一个构造函数
public User(String name , int age){
this.name=name;
this.age=age;
}
//第二个构造函数
public User(int age,String name){
this.name=name;
this.age=age;
}
}
<bean id="user" class="com.spring.springIoc.pojo.User" >
<constructor-arg type="java.lang.String" value="Jack"/>
<constructor-arg type="int" value="26"/>
</bean>
当程序运行时,Spring容器会尝试查找适合的User构造函数进而创建User对象,由于<constructor-arg>的注入顺序并不重要,从而导致不知该使用两种构造函数中的那种,这时user实例将创建失败,Spring容器也将启动失败。幸运地是,Spring早已为我们预测到这种情况,因此只要给Spring容器一点点提示,它便能成功找到适合的构造函数从而创建user实例,在<constructor-arg>标签中存在一个index的属性,通过index属性可以告诉spring容器传递的依赖参数的顺序,下面的配置将会令Spring容器成功找到第一个构造函数并调用创建user实例。
自动装配与注解注入
基于xml的自动装配
除了上述手动注入的情况,Spring还非常智能地为我们提供自动向Bean注入依赖的功能,这个过程一般被称为自动装配(autowiring)。博主认为这是一个非常酷的功能,当注入的bean特别多时,它将极大地节省编写注入程序的时间,因此在开发中,非常常见。Spring的自动装配有三种模式:byTpye(根据类型),byName(根据名称)、constructor(根据构造函数)。
在byTpye模式中,Spring容器会基于反射查看bean定义的类,然后找到与依赖类型相同的bean注入到另外的bean中,这个过程需要借助setter注入来完成,因此必须存在set方法,否则注入失败。
//dao层
public class UserDaoImpl implements UserDao{
//.......
@Override
public void done(){
System.out.println("UserDaoImpl.invoke......");
}
}
//service层
public class UserServiceImpl implements UserService {
//需要注入的依赖
private UserDao userDao;
/**
* set方法
* @param userDao
*/
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void done(){
userDao.done();
}
}
基于xml的配置如下,通过使用<bean>的autowire属性启动名称为userService的自动装配功能
<bean id="userDao" class="com.spring.springIoc.dao.impl.UserDaoImpl" />
<!-- byType 根据类型自动装配userDao-->
<bean id="userService" autowire="byType" class="com.spring.springIoc.service.impl.UserServiceImpl" />
测试代码:
@Test
public void test3() {
ApplicationContext applicationContext=new
ClassPathXmlApplicationContext("spring.xml");
UserService userService= (UserService) applicationContext.getBean("userService");
userService.done();
}
IOC操作Bean管理(作用域)
①默认获取到的对象为单实例对象
②设置单实例还是多实例
a.在spring配置文件bean标签里有属性(scope)用于设置单实例还是多实例
b.scope属性的值有多个 第一个默认值为singleton为单实例 scope=“prototype” 为多实例属性值
singleton和prototype的区别:
singleton在加载配置文件的时就会创建单实例对象
prototype不是在加载配置文件的时候创建而是在调用getbean的时候进行创建
※※※※※※※※※※※※※※※※※※
IOC操作Bean管理(生命周期)
※※※※※※※※※※※※※※※※※※
①生命周期概念:从对象创建到销毁的过程
②bean的生命周期
a.通过构造器创建bean实例(无参构造器)
b.为bean的属性设置对其他bean引用(调用)
c.调用bean的初始化方法(需要进行配置)
c.1.把bean实例传递bean后置处理器的方法:postProcessBeforeInitalization
d.bean可使用了(对象获取到了)
d.1.把bean实例传递bean后置处理器的方法:postProcessAfterInitalization
e.当容器关闭时,调用bean的销毁方法(需要进行配置销毁的方法)
bean的后置处理器:
c.1把bean实例传递bean后置处理器的方法:postProcessBeforeInitalization
d.1把bean实例传递bean后置处理器的方法:postProcessAfterInitalization
未完待续…