Spring-IOC(控制反转)
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按照类型装配,则会找到FooFormatter、BarFormatter,此时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导入