Kotlin的Spring之旅(二):IOC控制反转

时间:2022-03-04 20:08:43

IOC(控制反转)

ioc是一种用来降低代码耦合度的设计模式,一直以来都有很多方法来降低耦合度,而ioc是目前最有效最彻底的方法

ioc是基于xml配置的方式,使用反射的方式来实现类的创建。这样可由IoC容器来管理对象的生命周期、依赖关系等,从而使得应用程序的配置和依赖性规范与实际的应用程序代码分开

简单来说,ioc就是把所有类都配置到了xml中,这样一来,你需要使用一个类的时候就不需要new出来了,这意味着你可以根据需要随意更改对象,而不需要修改任何一点源代码

优点:

因为把对象生成放在了XML里定义,所以当我们需要换一个实现子类将会变成很简单,只要修改XML就可以了,这样我们甚至可以实现对象的热插拔

缺点:

1. 代码量稍有提升

2. 由于用了反射,效率也有所降低

虽然有不少缺点,但是对于现在项目最大的成本来源——维护 来说,这点缺点都不是问题,ioc将维护的成本大大降低,修改功能再也不需要去在茫茫多的源代码里面寻找一条条去改了,只要修改一下xml就可以了

下面就进入Spring中的IOC学习

第一步 配置bean(将类配置到xml中)

bean的实例化有三种方法

  • 使用类的无参构造创建
  • 使用静态工厂创建
  • 使用实例工厂创建

首先第一种 无参构造

我们先创建一个Beans.kt,由于有了kotlin的数据类(data class),我们可以把所有的bean都写在这个文件中

@Bean
data class User(var name: String, var age: Int)

首先创建一个User类,注意使用@Bean,不然数据类是没有无参构造的,不知道这个是什么的童鞋可以看一下我的上一篇,详细介绍过了

然后进入我们的applicationContext.xml配置bean

<!-- 使用无参构造实现bean的创建 -->
<bean id="user" class="com.kotlin.Bean.User"></bean>

这里可能会报一个没有无参构造的错,不过没关系,这只是编译器发现了我们用的是数据类,提示我们数据类是没有无参构造的,但是我们用的noarg是在编译时候才会生成无参构造,所以编译器是检查不出来的

接下来我们就来写一个测试类来测试下我们有没有配置成功

class main
{
@Test
fun test()
{
//加载Spring配置文件(选择你自己的配置文件位置)
val context = FileSystemXmlApplicationContext("src/main/webapp/WEB-INF/applicationContext.xml")
//创建对象(这里就是你配置的id值了)
val user = context.getBean("user") as User
println(user)

}
}

然后运行下,让我们来看看结果

Kotlin的Spring之旅(二):IOC控制反转

如果显示出了User那么你就成功了

有小伙伴要问了,要是有参数怎么办,其实只需要在bean标签下加上constructor-arg标签就可以进行配置了,不过不急,我们后面会一一道来

第二种 静态工厂

这时候就有一个问题了,在kotlin中是没有静态类这个东西的,不过并不要紧,因为这种配置方式本来就很少用

如果非要写的话,也不是不可以,kotlin中虽然没有静态类,但是肯定是有差不多的东西的不是么

kotlin中有两种代替静态类的方法

  • 包级函数
  • 伴生对象

下面我们就一种种试试

1. 包级函数

先创建一个UserFactory.kt,然后来一个getUser()

inline fun getUser(): User
{
return User("mike", 13)
}

然后配置bean

<!-- 用包级函数实现静态工厂方式构建bean -->
<bean id="bean" class="com.kotlin.utils.UserFactoryKt" factory-method="getUser"></bean>

进入测试类来测试一下

class main
{
@Test
fun test()
{
//加载Spring配置文件,创建对象
val context = FileSystemXmlApplicationContext("src/main/webapp/WEB-INF/applicationContext.xml")
//只需要改变一下id就行了
val user = context.getBean("bean") as User
println(user)

}
}

最后来看看,可以么

Kotlin的Spring之旅(二):IOC控制反转

不出意外,成功了

2. 伴生对象

就在UserFactory.kt中,我们来写一个UserFactory类

class UserFactory
{
companion object
{
@JvmStatic
fun getUser(): User
{
return User("jack", 35)
}
}
}

我们可以看到我不仅写了一个getUser还加了一个注释,因为毕竟不是静态方法,用起来时候还是有点不一样的,而这个注释就可以帮助我们把这些不同全部抹去

(具体的我就不说了,毕竟不是专门说kotlin的,有兴趣的小伙伴可以在java文件中调用一下我们写的这个方法,看看加和不加有什么区别)

配置一下bean

<!-- 用伴生对象实现静态工厂方式构建bean -->
<bean id="bean2" class="com.kotlin.utils.UserFactory" factory-method="getUser"></bean>

让我们来运行下试试

Kotlin的Spring之旅(二):IOC控制反转

也是可以的

第三种 实例工厂

这种方式也是最复杂,最少用的方式,大家看看就行了

我就直接在我们之前的UserFactory中写了

class UserFactory
{
fun getUser2(): User
{
return User("rose", 28)
}
}

创建完后,由于这方法并不是静态的,所以我们还要配置下这个工厂

<!-- 使用实例工厂创建bean对象 -->
<!-- 首先创建工厂对象 -->
<bean id="userFactory" class="com.kotlin.utils.UserFactory"></bean>
<bean id="bean3" factory-bean="userFactory" factory-method="getUser2"></bean>

运行下看看

Kotlin的Spring之旅(二):IOC控制反转

结果毫无疑问

第二步 属性注入

对于属性的注入,spring中有两种方式

  • 通过有参构造注入
  • set方法注入

1. 有参构造注入

对象我们就用之前的那个User,就不改了,直接进入bean的配置

<!--有参构造注入属性值-->
<bean id="user" class="com.kotlin.Bean.User">
<!--配置属性值-->
<constructor-arg name="name" value="tom"/>
<constructor-arg name="age" value="35"/>
</bean>

之前也提了下,使用constructor-arg进行参数的赋值,然后就运行下看看吧

Kotlin的Spring之旅(二):IOC控制反转

结果显示注入成功

2. set方法注入

在我们平时的使用中,这个是我们用的最多的注入方法

由于kotlin的数据类自带了get和set方法,我们就不用自己手写了,直接进入bean的配置

<!--set方法注入-->
<bean id="user2" class="com.kotlin.Bean.User">
<property name="name" value="barry"/>
<property name="age" value="23"/>
</bean>

修改id,点击运行

Kotlin的Spring之旅(二):IOC控制反转

结果出来了

这时可能大家会想,那我要注入一个对象怎么办呢?

这个其实差不多,只是注入的从一个具体值变成了一个引用

这次我们稍微写完整一点,首先创建UserDao,而这个UserDao中有一个payMoney方法

@Bean
data class UserDao(var name : String)
{
fun payMoney()
{
println("pay money")
}
}

你可以直接把UserDao定义为数据类,这样会省事很多

然后再定义一个UserService,构造函数为UserDao

@Bean
data class UserService(var userDao: UserDao)
{
fun payMoney()
{
userDao.payMoney()
println("service pay money")
}
}

Servlet就暂时用我们的测试函数来代替,然后就进行bean的配置

<!--对象注入-->
<!--配置UserDao-->
<bean id="userDao" class="com.kotlin.Dao.UserDao"></bean>
<bean id="userService" class="com.kotlin.Service.UserService">
<!--name为UserService中属性的名字-->
<!--由于数据类的特殊性,用constructor-arg和property都是可以的-->
<!--<constructor-arg name="userDao" ref="userDao"/>-->
<property name="userDao" ref="userDao"/>
</bean>

然后进入我们的测试类,进行测试

class main
{
@Test
fun test()
{
//加载Spring配置文件,创建对象
val context = FileSystemXmlApplicationContext("src/main/webapp/WEB-INF/applicationContext.xml")
//service的id
val userService = context.getBean("userService") as UserService
userService.payMoney()
}
}

Kotlin的Spring之旅(二):IOC控制反转

能够注入对象,对数组,list等一些自然也是没有问题的了,这些我就不一一展示了,我把代码贴出来,大家看一下就行了

<!--注入复杂类型属性-->
<bean id="user" class="com.kotlin.Bean.User">
<!--array-->
<property name="array">
<array>
<value>张龙</value>
<value>赵虎</value>
<value>王朝</value>
<value>马汉</value>
</array>
</property>
<!--list-->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
<value>王二麻子</value>
</list>
</property>
<!--map-->
<property name="map">
<map>
<entry key="1" value="a"/>
<entry key="2" value="b"/>
<entry key="3" value="c"/>
</map>
</property>
<!--properties-->
<property name="pro">
<props>
<prop key="driverclass">com.mysql.jdbc.Driver</prop>
<prop key="username">root</prop>
</props>
</property>
</bean>

除此之外,还有一种p名称空间注入,这种方式也蛮简单的,这里贴出来,大家看一看

<!-- 只需要在这里加一个 xmlns:p="http://www.springframework.org/schema/p"-->
<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"
xmlns:p="http://www.springframework.org/schema/p"
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"
>


<!-- 这里就可以直接用 p:属性名="属性值" 的方式来注入 -->
<bean id="user" class="com.kotlin.Bean.User" p:name="26"/>

看到这边,肯定有人觉得,着代码量何止多了一点,简直多的没谱了。

确实,如果只能这样写的话,代码量确实有些大,不过自然是有解决办法的,那就是注解

第三步 使用注解

有了注解,我们的代码量一下就少了很多了,下面看下用法就知道了

<!--注解扫描-->
<!--1.到包中扫描类,方法,属性上面的注解-->
<context:component-scan base-package="com.kotlin"></context:component-scan>

<!--2.只扫描属性上面的注解-->
<!-- <context:annotation-config ></context:annotation-config> -->

这样以来,我们只要写一下注解,xml中只留一句话就行了。一般情况下我们都用上面那一种

我们先看一下例子,还是原来的那些,只是我们加上注解

@Bean
@Repository(value = "userDao")
data class UserDao(var name : String)
{
fun payMoney()
{
println("dao pay money")
}
}
@Bean
@Service(value = "userService")
data
class UserService(@Resource(name = "userDao")var userDao: UserDao)
{

fun payMoney()
{
userDao.payMoney()
println("service pay money")
}
}
class main
{
@Test
fun test()
{
//加载Spring配置文件,创建对象
val context = FileSystemXmlApplicationContext("src/main/webapp/WEB-INF/applicationContext.xml")
//service的id
val userService = context.getBean("userService") as UserService
userService.payMoney()
}
}

并没有多几句话,我们来试着运行一下

Kotlin的Spring之旅(二):IOC控制反转

结果和之前一模一样

现在我们来好好看一下注解

注解也分为两种,一种是在注解类的,一种是注解属性的,和我们配置文件是一样的

1. 注解类

注解类的注解有四种

  • @Controller : WEB层
  • @Service : 业务层
  • @Respository : 持久层
  • @Component

这些是为了区分各个层次的。让标注本身的用途更清晰,但其实,现在他们四个的功能是一样的

你没有看错,这四个的功能是一样的

这只是spring为了区分各个层次和为了未来版本的扩展准备的,所以我也建议大家还是记住并按这个去写,万一哪天spring更新了新功能,你也可以不需要做任何修改

2. 注解属性

属性的注解有两种

  • @Autowired : 自动填装
  • @Resource : 指定目标装配

自动填装根据你注释的那个属性的类去找被注解过的类,然后自动将其填充进去

指定目标装配 :需要指定一个name,它将会根据你给的name去找对应的类,然后填充

最后,注解和xml配置是可以混合使用的,需要的时候我们也会把他们混一起使用,比如在xml里面配置类,用注解填装属性