【Spring】IoC控制反转详解 ⟳
IOC(概念和原理)⟳
- 什么是 IOC
- 控制反转(Inversion of Control,缩写为IoC),把对象创建和对象之间的调用过程,交给 Spring 进行管理
- 使用 IOC 目的:为了耦合度降低
- 做入门案例就是 IOC 实现
- IOC 底层原理
- xml 解析
- 工厂模式
- 反射
- 画图讲解 IOC 底层原理
IOC(BeanFactory 接口)⟳
- IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂
- Spring 提供 IOC 容器实现两种方式:(两个接口)
- BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用
加载配置文件时不会创建对象,在获取对象(使用)才去创建对象
- ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用
加载配置文件时候就会把在配置文件对象进行创建
- ApplicationContext 接口有实现类
IOC 操作 Bean 管理⟳
概念⟳
- 什么是 Bean 管理
- Bean 管理指的是两个操作
- Spring 创建对象
- Spirng 注入属性
- Bean 管理操作有两种方式
- 基于 xml 配置文件方式实现
- 基于注解方式实现
基于 xml 方式⟳
-
基于 xml 方式创建对象
<!--配置 User 对象创建--> <bean id="user" class="com.spring5.User"></bean>
- 在 spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
- 在 bean 标签有很多属性,介绍常用的属性
- id 属性:唯一标识
- class 属性:类全路径(包类路径)
- 创建对象时候,默认也是执行无参数构造方法完成对象创建
-
基于 xml 方式注入属性
- DI:依赖注入,就是注入属性
-
第一种注入方式:使用 set 方法进行注入
-
创建类,定义属性和对应的 set 方法
/** * 演示使用 set 方法进行注入属性 */ public class Book { //创建属性 private String bname; private String bauthor; //创建属性对应的 set 方法 public void setBname(String bname) { this.bname = bname; } public void setBauthor(String bauthor) { this.bauthor = bauthor; } }
-
在 spring 配置文件配置对象创建,配置属性注入
<!--2 set 方法注入属性--> <bean id="book" class="com.nemo.spring5.Book"> <!--使用 property 完成属性注入 name:类里面属性名称 value:向属性注入的值 --> <property name="bname" value="易筋经"></property> <property name="bauthor" value="达摩老祖"></property> </bean>
-
-
第二种注入方式:使用有参数构造进行注入
-
创建类,定义属性,创建属性对应有参数构造方法
/** + 使用有参数构造注入 */ public class Orders { //属性 private String oname; private String address; //有参数构造 public Orders(String oname,String address) { this.oname = oname; this.address = address; } }
-
在 spring 配置文件中进行配置
<!--3 有参数构造注入属性--> <bean id="orders" class="com.nemo.spring5.Orders"> <constructor-arg name="oname" value="电脑"></constructor-arg> <constructor-arg name="address" value="China"></constructor-arg> </bean>
-
-
p 名称空间注入(了解)
使用 p 名称空间注入,可以简化基于 xml 配置方式-
第一步 添加 p 名称空间在配置文件中
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/schema.xsd"> </beans>
-
第二步 进行属性注入,在 bean 标签里面进行操作
<!--2 set 方法注入属性--> <bean id="book" class="com.nemo.spring5.Book" p:bname="九阳神功" p:bauthor="无名氏"></bean>
-
xml 注入其他类型属性⟳
-
字面量
-
null 值
<!--null 值--> <property name="address"> <null/> </property>
-
属性值包含特殊符号
<!--属性值包含特殊符号 1 把<>进行转义 < > 2 把带特殊符号内容写到 CDATA --> <property name="address"> <value><![CDATA[<<南京>>]]></value> </property>
-
-
注入属性-外部 bean
- 创建两个类 service 类和 dao 类
- 在 service 调用 dao 里面的方法
- 在 spring 配置文件中进行配置
public class UserService { //创建 UserDao 类型属性,生成 set 方法 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void add() { System.out.println("service add..............."); userDao.update(); } }
<!--1 service 和 dao 对象创建--> <bean id="userService" class="com.nemo.spring5.service.UserService"> <!--注入 userDao 对象 name 属性:类里面属性名称 ref 属性:创建 userDao 对象 bean 标签 id 值 --> <property name="userDao" ref="userDaoImpl"></property> </bean> <bean id="userDaoImpl" class="com.nemo.spring5.dao.UserDaoImpl"></bean>
-
注入属性-内部 bean
-
一对多关系:部门和员工
一个部门有多个员工,一个员工属于一个部门
部门是一,员工是多 -
在实体类之间表示一对多关系,员工表示所属部门,使用对象类型属性进行表示
//部门类 public class Dept { private String dname; public void setDname(String dname) { this.dname = dname; } } //员工类 public class Emp { private String ename; private String gender; //员工属于某一个部门,使用对象形式表示 private Dept dept; public void setDept(Dept dept) { this.dept = dept; } public void setEname(String ename) { this.ename = ename; } public void setGender(String gender) { this.gender = gender; } }
-
在 spring 配置文件中进行配置
<!--内部 bean--> <bean id="emp" class="com.nemo.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--设置对象类型属性--> <property name="dept"> <bean id="dept" class="com.nemo.spring5.bean.Dept"> <property name="dname" value="安保部"></property> </bean> </property> </bean>
-
-
注入属性-级联赋值
-
第一种写法
<!--级联赋值--> <bean id="emp" class="com.nemo.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--级联赋值--> <property name="dept" ref="dept"></property> </bean> <bean id="dept" class="com.nemo.spring5.bean.Dept"> <property name="dname" value="财务部"></property> </bean>
-
第二种写法:需要用到 get 方法
<!--级联赋值--> <bean id="emp" class="com.nemo.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--级联赋值--> <property name="dept" ref="dept"></property> <property name="dept.dname" value="技术部"></property> </bean> <bean id="dept" class="com.nemo.spring5.bean.Dept"> <property name="dname" value="财务部"></property> </bean>
-
xml 注入集合属性⟳
-
注入数组类型属性
-
注入 List 集合类型属性
-
注入 Map 集合类型属性
-
创建类,定义数组、list、map、set 类型属性,生成对应 set 方法
public class Stu { //1 数组类型属性 private String[] courses; //2 list 集合类型属性 private List<String> list; //3 map 集合类型属性 private Map<String,String> maps; //4 set 集合类型属性 private Set<String> sets; public void setSets(Set<String> sets) { this.sets = sets; } public void setCourses(String[] courses) { this.courses = courses; } public void setList(List<String> list) { this.list = list; } public void setMaps(Map<String, String> maps) { this.maps = maps; } }
-
在 spring 配置文件进行配置
<!--1 集合类型属性注入--> <bean id="stu" class="com.nemo.spring5.collectiontype.Stu"> <!--数组类型属性注入--> <property name="courses"> <array> <value>java 课程</value> <value>数据库课程</value> </array> </property> <!--list 类型属性注入--> <property name="list"> <list> <value>张三</value> <value>小三</value> </list> </property> <!--map 类型属性注入--> <property name="maps"> <map> <entry key="JAVA" value="java"></entry> <entry key="PHP" value="php"></entry> </map> </property> <!--set 类型属性注入--> <property name="sets"> <set> <value>MySQL</value> <value>Redis</value> </set> </property> </bean>
-
-
在集合里面设置对象类型值
<!--创建多个 course 对象--> <bean id="course1" class="com.nemo.spring5.collectiontype.Course"> <property name="cname" value="Spring5 框架"></property> </bean> <bean id="course2" class="com.nemo.spring5.collectiontype.Course"> <property name="cname" value="MyBatis 框架"></property> </bean> <!--注入 list 集合类型,值是对象--> <property name="courseList"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property>
5、把集合注入部分提取出来
1. 在 spring 配置文件中引入名称空间 util
```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:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
</beans>
```
2. 使用 util 标签完成 list 集合注入提取
```xml
<!--1 提取 list 集合类型属性注入-->
<util:list id="bookList">
<value>易筋经</value>
<value>九阴真经</value>
<value>九阳神功</value>
</util:list>
<!--2 提取 list 集合类型属性注入使用-->
<bean id="book" class="com.nemo.spring5.collectiontype.Book">
<property name="list" ref="bookList"></property>
</bean>
```
FactoryBean⟳
-
Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
-
普通 bean:在配置文件中定义 bean 类型就是返回类型
-
工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
- 第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean
- 第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型
public class MyBean implements FactoryBean<Course> { //定义返回 bean @Override public Course getObject() throws Exception { Course course = new Course(); course.setCname("abc"); return course; } @Override public Class<?> getObjectType() { return null; } @Override public boolean isSingleton() { return false; } }
<bean id="myBean" class="com.nemo.spring5.factorybean.MyBean"></bean>
@Test public void test3() { ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml"); Course course = context.getBean("myBean", Course.class); System.out.println(course); }
bean 作用域(@Scope)⟳
- 在 Spring 里面,设置创建 bean 实例是单实例还是多实例
- 在 Spring 里面,默认情况下,bean 是单实例对象
- 如何设置单实例还是多实例
- 在 spring 配置文件 bean 标签里面有属性(scope)用于设置单实例还是多实例
- scope 属性值
- 第一个值 默认值,singleton,表示是单实例对象
- 第二个值 prototype,表示是多实例对象
- singleton 和 prototype 区别
- 第一 singleton 单实例,prototype 多实例
- 第二 设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象
设置 scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建 对象,在调用 getBean 方法时候创建多实例对象
什么是单例多例:⟳
所谓单例就是所有的请求都用一个对象来处理,我们称之为Full模式,比如我们常用的service和dao层的对象通常都是单例的。而多例则指每个请求用一个新的对象来处理,我们称之为Lite模式,比如action;
如何产生单例多例⟳
单例在spring中是默认的,如果要产生多例,则在配置文件的bean中添加scope="prototype",或者在springboot中的MyConfig类的@Configuration()注解中添加proxyBeanMethods = false。
为什么用单例多例⟳
之所以用单例,是因为没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存;
之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态,此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;
用单例和多例的标准只有一个: 当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),或类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断,即多例;当类组件之间有依赖关系,方法会被调用得到之前单实例组件时,用Full模式,即单例。
@Scope注解的使用场景⟳
可以使用@Scope
来设置bean的单例或多例。
- 几乎90%以上的业务使用 singleton单例就可以,所以 Spring 默认的类型也是singleton,singleton虽然保证了全局是一个实例,对性能有所提高,但是如果实例中有非静态变量时,会导致线程安全问题(多个线程共同更改同一资源),共享资源的竞争。
- 当设置为prototype多例时:每次连接请求,都会生成一个bean实例,也会导致一个问题,当请求数越多,性能会降低,因为创建的实例,导致GC频繁,GC时长增加。
如果需要更改bean的属性时,可以尝试采用多例来避免单例造成的线程安全问题。
bean 生命周期⟳
-
生命周期
- 从对象创建到对象销毁的过程
-
bean 生命周期
- 通过构造器创建 bean 实例(无参数构造):此时会调用类的构造方法,产生一个新的对象
- 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
- 调用 bean 的初始化的方法(需要进行配置初始化的方法):此时对象已经创建好,但还没有被正式使用,可能这里面需要做一些额外的操作(如预初始化数据库的连接池)
理解:看到这里可能会有读者不理解,诶,属性不是已经赋值完了吗?不是已经初始化完了吗?怎么还要调用初始化方法?
这里我就来说说我的例子,不知道大家使用过一些“神奇的”依赖包没有,一依赖,什么都不用做,什么都不用配置,自动生效,而这就是初始化的方法的用处所在了!!- bean 可以使用了(对象获取到了):此时对象已经完全初始化好,程序正常运行,对象被使用
- 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法):此时对象准备被销毁,已不再使用,需要预先的把自身占用的资源等处理好(如关闭、释放数据库连接),最后对象已经完全没有被引用了,被垃圾回收器回收
-
演示 bean 生命周期
public class Orders { //无参数构造 public Orders() { System.out.println("第一步 执行无参数构造创建 bean 实例"); } private String oname; public void setOname(String oname) { this.oname = oname; System.out.println("第二步 调用 set 方法设置属性值"); } //创建执行的初始化的方法 public void initMethod() { System.out.println("第三步 执行初始化的方法"); } //创建执行的销毁的方法 public void destroyMethod() { System.out.println("第五步 执行销毁的方法"); } }
<bean id="orders" class="com.nemo.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod"> <property name="oname" value="手机"></property> </bean>
@Test public void testBean3() { // ApplicationContext context = // new ClassPathXmlApplicationContext("bean4.xml"); ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml"); Orders orders = context.getBean("orders", Orders.class); System.out.println("第四步 获取创建 bean 实例对象"); System.out.println(orders); //手动让 bean 实例销毁 context.close(); }
-
bean 的后置处理器,bean 生命周期有七步
- 通过构造器创建 bean 实例(无参数构造)
- 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
- 把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
- 调用 bean 的初始化的方法(需要进行配置初始化的方法)
- 把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
- bean 可以使用了(对象获取到了)
- 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
-
演示添加后置处理器效果
-
创建类,实现接口 BeanPostProcessor,创建后置处理器
public class MyBeanPost implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("在初始化之前执行的方法"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("在初始化之后执行的方法"); return bean; } }
<!--配置后置处理器--> <bean id="myBeanPost" class="com.nemo.spring5.bean.MyBeanPost"> </bean>
-
xml 自动装配⟳
- 什么是自动装配
- 根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入
- 演示自动装配过程
-
根据属性名称自动注入
<!--实现自动装配 bean 标签属性 autowire,配置自动装配 autowire 属性常用两个值: byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样 byType 根据属性类型注入 --> <bean id="emp" class="com.nemo.spring5.autowire.Emp" autowire="byName"> <!--<property name="dept" ref="dept"></property>--> </bean> <bean id="dept" class="com.nemo.spring5.autowire.Dept"></bean>
-
根据属性类型自动注入
<!--实现自动装配 bean 标签属性 autowire,配置自动装配 autowire 属性常用两个值: byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样 byType 根据属性类型注入 --> <bean id="emp" class="com.nemo.spring5.autowire.Emp" autowire="byType"> <!--<property name="dept" ref="dept"></property>--> </bean> <bean id="dept" class="com.nemo.spring5.autowire.Dept"></bean>
-
外部属性文件⟳
连接池:
连接池的作用就是管理连接,因为如果单纯的用JDBC去连接的话,频繁的开启 和关闭连接对于数据库也是一种负担。使用连接池技术后,可以不必频繁开启关闭连接 需要的时候就去用就好了,这样是资源重用,而且有更快的系统反应速度
市面上实现作为连接池的方案有很多 c3p0 阿里的 druid(德鲁伊) 等 都是很不错的 实现方式
配置数据库url 用户 密码 驱动等信息 管理连接
-
直接配置数据库信息
- 配置德鲁伊连接池
- 引入德鲁伊连接池依赖 jar 包
<!--直接配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/userDb"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
-
引入外部属性文件配置数据库连接池
- 创建外部属性文件,properties 格式文件,写数据库信息
- 把外部 properties 属性文件引入到 spring 配置文件中
引入 context 名称空间
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" 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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
在 spring 配置文件使用标签引入外部属性文件
<!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></property> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.userName}"></property> <property name="password" value="${prop.password}"></property> </bean>
基于注解方式⟳
-
什么是注解
- 注解是代码特殊标记,格式:
@注解名称(属性名称=属性值, 属性名称=属性值..)
- 使用注解,注解作用在类上面,方法上面,属性上面
- 使用注解目的:简化 xml 配置
- 注解是代码特殊标记,格式:
-
Spring 针对 Bean 管理中创建对象提供注解
1、@Service
用于标注业务层组件
2、@Controller
用于标注控制层组件(如struts中的action)
3、@Repository
用于标注数据访问组件,即DAO组件.
4、@Component
泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。@Component
@Bean
和@Component
:
@Bean
是创建一个对象并且设置值(一般是外部的对象) ,一般修饰一个方法
,用来调用构造方法生成对象并设置属性值;
@Component
是创建一个对象(一般是自己写的对象) ,一般修饰一个类
。原因:因为外部的对象我们手头并没有源码,所以只能用@Bean的方式调用构造方法创建,并修改数据。
而我们自己写的代码,可以任意修改。@Service
@Controller
@Repository
上面四个注解功能是一样的,都可以用来创建 bean 实例,加入到IoC容器中。
注意:四个创建对象的注解功能其实是一样的,可以混着用。他们实现一样的功能,只是我们习惯区分标识,让我们开发人员更加清晰,当前组件所扮演的角色,但是实际中把他们混用也是可以的。 -
基于注解方式实现对象创建
-
第一步 引入依赖
-
第二步 开启组件扫描
<!--开启组件扫描 1 如果扫描多个包,多个包使用逗号隔开 2 扫描包上层目录 --> <context:component-scan base-package="com.nemo"></context:component-scan>
-
第三步 创建类,在类上面添加创建对象注解
//在注解里面 value 属性值可以省略不写, //默认值是类名称,首字母小写 //UserService -- userService @Component(value = "userService") //<bean id="userService" class=".."/> public class UserService { public void add() { System.out.println("service add......."); } }
-
-
开启组件扫描细节配置
<!--示例 1 use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter context:include-filter ,设置扫描哪些内容 --> <context:component-scan base-package="com.nemo" use-defaultfilters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!--示例 2 下面配置扫描包所有内容 context:exclude-filter: 设置哪些内容不进行扫描 --> <context:component-scan base-package="com.nemo"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
-
基于注解方式实现属性注入
-
@Autowired
:根据属性类型进行自动装配- 第一步 把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解
- 第二步 在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解
@Service public class UserService { //定义 dao 类型属性 //不需要添加 set 方法 //添加注入属性注解 @Autowired private UserDao userDao; public void add() { System.out.println("service add......."); userDao.add(); } }
-
@Qualifier
:根据名称进行注入
这个@Qualifier 注解的使用,和上面@Autowired 一起使用//定义 dao 类型属性 //不需要添加 set 方法 //添加注入属性注解 @Autowired //根据类型进行注入 @Qualifier(value = "userDaoImpl1") //根据名称进行注入 private UserDao userDao;
-
@Resource
:可以根据类型注入,可以根据名称注入
注意:如果接口的实现类只有一个,那么可以根据类型来出入属性;
但是如果实现类有多个,那么就必须根据名字来注入属性。```java //@Resource //根据类型进行注入 @Resource(name = "userDaoImpl1") //根据名称进行注入 private UserDao userDao; ```
@Value
:注入普通类型属性
注意:开发中会用到@value去动态读取服务器端口号,或者是配置环境是生产环境还是上线环境等等。。
```java @Value(value = "abc") private String name; ```
-
接口或抽象类根据实现类进行注入List或Map。接口列表会直接注入所有的Component实现类。
public interface Human {} /** * order:把实现类排序输出 只适合List */ @Order(1) @Component public class Man implements Human {} @Order(2) @Component public class Woman implements Human {}
//Man和Woman分别实现了Human类,会自动注入到列表中 //定义 dao 类型属性 //不需要添加 set 方法 //添加注入属性注解 @Autowired //根据类型进行注入 private List<Human> humans; // 包含有man和woman对象 private Map<String, Human> humans2; // <key, value> = <组件名, 组件>
-
-
完全注解开发
-
创建配置类,替代 xml 配置文件
@Configuration //作为配置类,替代 xml 配置文件 @ComponentScan(basePackages = {"com.nemo"}) public class SpringConfig { }
-
编写测试类
@Test public void testService2() { //加载配置类 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = context.getBean("userService", UserService.class); System.out.println(userService); userService.add(); }
-
基于静态方法⟳
理论知识⟳
工具类常设置为静态static方法,而非一个Bean(非Component),因而不能使用
@Autowired
注入,但可以通过applicationContext
(Spring自带)SpringContextHolder(第三方框架)获得所需对象。
applicationContext.getBean(beanClass);
可以使用SpringContextHolder
获取bean
@Data
public class Result<T> implements Serializable {
private static LogTask logTask = SpringContextHolder.getBean(LogTask.class);
}
Spring静态注入的四种方式:
(说明:MongoFileOperationUtil是自己封装的一个Mongodb文件读写工具类,里面需要依赖AdvancedDatastore对象实例,dsForRW用来获取Mongodb数据源)
在springframework里,我们不能@Autowired一个静态变量,使之成为一个spring bean,例如下面这种方式:
@Autowired
private static AdvancedDatastore dsForRW;
可以试一下,dsForRW在这种状态下不能够被依赖注入,会抛出运行时异常java.lang.NullPointerException,为什么呢?静态变量/类变量不是对象的属性,而是一个类的属性,spring则是基于对象层面上的依赖注入,它并不能管理到类的属性,它只能管理到自己的代理对象。
但是自己比较喜欢封装工具类,并通过@Component注解成功能组件,但是功能组件中的方法一般都是静态方法,静态方法只能调用静态成员变量,于是就有了下面的问题。封有的时候封装功能组件会需要底层的service注入,怎么办呢?
去网上搜了下解决办法,简单总结一下几种实现方式;
1.xml方式实现;⟳
自己注入自己
<bean id="mongoFileOperationUtil" class="com.*.*.MongoFileOperationUtil" init-method="init">
<property name="dsForRW" ref="dsForRW"/>
</bean>
public class MongoFileOperationUtil {
private static AdvancedDatastore dsForRW;
private static MongoFileOperationUtil mongoFileOperationUtil;
public void init() {
mongoFileOperationUtil = this;
mongoFileOperationUtil.dsForRW = this.dsForRW;
}
}
这种方式适合基于XML配置的WEB项目;
2.@PostConstruct方式实现;⟳
自注入
import org.mongodb.morphia.AdvancedDatastore;
import org.springframework.beans.factory.annotation.Autowired;
@Component
public class MongoFileOperationUtil {
@Autowired
private static AdvancedDatastore dsForRW;
private static MongoFileOperationUtil mongoFileOperationUtil;
@PostConstruct
public void init() {
mongoFileOperationUtil = this;
mongoFileOperationUtil.dsForRW = this.dsForRW;
}
}
@PostConstruct
注解的方法在加载类的构造函数之后执行,也就是在加载了构造函数之后,执行init方法;(@PreDestroy
注解定义容器销毁之前的所做的操作)
这种方式和在xml中配置 init-method和 destory-method方法差不多,定义spring 容器在初始化bean 和容器销毁之前的所做的操作;
3.set方法上添加@Autowired
注解,类定义上添加@Component
注解;(通用)⟳
import org.mongodb.morphia.AdvancedDatastore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MongoFileOperationUtil {
private static AdvancedDatastore dsForRW;
@Autowired
public void setDatastore(AdvancedDatastore dsForRW) {
MongoFileOperationUtil.dsForRW = dsForRW;
}
}
首先Spring要能扫描到AdvancedDatastore的bean,然后通过setter方法注入到静态字段;
然后注意:成员变量上不需要再添加@Autowired
注解;
@Value
注解标注静态变量
如果 @Value
注解标注在 static 或 final 变量,并不能将属性值注入,最终得到的变量值是 null。
Spring 注入内容到静态变量中
@Value + set 方法注入静态变量
如果想要为 static 变量注入值,可以间接的操作方式,比如可以通过注解标注在 set 方法(非静态)上,使用 set 方法注入的方式将属性值赋值到静态变量中。
@Component
public class Customer {
private static String name;
@Value("${customer.name}")
public void setName(String name) {
Customer.name = name;
}
}
4. BeanFactoryAware,ApplicationContextAware⟳
spring提供了两个接口:实现了BeanFactoryAware
和ApplicationContextAware
,这两个接口都继承自Aware
接口如下是这两个接口的声明:
public interface BeanFactoryAware extends Aware {
void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}
public interface ApplicationContextAware extends Aware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
在Spring官方文档中描述,在初始化Spring bean时,如果检测到某个bean实现了这两个接口中的一个,那么就会自动调用该bean所实现的接口方法。这里可以看到,这两个方法都是将的的IoC容器管理的bean工厂对象传递给当前bean,也就是说如果我们在当前bean中将工厂对象保存到某个静态属性中,那么我们就能够通过工厂对象获取到我们需要的bean。
如下使用了ApplicationContextAware
实现的一个SpringBeanUtil:
@Component
public class SpringBeanUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringBeanUtil.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
return (T) applicationContext.getBean(clazz);
}
public static Object getBean(String name) throws BeansException {
return applicationContext.getBean(name);
}
}
这里还需要在配置文件中指定创建当前类的一个实例:(或者加@Component
)
<bean id="springBeanFactory" class="com.business.until.SpringBeanUtil"/>
可以看到,我们在SpringBeanUtil中声明了一个的ApplicationContext中的类型的静态属性,并且在setApplicationContext()方法中将获取到的ApplicationContext中的赋值给了该静态属性,这样我们就可以在另外两个声明的静态方法中通过ApplicationContext中获取的IoC容器所管理的bean了。如下是一个测试示例:
public class ClassRoom {
public void describeStudent() {
Student student = SpringBeanUtil.getBean(Student.class);
System.out.println(student);
}
public static void describeClassRoomCapacity() {
Student student = SpringBeanUtil.getBean(Student.class);
System.out.println("Is it not empty? " + (null != student));
}
}
public class Student {
@Override
public String toString() {
return "I am a student.";
}
}
<bean id="springBeanFactory" class="com.until.SpringBeanUtil"/>
<bean id="student" class="com.domain.Student"/>
如下是驱动类:
public class BeanApp {
public static void main(String[] args) {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("com/resources/application.xml");
ClassRoom.describeClassRoomCapacity();
ClassRoom classRoom = new ClassRoom();
classRoom.describeStudent();
}
}
在驱动类中,我们首先使用的ClassPathXmlApplicationContext的加载配置文件中的bean。可以看到,我们创建了一个SpringBeanUtil和一个学生的bean。我们首先在静态方法中获取了学生实例,并将其打印出来了,我们也在新出来的classRoom实例中通过SpringBeanUtil获取了学生实例,并且对其进行了输出。如下是输出结果:
Is it not empty? true
I am a student.
可以看到,无论是在静态方法中,还是在手动new的实例中,我们都成功获取了IoC容器所管理的bean。如果我们想在静态属性中获取SpringBean,其实也非常简单,直接对属性赋值即可,如下所示:
private static Student student = SpringBeanUtil.getBean(Student.class);
https://blog.csdn.net/chen1403876161/article/details/53644024
https://www.cnblogs.com/zhangxufeng/p/9162184.html
@Bean
和@Component
:⟳
@Bean
是创建一个对象并且设置值(一般是外部的对象) ,一般修饰一个方法
,用来调用构造方法生成对象并设置属性值;@Component
是创建一个对象(一般是自己写的对象) ,一般修饰一个类
。
原因:因为外部的对象我们手头并没有源码,所以只能用
@Bean
的方式调用构造方法创建,并修改属性,做一些处理,然后再返回为组件。
而我们自己写的代码,可以任意修改。
@Bean
⟳
我们拿系统自带的线程池来举例子,我们的线程池可以自行去配置相关属性以及规则,最后装配成bean组件进行使用。
@EnableAsync
@Configuration
class TaskPoolConfig {
@Value("${spring.task.execution.pool.core-size}")
private int corePoolSize;
@Value("${spring.task.execution.pool.max-size}")
private int maxPoolSize;
@Value("${spring.task.execution.pool.queue-capacity}")
private int queueCapacity;
@Value("${spring.task.execution.thread-name-prefix}")
private String threadNamePrefix;
// 线程池A
@Primary // 当有多个相同类型的bean时,使用@Primary来赋予bean更高的优先级
@Bean("commonTaskExecutor")
public ThreadPoolTaskExecutor commonTaskExecutor() {
ThreadPoolTaskExecutor executor = new ShiroSubjectAwareWithTraceTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(180);
executor.setThreadNamePrefix(threadNamePrefix);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(180);
executor.initialize();
return executor;
}
// 下面还会有线程池B。。。。
}
@Resource
private ThreadPoolTaskExecutor commonTaskExecutor;
@Async("commonTaskExecutor")
public void test() {}
还有,我们拿MybatisPlus框架使用乐观锁来举例子
首先需要在数据库增加version字典,默认为1
然后在实体类增加对应的字段
// 乐观锁Version注解
@Version
private Integer version;
注册组件,在MybatisPlusConfig中配置
// 注册乐观锁
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
小伙伴们看到了吗?上面就是调用了构造方法来创建乐观锁对象,使用@Bean
注册。
@Component
⟳
上面说过了,@Component
和@Controller
是一个东西,我们就拿控制器来举例子吧。
@Controller
public class UserController {
}
配置文件⟳
配置类:
// 只有当条件满足时,该配置才生效
@Configuration
@ConditionalOnProperty(prefix = "human", name = "type", havingValue = "man", matchIfMissing = true)
public class StorageBosConfigure {
@Bean
@ConfigurationProperties(prefix = "human.man")
public ManConfig manConfig() {
return new ManConfig();
}
@Data
public static class ManConfig {
private String id;
private String name;
private int age = 0; // 默认年龄为0
}
}
yaml:
human:
type: man
man:
id: 1
name: zhangsan
age: 20
笔者将不定期更新【考研或就业】的专业相关知识以及自身理解,希望大家能【关注】我。
如果觉得对您有用,请点击左下角的【点赞】按钮,给我一些鼓励,谢谢!
如果有更好的理解或建议,请在【评论】中写出,我会及时修改,谢谢啦!
本文来自博客园,作者:Nemo&
转载请注明原文链接:https://www.cnblogs.com/blknemo/p/13453710.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步