(Spring版本 5.x)
什么是IOC
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。
IOC是我们在编程中需要达到的一种目标,而DI是实现IOC的一种技术手段。
比如说ORM(对象关系映射),实现的技术手段有Hibernate、MyBatis等。
依赖查找
比如说JNDI中,我们在tomcat下会配置一个数据源,然后在代码中通过lookup去查找这个服务。
DataSource ds = (DataSource)context.lookup("java:comp/env/dataSource");
依赖注入
什么是依赖?
所谓依赖就是在A类中有一个属性是B类,那么我们可以理解为A依赖了B。或者是在A类的构造方法上,把B类作为形参。
(下面的代码就是OrderDao依赖了IndexDao )
public class OrderDao {
private IndexDao indexDao;
}
public class OrderDao {
public OrderDao(IndexDao indexDao){
}
}
什么是注入?
我们仅仅只需要在代码中提供类与类之间的依赖关系,spring把这些对象存储到一个容器里面(CurrentHashMap),当需要使用到这些对象时,容器会自动把这些对象给我们,给的这个过程称之为注入。
那么DI是如何实现的呢?
Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
为什么要使用Spring IOC
在日常程序开发过程当中,我们推荐面向抽象编程,面向抽象编程会产生类的依赖,那么这些依赖关系需要我们去维护,当然如果你够强大可以自己写一个管理的容器,但是既然spring以及实现了,并且spring如此优秀,我们仅仅需要学习spring框架便可。当我们有了一个管理对象的容器之后,类的产生过程也交给了容器,至于我们自己的app则可以不需要去关系这些对象的产生了。
注入的两种方法
构造方法注入和setter方法注入,接口注入目前spring5已经不支持。
DI exists in two major variants, Constructor-based dependency injection and Setter-based dependency injection.
Constructor-based dependency injection(构造方法注入)
public class IndexDao {
public void indexMethod(){
System.out.println("IndexDao");
}
}
public class OrderDao {
//构造方法注入
public OrderDao(IndexDao indexDao) {
indexDao.indexMethod();
}
}
<bean id="orderDao" class="com.along.dao.OrderDao">
<constructor-arg name="indexDao" ref="indexDao"/>
</bean>
<bean id="indexDao" class="com.along.dao.IndexDao"/>
Setter-based dependency injection(setter方法注入)
public class IndexDao {
public void indexMethod(){
System.out.println("IndexDao");
}
}
public class OrderDao {
private IndexDao indexDao;
//setter方法注入
public void setIndexDao(IndexDao indexDao) {
this.indexDao = indexDao;
}
}
<bean id="orderDao" class="com.along.dao.OrderDao">
<property name="indexDao" ref="indexDao"/>
</bean>
<bean id="indexDao" class="com.along.dao.IndexDao"/>
spring实现IOC的思路和方法
spring实现IOC的思路是提供一些配置信息用来描述类之间的依赖关系,然后由容器去解析这些配置信息,继而维护好对象之间的依赖关系,前提是对象之间的依赖关系必须在类中定义好,比如A.class中有一个B.class的属性,那么我们可以理解为A依赖了B。既然我们在类中已经定义了他们之间的依赖关系那么为什么还需要在配置文件中去描述和定义呢?
spring实现IOC的思路大致可以拆分成3点
- 应用程序中提供类,提供依赖关系(属性或者构造方法)
- 把需要交给容器管理的对象通过配置信息告诉容器(xml、annotation,javaconfig)
- 把各个类之间的依赖关系通过配置信息告诉容器
配置这些信息的方法有三种分别是xml,annotation和javaconfig
维护的过程称为自动注入,自动注入的方法有两种构造方法和setter
自动注入的值可以是对象,数组,map,list和常量比如字符串整形等
spring编程的风格
schema-based-------xml
annotation-based-----annotation
java-based----java Configuration
自动装配
上面说过,IOC的注入有两个地方需要提供依赖关系,一是类的定义中,二是在spring的配置中需要去描述。自动装配则把第二个取消了,即我们仅仅需要在类中提供依赖,继而把对象交给容器管理即可完成注入。
在实际开发中,描述类之间的依赖关系通常是大篇幅的,如果使用自动装配则省去了很多配置,并且如果对象的依赖发生更新我们可以不需要去更新配置。
自动装配的方式
常用的就是byName和byType
1.schema-based(XML方式)实现
byType
首先会去解析XML配置的bean或者使用了注解(@Service、@Component、@Repository等)的bean,然后把这些bean实例化出来放到一个Map当中,实例化后如果发现某个bean下面有一个属性,也就是之前说的存在依赖关系(比如A依赖B),那么就会去容器中找A所依赖的这个属性B所对应的类型,然后循环遍历刚刚那个Map,看看哪个对象是属于B这个类型,如果找到了,就会把B这个对象的实例给A这个对象的属性。
我们来看看下面这种情况:
public interface UserDao {
public void query();
}
public class UserDaoImpl implements UserDao {
@Override
public void query() {
System.out.println("UserDaoImpl");
}
}
public class UserDaoImplTwo implements UserDao {
@Override
public void query() {
System.out.println("UserDaoImplTwo");
}
}
public interface UserService {
public void doService();
}
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void doService() {
userDao.query();
}
}
<?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" default-autowire="byType">
<bean id="userDao" class="com.along.dao.impl.UserDaoImpl"/>
<bean id="userDaoTwo" class="com.along.dao.impl.UserDaoImplTwo"/>
<bean id="userService" class="com.along.service.impl.UserServiceImpl"/>
</beans>
这里会报异常, 大概意思就是预期匹配单个bean,但是找到了两个
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'com.along.dao.UserDao' available: expected single matching
bean but found 2: userDao,userDaoTwo
大概说下上面的代码,UserDaoImpl和UserDaoImplTwo都实现了UserDao接口,UserServiceImpl实现了UserService接口,在UserServiceImpl中有一个属性UserDao,使用setter方法注入UserDao类型的bean,那么现在如果是byType就会有问题,因为UserDaoImpl和UserDaoImplTwo都属于UserDao类型,查找UserServiceImpl所依赖的bean类型就会找到两个UserDaoImpl和UserDaoImplTwo,无法确认到底注入哪个实现类,那这个问题怎么解决呢?我们可以使用byName(当然还有其他方法,使用注解方式@Qualifier("bean的名字") 按名称装配Bean,与@Autowired组合使用,解决按类型匹配找到多个Bean问题)。
byName
我们在xml配置中 <bean id="UserDao" name="UserDaoImpl"/> 如果name属性没有指定,那么这个bean的name就等于这个id属性,或者使用注解的方式去指定bean的name,如果不指定则是类名首字母小写后做为beanName。上面byType引发的问题,现在使用byName。
public class UserServiceImpl implements UserService {
private UserDao userDao;
//注意setter方法注入是根据方法名,然后去掉set以后,首字母小写去匹配的
public void setUserDaoTwo(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void doService() {
userDao.query();
}
}
<?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" default-autowire="byName">
<bean id="userDao" class="com.along.dao.impl.UserDaoImpl"/>
<bean id="userDaoTwo" class="com.along.dao.impl.UserDaoImplTwo"/>
<bean id="userService" class="com.along.service.impl.UserServiceImpl"/>
</beans>
由于上面把setter方法名setUserDao修改为setUserDaoTwo,输出的结果就是 UserDaoImplTwo 。这里根据set方法名,然后去掉set用首字母小写去匹配的,比如setUserDao获取的就是userDao这个对象。
2.annotation-based(注解方式)实现
这里有个问题要说一下,为什么注解需要分层为@Repository、@Service、@Controller 持久性层、服务层和表示层,直接用@Component效果也是一样的啊,看了官方文档才知道。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
那是因为在Spring框架的未来版本中,@Repository、@Service和@Controller也可能携带额外的语义。
下面用注解的方式改造:
public interface UserDao {
public void query();
}
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void query() {
System.out.println("UserDaoImpl");
}
}
@Repository
public class UserDaoImplTwo implements UserDao {
@Override
public void query() {
System.out.println("UserDaoImplTwo");
}
}
public interface UserService {
public void doService();
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void doService() {
userDao.query();
}
}
@Configuration
@ComponentScan("com.along")
public class AppConfig {
}
执行结果:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of
type 'com.along.dao.UserDao' available: expected single matching bean but found 2:
userDaoImpl,userDaoImplTwo
这里说明了一个问题,@AutoWired自动装配默认是byType,但是如果改为@Resource就是byName去自动装配的。
@Service
public class UserServiceImpl implements UserService {
@Resource(name = "userDaoImpl")
private UserDao userDao;
@Override
public void doService() {
userDao.query();
}
}
上面的执行结果是 UserDaoImpl
但@Resource如果不指定name属性的话依然会抛 expected single matching bean but found 2 的异常,那就证明了@Resource先byName如果没找到再byType。
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
@Override
public void doService() {
userDao.query();
}
}
下面的代码还证明了一个问题,如果不指定name属性,就会根据注入对象的属性名去找(这里就是userDaoImplTwo属性)
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDaoImplTwo;
@Override
public void doService() {
userDaoImplTwo.query();
}
}
这里总结一下@AutoWired和@Resource的区别:
- @AutoWired自动装配默认是byType,如果byType没有找到就按byName去找;
- @Resource默认使用byName去自动装配的,如果byName没有找到就会使用byType去找;
- 如果byType和byName都没找到,就会按这个属性的class类型去匹配(如果注入的是接口,就会去找实现类,如果是父类就会去找子类,如果都不是则按这个属性自身的class类型去匹配),如果匹配到多个则抛异常(匹配一个但是找到两个)。
Bean的作用域
常用的singleton或prototype,singleton也就是每次获取都是同一个对象,prototype每次获取都会重新创建一个对象。其他的都依赖于web环境,这里就先不说明了。下面是spring官网对单例和原型的解释。众所周知,在Spring容器中,bean的scope默认是singleton单例的。
需要注意的是,单例bean中依赖了原型bean引发的问题:
https://blog.csdn.net/wu8439512/article/details/90048422
bean生命周期的回调
所谓生命周期的回调就是在bean在初始化和销毁bean时执行的某些操作。
Initialization Callbacks(初始化回调)在构造方法之后执行
实现的方式有三种:
1.实现InitializingBean接口,重写afterPropertiesSet方法
public class IndexDao implements InitializingBean {
public void indexMethod(){
System.out.println("IndexDao Constructor");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("init");
}
}
2.在方法上加上@PostConstruct注解
public class IndexDao {
public IndexDao(){
System.out.println("IndexDao Constructor");
}
@PostConstruct
public void init(){
System.out.println("PostConstruct");
}
}
3.XML配置指定初始化方法
<bean id="indexDao" class="com.along.dao.IndexDao" init-method="initMethod"/>
public class IndexDao {
public IndexDao(){
System.out.println("IndexDao Constructor");
}
public void initMethod(){
System.out.println("initMethod");
}
}
Destruction Callbacks(销毁回调)
实现方式三两种:
1.实现DisposableBean接口,重写afterPropertiesSet方法
public class IndexDao implements DisposableBean {
public IndexDao(){
System.out.println("IndexDao Constructor");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy");
}
}
(调用AnnotationConfigApplicationContext.close();方法可以销毁bean)
2.在方法上加上@PreDestroy注解
public class IndexDao {
public IndexDao(){
System.out.println("IndexDao Constructor");
}
@PreDestroy
public void preDestroy() throws Exception {
System.out.println("PreDestroy");
}
}
3. XML配置指定销毁方法
<bean id="indexDao" class="com.along.dao.IndexDao" destroy-method="destroyMethod"/>
public class IndexDao{
public IndexDao(){
System.out.println("IndexDao Constructor");
}
public void destroyMethod(){
System.out.println("destroyMethod");
}
}
好了,就先写到这,后面理解的新知识再慢慢补充。