跳至主要內容

Spring-IOC(控制反转)

holic-x...大约 12 分钟框架Spring

Spring-IOC(控制反转)

基础概念

IOC概述

​ ApplicationContext是Spring IoC容器实现的代表,它负责实例化,配置和组装Bean。容器通过读取配置元数据获取有关实例化、配置和组装哪些对象的说明 。

​ 配置元数据可以使用XML、Java注解或Java代码来呈现。它允许你处理应用程序的对象与其他对象之间的互相依赖关系

元数据配置

  • 使用XML配置:最基础的配置方式(Bean标签、工厂方法等模式)
  • 基于注解的配置:@Compont(@serivce @controller@repository) @Autowride(Spring2.5支持基于注解的元数据配置,SSM框架开发中应用)
  • 基于Java的配置:@Confiration @Bean @Import(Spring3.0开始,引入Spring JavaConfig框架核心,可以使用Java配置来替代XML额外配置外部bean;Spring4.0开始支持spirngboot1.0,之后完全采用JavaConfig的方法进行开发)

容器的实例化和使用

实例化:对象在Spring容器创建完成的时候就已经创建完成,不是需要用的时候才创建

使用:ApplicationContext是能够创建bean定义以及处理相互依赖关系的高级工厂接口,使用方法T getBean(String name, Class<T> requiredType)获取容器实例

// 创建spring上下文 加载所有的bean
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// 获取bean
UserService service = context.getBean("userList", UserService.class);
// 使用bean的对象
List<String> userList = service.getUsernameList();

Bean的基础配置

Bean的概述

Bean命名

# 命名bean
<bean class="com.entity.User" id="user" name="user2 user3,user4,user5"></bean>
# 为外部的bean起别名
 <alias name="user" alias="user6"></alias>

Bean的实例化:构造器实例化、静态工厂方法实例化、实例工厂实例化

# 方式1:使用构造器实例化(默认方式,无法干预实例化过程)
<bean id="bean" class="com.entity.User"></bean>
public class User {
	// 定义一个无参的构造方法,系统有默认的无参构造器
}


# 方式2:使用静态工厂方法实例化
<bean class="com.service.impl.UserServiceImpl" id="userService" factory‐method="createUserServiceInstance" ></bean>
public static UserServiceImpl createUserServiceInstance(){
	return new UserServiceImpl();
}

# 方式3:
<bean class="com.service.impl.UserServiceImpl" id="userService" factory‐bean="serviceFactory" factory‐method="createUserService" ></bean>
public class createUserService{
  public UserServiceImpl createUserFactory(){
  	return new UserServiceImpl();
  }
}

依赖注入:基于setter方法注入、基于构造函数注入

public class Car {
	// 定义Car类的两个属性
	private String carType ;
	private String carPrice;
	// 提供带参数的构造器
	public Car(String carType, String carPrice) {
		super();
		this.carType = carType;
		this.carPrice = carPrice;
	}
	@Override
	public String toString() {
		return "Car [carType=" + carType + ", carPrice=" + carPrice + "]";
	}
}
# 方式1:基于setter方法注入(根据set方法进行注入,和类中定义的属性名一致)
<bean class="com.entity.Car" id="car">
  <property name="carType" value="保时捷"></property>
  <property name="carPrice" value="150000"></property>
</bean>


# 方式2:基于构造函数注入(调用对应自定义构造函数进行初始化),name属性可以省略但需注意属性的定义顺序和下标(index)配置
<bean class="com.entity.Car" id="car">
  <constructor‐arg name="carType" value="保时捷"></constructor‐arg>
  <constructor‐arg name="carPrice" value="150000"></constructor‐arg>
</bean>

依赖注入的其他配置细节

<!‐‐复杂数据类型‐‐>
<bean class="cn.beans.Person" id="person" p:wife‐ref="wife2">
	<property name="id" value="1"></property>
	<property name="realName" value=""></property>
  <!‐‐设置null值‐‐>
  <property name="name"><null></null></property>
  
<!‐‐当依赖其他bean: 内部bean inner bean‐‐>	
<property name="user">
	<bean class="cn.beans.User" >
		<property name="age" value="18"></property>
		<property name="name" value="迪丽热巴"></property>
	</bean>
</property>
  
<!‐‐当依赖其他bean: 引用外部bean‐‐>
<property name="user" ref="user"></property>
  
<property name="birthday" value="2020/05/20"></property>
<property name="hobbies">
  <list>
    <value>唱歌</value>
    <value>跳舞</value>
    <!‐‐如果List的泛型是比如:List<Wife> <bean>‐‐>
  </list>
</property>
  
<property name="course" >
  <map>
  <entry key="1" value="JAVA"> </entry>
  <entry key="2" value="HTML"> </entry>
  </map>
</property>
</bean>

<!‐‐可以使用p命名空间来简化基于setter属性注入 它不支持集合‐‐>
<bean class="cn.beans.User" id="user1" p:age="18" p:name="haha" ></bean>

<!‐‐可以使用c命名空间来简化基于构造函数属性注入 它不支持集合‐‐>
<bean class="cn.beans.User" id="user2" c:age="20" c:name="xxx">
	<!‐‐ <constructor‐arg name="age" value="18"></constructor‐arg>‐‐> 
</bean>

depend-on属性(控制bean的加载顺序)

<!‐‐使用depends‐on可以设置先加载的Bean 也就是控制bean的加载顺序‐‐>
<bean class="cn.beans.Person" id="person" depends‐on="user"></bean>
<bean class="cn.beans.User" id="user"></bean>

懒加载bean:lazy-init设置懒加载(懒加载默认为false时Spring容器创建时就会实例化bean;如果设置为true则只有在使用时getBean才会实例化)

<!‐‐使用lazy‐init设置懒加载 默认为false: 在spring容器创建的时候加载(实例化)true: 在使用的时候(getBean)才会去加载(实例化)‐‐>
<bean class="cn.beans.Person" id="person" lazy‐init="true">
  <property name="id" value="1"></property>
  <property name="name" value="noob"></property>
</bean>

bean的自动注入

​ 当一个对象中需要引用另外一个对象的时候,在之前的配置中都是通过property标签来进行手动配置的,spring中还提供了一个非常强大的功能就是自动装配,可以按照指定的规则进行配置,配置的方式有以下几种:

  • default/no:不自动装配
  • byName:按照名字进行装配,以属性名作为id去容器中查找组件,进行赋值,如果找不到则装配null
  • byType:按照类型进行装配,以属性的类型作为查找依据去容器中找到这个组件,如果有多个类型相同的bean对象,那么会报异常,如果找不到则装配null
  • constructor:按照构造器进行装配,先按照有参构造器参数的类型进行装配,没有就直接装配null;如果按照类型找到了多个,那么就使用参数名作为id继续匹配,找到就装配,找不到就装配null
  • 通过将autowire-candidate 属性设置为false,避免对bean定义进行自动装配
  • 通过将其<bean/> 元素的primary属性设置为 true,将单个bean定义指定为主要候选项

bean的作用域

  • Singleton(单例)的作用域
  • Prototype(原型)的作用域
<!‐‐作用域scope
	singleton 默认:单例 只会在Ioc容器种创建一次
	prototype 多例(原型bean) 每次获取都会new一次新的bean
‐‐>
<bean class="cn.beans.Person" id="person3" scope="prototype">
  <property name="id" value="1"></property>
  <property name="name" value="noob"></property>
</bean>

自定义bean的特性

生命周期回调

(1)使用接口实现的方式来实现生命周期的回调:

​ 初始化方法: 实现接口: InitializingBean 重写afterPropertiesSet方法初始化会自动调用的方法

​ 销毁的方法: 实现接口: DisposableBean 重写destroy 方法销毁的时候自动调用方法

​ 什么时候销毁:在spring容器关闭的时候 close(),或者使用ConfigurableApplicationContext.registerShutdownHook方法优雅的关闭

(2)使用指定具体方法的方式实现生命周期的回调:

​ 在对应的bean里面创建对应的两个方法:init‐method="init" destroy‐method="destroy"

ApplicationContextAware和BeanNameAware

bean定义的继承

​ bean的继承:一个bean继承另一个bean,可使用parent属性指定父类bean,如果想让父类bean不被实例化可设置abstract="true"

<!-- 父类bean定义 -->
<bean class="cn.beans.Person" id="ParentPerson" abstract="true">
  <property name="id" value="1"></property>
  <property name="name" value="noob"></property>
</bean>

<!-- 子类bean定义 -->
<bean class="cn.beans.Person" id="subPerson" parent="ParentPerson" >
	<property name="realName" value="hah"></property>
</bean>

Spring中创建第三方bean对象

​ 在Spring中,很多对象都是单实例的,在日常的开发中,常需要使用某些外部的单实例对象,例如数据库连接池(通过数据库连接池的引入说明如何在spring中创建第三方bean实例)

方式1:通过bean定义引入第三方bean

(1)引入数据库连接池的相关依赖(maven配置)

<!‐‐ https://mvnrepository.com/artifact/com.alibaba/druid ‐‐>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.21</version>
</dependency>
<!‐‐ https://mvnrepository.com/artifact/mysql/mysql‐connector‐java ‐‐>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql‐connector‐java</artifactId>
  <version>5.1.47</version>
</dependency>

(2)编写配置文件(ioc.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 htt
p://www.springframework.org/schema/beans/spring‐beans.xsd">

  <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="username" value="root"></property>
    <property name="password" value="123456"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/demo"></property>
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
  </bean>
</beans>

(3)测试

public class MyTest {
  public static void main(String[] args) throws SQLException {
    ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
    DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
    System.out.println(dataSource);
    System.out.println(dataSource.getConnection());
  }
}

方式2:通过引入外部配置文件的方式引入

(1)在resource中添加dbconfig.properties

username=root
password=123456
url=jdbc:mysql://localhost:3306/demo
driverClassName=com.mysql.jdbc.Driver

(2)编写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.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring‐context.xsd">
<!‐‐加载外部配置文件,在加载外部依赖文件的时候需要context命名空间‐‐>
<context:property‐placeholder location="classpath:dbconfig.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="username" value="${username}"></property>
  <property name="password" value="${password}"></property>
  <property name="url" value="${url}"></property>
  <property name="driverClassName" value="${driverClassName}"></property>
</bean>

方式3:SpEL的使用

<bean id="user" class="cn.entity.User">
  <!‐‐支持任何运算符‐‐>
  <property name="id" value="#{12*2}"></property>
  <!‐‐可以引用其他bean的某个属性值‐‐>
  <property name="name" value="#{address.province}"></property>
  <!‐‐引用其他bean‐‐>
  <property name="role" value="#{address}"></property>
  <!‐‐调用静态方法‐‐>
  <property name="hobbies" value="#{T(java.util.UUID).randomUUID().toString().substring(0,4)}"></property>
  <!‐‐调用非静态方法‐‐>
  <property name="gender" value="#{address.getCity()}"></property>
</bean>

Bean的注解应用

组件注解、@AutoWired、@Qualifier

​ 上述方式是通过xml文件进行bean或者某些属性的赋值,在企业开发中使用通过注解方式注册bean,在bean上添加注解,可以快速的将bean注册到ioc容器

步骤说明

(1)配置applicationContext.xml:配置扫描包内容

<?xml version="1.0" encoding="UTF‐8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
  <!-- 配置注解扫描包路径 -->
	<context:component‐scan base‐package="cn.beans"></context:component‐scan>
</beans>

<!-- 选择性的配置注册(定义扫描包时要包含的类和不要包含的类) -->
type:表示指定过滤的规则
annotation:按照注解进行排除,标注了指定注解的组件不要,expression表示要过滤的注解
assignable:指定排除某个具体的类,按照类排除,expression表示不注册的具体类名
aspectj:aop中要使用的aspectj表达式
custom:定义一个typeFilter,自己写代码决定哪些类被过滤掉
regex:使用正则表达式过滤

<context:component‐scan base‐package="cn.tulingxueyuan" use‐default‐filters="false">
  <!-- 按照注解进行排除 -->
  <!‐‐ <context:exclude‐filter type="annotation" expression="org.springframework.stereotype.Controller"/> ‐‐>
	<!‐‐指定只扫描哪些组件,默认情况下是全部扫描的,要配置的话需要在component‐scan标签中添加 use‐default‐filters="false"‐‐>
	<context:include‐filter type="assignable" expression="cn.service.PersonService"/>
</context:component‐scan>
  

(2)在对应Bean定义中通过注解方式注入实例

@Controller:控制器,用于controller层
@Service:业务逻辑,用于业务逻辑层
@Repository:仓库管理,用于数据访问层
@Component:通用,可用于一些不属于以上基层的组件

​ 虽然可以认为指定不同类型的注解,目的还是提升代码可读性,Spring底层并不会对这些层次进行验证,最偷懒的方式可以通过@Component注解注入对象

@Controller
public class PersonController {}

@Service
public class PersonService {}

@Repository("personDao")
@Scope(value="prototype")
public class PersonDao {}

(3)使用**@AutoWired**进行自动注入

​ 使用注解的方式实现自动注入需要使用@AutoWired注解,当使用AutoWired注解的时候,自动装配的时候是根据类型实现的

  • 如果只找到一个,则直接进行赋值
  • 如果没有找到,则直接抛出异常
  • 如果找到多个,那么会按照变量名作为id继续匹配,匹配上直接进行装配,如果匹配不上则直接报异常
@Controller
public class PersonController {
  @Autowired
  private PersonService personService;
  public PersonController() {
  	System.out.println("创建对象");
  }
  public void getPerson(){
  	personService.getPerson();
  }
}

@Service
public class PersonService {
  @Autowired
  private PersonDao personDao;
  
  public void getPerson(){
  	personDao.getPerson();
  }
}

@Repository("personDao")
@Scope(value="prototype")
public class PersonDao {
  public void getPerson(){
  	System.out.println("PersonDao:getPerson");
  }
}

​ @AutoWired是根据类型装配Spring Bean,也可借助@Qualifier注解来指定id的名称(@Qualifier(value="")),用于消除同类型依赖注入冲突

​ 为了处理多个同类型bean装载冲突的情况,还可借助@Primary注解指定优先选择的内容

		 @Component
     public class FooFormatter implements Formatter {
         public String format() {
             return "foo";
         }
     }
 
     @Component
     public class BarFormatter implements Formatter {
         public String format() {
             return "bar";
         }
     }

 		 @Component
		 @Primary
     public class StrFormatter implements Formatter {
         public String format() {
             return "str";
         }
     }


		@Component
    public class FooService {
        @Autowired
        // @Qualifier("fooFormatter")
        private Formatter formatter;
        
        //todo 
    }
@Autowired按照类型装配,则会找到FooFormatterBarFormatter,此时Spring不知道加载哪个就会出现异常,可通过@Qualifier("")进一步指定或借助@Primary指定优先的选择

@Autowired不仅可以定义在变量上,还可定义在方法上。当方法上有@AutoWired注解时:此方法在bean创建的时候会自动调用、这个方法的每一个参数都会自动注入值

@Autowired
public void test(PersonDao personDao){
	System.out.println("此方法被调用:"+personDao);
}

@Qualifier注解也可以作用在属性上,用来被当作id去匹配容器中的对象,如果没有此注解,那么直接按照类型进行匹配

@Autowired
public void test2(@Qualifier("personServiceExt") PersonService personService){
	System.out.println("此方法被调用:"+personService);
}

自动装配注解:@AutoWired、@Resource

​ 在使用自动装配的时候,除了@AutoWired注解之外,还可以使用@Resource注解

@AutoWired是spring中提供的注解,@Resource是jdk中定义的注解,依靠的是java的标准

@AutoWired默认是按照类型进行装配,默认情况下要求依赖的对象必须存在,@Resource默认是按照名字进行匹配的,同时可以指定name属性

@AutoWired只适合spring框架,而@Resource扩展性更好

@AutoWired如果按照名称匹配需要结合@Qualifier一起使用

Java类使用

​ JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。

基于java的容器配置

@Bean、@Configuration、@Import

​ 以数据库配置引入方式测试(对比传统xml配置bean注入)

@Configuration // 就相当于创建了一个xml 文件 <beans></beans>
@ComponentScan("com.noob.demo") //<context:component‐scan base‐package="com.noob.demo" >
@PropertySource("classpath:db.properties")
public class MainConfiguration {

    @Value("${mysql.username}")
    private String name;
    @Value("${mysql.password}")
    private String password;
    @Value("${mysql.url}")
    private String url;
    @Value("${mysql.driverClassName}")
    private String driverName;

    // <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"></bean>
    @Bean
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setName(name);
        dataSource.setPassword(password);
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driverName);
        return dataSource;
    }

}

@Bean:方法级别的注解,和<bean>标签定义类似,支持init-method、destroy-method、autowiring、name等

  • 接收生命周期回调: @Bean(initMethod = "initByConfig", destroyMethod = "destroyByConfig")
  • 指定Bean的作用域:@Scope("prototype")
  • 自定义Bean的名字:@Bean(name = "myUser")
@Component
@Data
public class User {

    private String name;

    public User() {
        System.out.println("User已加载");
    }

    private void initByConfig() {
        System.out.println("User初始化");
    }

    private void destroyByConfig() {
        System.out.println("User销毁");
    }
}

@Configuration
@ComponentScan("com.noob.demo") 
public class MainConfiguration {
    // init‐method="initByConfig" destroy‐method="destroyByConfig"
    @Bean(initMethod = "initByConfig", destroyMethod = "destroyByConfig")
    public User userconf() {
        return new User();
    }
}

@Configuration:注入内部bean依赖

 @Configuration
public class AppConfig {
  @Bean
  public BeanOne beanOne() {
  	return new BeanOne(beanTwo());
	}
  
  @Bean
  public BeanTwo beanTwo() {
		return new BeanTwo();
  }
}

@Import:构成基于Java的配置

@Import类似Spring配置文件中的<import>标签,该注解允许从另一个配置类中加载@Bean定义

public class ConfigA {
    @Bean
    public User get() {
        return new User();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {
    @Bean
    public Person get() {
        return new Person();
    }
}

如何将一个类注入到IOC容器中

(1)xml:<bean>标签

(2)注解:@Component (@Controller,@Service,@Repository)

(3)@Bean注解

(4)@Import导入

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3