1. IOC
1.1 拓展
现代化的Java开发,就是基于Spring的开发
- SpringBoot
- 快速开发的脚手架
- 基于SpringBoot可以快速开发微服务
- 约定大于配置
- SpringCloud
- SpringCloud是基于SpringBoot实现的
学习SpringBoot的前提是掌握Spring以及SpringMVC,SpringBoot起到承上启下 的作用;
弊端:配置十分繁琐,人称:“配置地狱”;
1.2 IOC理论推导
问题展示
对象包:(接口)
package com.kangzhu.dao;
public interface UserDao {
public void getUser();
}
对象包:(实现1)
package com.kangzhu.dao;
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("I am the user");
}
}
对象包:(实现2)
package com.kangzhu.dao;
public class UserDaoMysqlImpl implements UserDao{
@Override
public void getUser() {
System.out.println("I am the user found by Mysql");
}
}
服务包:(接口)
package com.kangzhu.service;
public interface UserService {
public void getUser();
}
服务包:(实现)
package com.kangzhu.service;
import com.kangzhu.dao.UserDao;
import com.kangzhu.dao.UserDaoImpl;
import com.kangzhu.dao.UserDaoMysqlImpl;
public class UserServiceImpl implements UserService{
// private UserDao userDao = new UserDaoImpl();
private UserDao userDao = new UserDaoMysqlImpl();
@Override
public void getUser() {
// 业务层调用dao层
userDao.getUser();
}
}
客户端(测试类)
import com.kangzhu.service.UserServiceImpl;
public class MyTest {
public static void main(String[] args) {
// 用户实际调用的是业务层
UserServiceImpl userService = new UserServiceImpl();
userService.getUser();
}
}
问题在哪呢?
客户端的需求可能会影响我们的代码,如果说客户端想要通过普通的方式查询用户,服务包:(实现)中需要引入对象包(实现1),如果客户端想要通过Mysql的方式查询用户,需要引入对象包(实现2);
需要根据客户端的需求修改源代码,如果代码量很大,修改一次的成本就会十分昂贵!
解决方法(set注入)
改变服务实现类
package com.kangzhu.service;
import com.kangzhu.dao.UserDao;
public class UserServiceImpl implements UserService{
private UserDao userDao = null;
// set注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
客户端:
import com.kangzhu.dao.UserDaoMysqlImpl;
import com.kangzhu.service.UserServiceImpl;
public class MyTest {
public static void main(String[] args) {
// 用户实际调用的是业务层
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(new UserDaoMysqlImpl());
userService.getUser();
}
}
- 之前,程序是主动对象,控制权在程序员手上;
- 使用了set注入后,程序不再具有主动性,而是变成了接收对象;
这种思想从本质上解决了问题,程序员不用再去管理对象的创建;系统的耦合性降低,程序员可以专注在业务实现上;
这就是IOC的原型!
IOC的本质:
- 控制反转,是一种设计思想;
- 所谓控制反转就是:获得依赖对象的方式反转了;
2. HelloSpring
-
编写实体类
package com.kangzhu.pojo; public class Hello { private String str; public String getStr() { return str; } public void setStr(String str) { this.str = str; } @Override public String toString() { return "Hello{" + "str='" + str + '\'' + '}'; } }
-
添加配置文件(名称任意,这里是beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--使用Spring来创建对象,在Spring这些都称为Bean 类型 变量名 = new 类型(); id = 变量名 class = new的对象 property 相当于给对象中的属性设置一个值 --> <bean id="hello" class="com.kangzhu.pojo.Hello"> <property name="str" value="Spring"/> </bean> </beans>
-
测试
import com.kangzhu.pojo.Hello; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { public static void main(String[] args) { // 获取Spring的上下文对象 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); // 我们的对象都在Spring中管理了,我们要使用,直接去里面取出来就可以了 Hello hello = (Hello) context.getBean("hello"); System.out.println(hello.toString()); } }
控制:传统应用程序对象是由程序控制创建的,使用Spring后,对象是由Spring来创建的;
反转:程序本身不创建对象,而变成被动的接收对象
3. IOC创建对象方式
-
使用无参构造创建对象,默认
-
使用有参构造创建对象
-
下标赋值
<bean id="user1" class="com.kangzhu.pojo.User"> <constructor-arg index="0" value="kangzhu1"/> </bean>
-
根据参数类型赋值
<!--但是假设有两个参数的类型相同,这种方式不适用--> <bean id="user2" class="com.kangzhu.pojo.User"> <constructor-arg type="java.lang.String" value="kangzhu2"/> </bean>
-
根据参数名赋值
<bean id="user3" class="com.kangzhu.pojo.User"> <constructor-arg name="name" value="kangzhu3"/> </bean>
-
4. Spring配置
4.1 别名
<alias name="user" alias="aliasUser"/>
这里的name是bean中的id,也就是对象名,alias是别名;
4.2 Bean的配置
id:bean的唯一标识符,也就是对象名;
class:bean对象所对应的权限命名(包名.类名);
name:也是别名,而且name更高级,可以同时取多个别名(不同别名间通过空格,逗号,分号等等进行分割);
4.3 import
import一般用于团队开发使用,它可以将多个配置文件导入合并为一个;
假设项目中有多个人开发,这三个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的;
- programmer1 --> beans1.xml
- programmer2 --> beans2.xml
- programmer3 --> beans3.xml
总的是applicationContext.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
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
</beans>
如果说遇到相同的对象名,后面的会覆盖前面的;
5. 依赖注入
-
依赖:bean对象的创建依赖于容器;
-
注入:bean对象中的所有属性由容器注入;
5.1 构造器注入
上面的IOC创建对象方式中的使用有参构造创建对象就是构造器注入;
5.2 Set方式注入
这里创建两个实体类:
package com.kangzhu.pojo;
import java.util.*;
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String, String> card;
private Set<String> games;
private String wife;
private Properties info;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String[] getBooks() {
return books;
}
public void setBooks(String[] books) {
this.books = books;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public Map<String, String> getCard() {
return card;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public Set<String> getGames() {
return games;
}
public void setGames(Set<String> games) {
this.games = games;
}
public String getWife() {
return wife;
}
public void setWife(String wife) {
this.wife = wife;
}
public Properties getInfo() {
return info;
}
public void setInfo(Properties info) {
this.info = info;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", address=" + address +
", books=" + Arrays.toString(books) +
", hobbies=" + hobbies +
", card=" + card +
", games=" + games +
", wife='" + wife + '\'' +
", info=" + info +
'}';
}
}
package com.kangzhu.pojo;
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
根据数据类型来看set注入:
<?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
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="address" class="com.kangzhu.pojo.Address"/>
<bean id="student" class="com.kangzhu.pojo.Student">
<!-- 第一种,基本数据类型注入-->
<property name="name" value="kangzhu"/>
<!-- 第二种,引用注入-->
<property name="address" ref="address"/>
<!-- 第三种,数组注入-->
<property name="books">
<array>
<value>哈利波特</value>
<value>飘</value>
<value>箱子里的人</value>
</array>
</property>
<!-- 第四种,list注入-->
<property name="hobbies">
<list>
<value>电影</value>
<value>打游戏</value>
</list>
</property>
<!-- 第五种,map注入-->
<property name="card">
<map>
<entry key="学生卡" value="123456"/>
<entry key="身份证" value="123456"/>
</map>
</property>
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
<value>BOB</value>
</set>
</property>
<!-- 注入为null值-->
<property name="wife">
<null/>
</property>
<!-- properties注入-->
<property name="info">
<props>
<prop key="学号">123</prop>
<prop key="姓名">糠猪</prop>
</props>
</property>
</bean>
</beans>
5.3 拓展方式注入
可以使用p命名空间和c命名空间进行注入:
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- p命名空间注入,可以直接注入属性的值-->
<bean id="user" class="com.kangzhu.pojo.User" p:name="糠猪" p:age="18"/>
<!-- c命名空间注入-->
<bean id="user2" class="com.kangzhu.pojo.User" c:name="糠猪2" c:age="18"/>
</beans>
note:
-
p命名空间和c命名空间不能直接使用,需要导入xml约束:
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
-
p代表properties,相当于set注入,c代表constructor,相当于构造器注入;
6. bean作用域
-
The Singleton Scope(默认)
单例模式,全局共享一个实例,显式的设置单例模式
<bean id="user" class="com.kangzhu.pojo.User" p:name="糠猪" p:age="18" scope="singleton"/>
测试:
@Test public void test() { ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("userBeans.xml"); User user1 = (User) classPathXmlApplicationContext.getBean("user"); User user2 = (User) classPathXmlApplicationContext.getBean("user"); System.out.println(user1 == user2); }
返回结果式true;
-
The Prototype Scope
原型模式
<bean id="user" class="com.kangzhu.pojo.User" p:name="糠猪" p:age="18" scope="prototype"/>
每次getBean的时候都会返回一个新的对象,同样的测试返回结果式false;
-
其余的request、session、application这些只能在web开发中使用到;
7. Bean的自动装配
- 自动装配是Spring满足bean依赖注入的一种方式;
- Spring会在上下文中自动寻找,并自动给bean装配属性;
在Spring中有三种装配的方式:
- 在xml中显示的配置
- 在Java中显示配置
- 隐式的自动装配
7.1 测试环境搭建
实体类有People、Cat、Dog:
package com.kangzhu.pojo;
public class People {
private Cat cat;
private Dog dog;
private String name;
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "People{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
}
}
package com.kangzhu.pojo;
public class Cat {
public void shout() {
System.out.println("喵喵喵");
}
}
package com.kangzhu.pojo;
public class Dog {
public void shout() {
System.out.println("汪汪汪");
}
}
配置beans.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
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="cat" class="com.kangzhu.pojo.Cat"/>
<bean id="dog" class="com.kangzhu.pojo.Dog"/>
<bean id="people" class="com.kangzhu.pojo.People">
<property name="name" value="kanghzu"/>
<property name="dog" ref="dog"/>
<property name="cat" ref="cat"/>
</bean>
</beans>
7.2 byName自动装配
<bean id="people" class="com.kangzhu.pojo.People" autowire="byName">
<property name="name" value="kanghzu"/>
</bean>
会自动在容器上下文中查找,和自己对象set方法后面的值对应的beanId:
public void setCat(Cat cat) {
this.cat = cat;
}
public void setDog(Dog dog) {
this.dog = dog;
}
7.3 byType自动装配
<bean id="people" class="com.kangzhu.pojo.People" autowire="byType">
<property name="name" value="kanghzu"/>
</bean>
会自动在容器上下文中查找,和自己对象属性类型相同的bean(这里bean里面的id也可以省略);
假设有两个Dog类型会报错,因此必须保证类型全局唯一;
小结:
- byName的时候,需要保证所有的bean的id唯一,并且这个bean的id需要和自动注入的属性的set方法的值一致;
- byType的时候,需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性的类型一致;
7.4 使用注解实现自动装配
jdk1.5开始支持的注解,Spring2.5开始支持注解;
注解的配置bi
使用注解需要:
-
导入约束,context约束;
-
配置注解的支持:
<?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:annotation-config/> </beans>
@Autowired
直接在属性上使用即可:(People的部分代码)
public class People { @Autowired private Cat cat; @Autowired private Dog dog; private String name; }
同时因为是使用反射实现自动装配,所以可以不写相应的set方法;
默认情况是byType, 类型相同看neme,如果没有匹配的name的话会报错;
也可以显示指定:
// 指定IOC容器中的对象 @Qualifier(value="dog")
其它:
// 如果某个字段标记了这个注解,说明这个字段可以为null @Nullable
//如果显示定义了@Autowired的require属性为false,说明这个对象可以为null,否则不允许为空 @Autowired(required = false)
8. 使用注解开发
在Spring4之后,要使用注解开发,必须要保证aop的包导入了;
使用注解需要导入context约束,增加注解的支持;
-
bean
package com.kangzhu.pojo; import org.springframework.stereotype.Component; //@Component:组件,放在类上,说明这个类被Spring管理了,就是bean @Component //等价于<bean id="user" class="com.kangzhu.pojo.User"/> public class User { @Value("kangzhu") public String name; // 相当与<property name="name" value="kangzhu"/> }
-
衍生注解
mvc三层架构中:
- dao: @Repository
- service: @Service
- controller: @Controller
这三个注解和@Component功能是一样的;
-
自动装配(上面)
-
作用域(@Scope)
package com.kangzhu.pojo; import org.springframework.stereotype.Component; @Component @Scope("singleton") // 单例模式 @Scope("prototype") // 原型模式 public class User { @Value("kangzhu") public String name; }
-
最佳时间(见仁见智)
- xml来管理bean;
- 注解只负责完成属性的注入;
9. 使用Java的方式配置Spring
完全不使用Spring的xml配置!
Javaconfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能;
实体类:
package com.kangzhu.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
@Value("kangzhu")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
配置类:
package com.kangzhu.config;
import com.kangzhu.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//这个也会被Spring容器托管,注册到容器中,因为它本来就是一个@Component
//@Configuration代表这是一个配置类,和applicationContext.xml一样
@Configuration
public class Config {
// 这个方法的名字就相当于bean标签中的id属性
@Bean
public User getUser() {
return new User();
}
}
测试类:
import com.kangzhu.config.Config;
import com.kangzhu.pojo.User;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
@Test
public void test() {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Config.class);
// 取的话需要使用方法名
User user = (User) annotationConfigApplicationContext.getBean("getUser");
System.out.println(user.getName());
}
}