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 id="user" class="com.kotlin.Bean.User"></bean>
这里可能会报一个没有无参构造的错,不过没关系,这只是编译器发现了我们用的是数据类,提示我们数据类是没有无参构造的,但是我们用的noarg是在编译时候才会生成无参构造,所以编译器是检查不出来的
接下来我们就来写一个测试类来测试下我们有没有配置成功
class main
{
@Test
fun test()
{
val context = FileSystemXmlApplicationContext("src/main/webapp/WEB-INF/applicationContext.xml")
val user = context.getBean("user") as User
println(user)
}
}
然后运行下,让我们来看看结果
如果显示出了User那么你就成功了
有小伙伴要问了,要是有参数怎么办,其实只需要在bean标签下加上constructor-arg标签就可以进行配置了,不过不急,我们后面会一一道来
第二种 静态工厂
这时候就有一个问题了,在kotlin中是没有静态类这个东西的,不过并不要紧,因为这种配置方式本来就很少用
如果非要写的话,也不是不可以,kotlin中虽然没有静态类,但是肯定是有差不多的东西的不是么
kotlin中有两种代替静态类的方法
下面我们就一种种试试
1. 包级函数
先创建一个UserFactory.kt,然后来一个getUser()
inline fun getUser(): User
{
return User("mike", 13)
}
然后配置bean
<bean id="bean" class="com.kotlin.utils.UserFactoryKt" factory-method="getUser"></bean>
进入测试类来测试一下
class main
{
@Test
fun test()
{
val context = FileSystemXmlApplicationContext("src/main/webapp/WEB-INF/applicationContext.xml")
val user = context.getBean("bean") as User
println(user)
}
}
最后来看看,可以么
不出意外,成功了
2. 伴生对象
就在UserFactory.kt中,我们来写一个UserFactory类
class UserFactory
{
companion object
{
@JvmStatic
fun getUser(): User
{
return User("jack", 35)
}
}
}
我们可以看到我不仅写了一个getUser还加了一个注释,因为毕竟不是静态方法,用起来时候还是有点不一样的,而这个注释就可以帮助我们把这些不同全部抹去
(具体的我就不说了,毕竟不是专门说kotlin的,有兴趣的小伙伴可以在java文件中调用一下我们写的这个方法,看看加和不加有什么区别)
配置一下bean
<bean id="bean2" class="com.kotlin.utils.UserFactory" factory-method="getUser"></bean>
让我们来运行下试试
也是可以的
第三种 实例工厂
这种方式也是最复杂,最少用的方式,大家看看就行了
我就直接在我们之前的UserFactory中写了
class UserFactory
{
fun getUser2(): User
{
return User("rose", 28)
}
}
创建完后,由于这方法并不是静态的,所以我们还要配置下这个工厂
<bean id="userFactory" class="com.kotlin.utils.UserFactory"></bean>
<bean id="bean3" factory-bean="userFactory" factory-method="getUser2"></bean>
运行下看看
结果毫无疑问
第二步 属性注入
对于属性的注入,spring中有两种方式
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
进行参数的赋值,然后就运行下看看吧
结果显示注入成功
2. set方法注入
在我们平时的使用中,这个是我们用的最多的注入方法
由于kotlin的数据类自带了get和set方法,我们就不用自己手写了,直接进入bean的配置
<bean id="user2" class="com.kotlin.Bean.User">
<property name="name" value="barry"/>
<property name="age" value="23"/>
</bean>
修改id,点击运行
结果出来了
这时可能大家会想,那我要注入一个对象怎么办呢?
这个其实差不多,只是注入的从一个具体值变成了一个引用
这次我们稍微写完整一点,首先创建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的配置
<bean id="userDao" class="com.kotlin.Dao.UserDao"></bean>
<bean id="userService" class="com.kotlin.Service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
然后进入我们的测试类,进行测试
class main
{
@Test
fun test()
{
val context = FileSystemXmlApplicationContext("src/main/webapp/WEB-INF/applicationContext.xml")
val userService = context.getBean("userService") as UserService
userService.payMoney()
}
}
能够注入对象,对数组,list等一些自然也是没有问题的了,这些我就不一一展示了,我把代码贴出来,大家看一下就行了
<bean id="user" class="com.kotlin.Bean.User">
<property name="array">
<array>
<value>张龙</value>
<value>赵虎</value>
<value>王朝</value>
<value>马汉</value>
</array>
</property>
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
<value>王二麻子</value>
</list>
</property>
<property name="map">
<map>
<entry key="1" value="a"/>
<entry key="2" value="b"/>
<entry key="3" value="c"/>
</map>
</property>
<property name="pro">
<props>
<prop key="driverclass">com.mysql.jdbc.Driver</prop>
<prop key="username">root</prop>
</props>
</property>
</bean>
除此之外,还有一种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">
<bean id="user" class="com.kotlin.Bean.User" p:name="26"/>
看到这边,肯定有人觉得,着代码量何止多了一点,简直多的没谱了。
确实,如果只能这样写的话,代码量确实有些大,不过自然是有解决办法的,那就是注解
第三步 使用注解
有了注解,我们的代码量一下就少了很多了,下面看下用法就知道了
<context:component-scan base-package="com.kotlin"></context:component-scan>
这样以来,我们只要写一下注解,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()
{
val context = FileSystemXmlApplicationContext("src/main/webapp/WEB-INF/applicationContext.xml")
val userService = context.getBean("userService") as UserService
userService.payMoney()
}
}
并没有多几句话,我们来试着运行一下
结果和之前一模一样
现在我们来好好看一下注解
注解也分为两种,一种是在注解类的,一种是注解属性的,和我们配置文件是一样的
1. 注解类
注解类的注解有四种
- @Controller : WEB层
- @Service : 业务层
- @Respository : 持久层
- @Component
这些是为了区分各个层次的。让标注本身的用途更清晰,但其实,现在他们四个的功能是一样的
你没有看错,这四个的功能是一样的
这只是spring为了区分各个层次和为了未来版本的扩展准备的,所以我也建议大家还是记住并按这个去写,万一哪天spring更新了新功能,你也可以不需要做任何修改
2. 注解属性
属性的注解有两种
- @Autowired : 自动填装
- @Resource : 指定目标装配
自动填装 :根据你注释的那个属性的类去找被注解过的类,然后自动将其填充进去
指定目标装配 :需要指定一个name,它将会根据你给的name去找对应的类,然后填充
最后,注解和xml配置是可以混合使用的,需要的时候我们也会把他们混一起使用,比如在xml里面配置类,用注解填装属性