Spring学习04-初识Spring(IoC&DI)
1.1Spring是什么
Spring框架是一个开放源代码的J2EE应用程序框架
这里罗列了Spring的六大定义。
1、Spring的核心是一个轻量级(Lightweight)的容器(Container)。
2、Spring是实现IoC(Inversion of Control)容器和非入侵性(No intrusive)的框架。
3、Spring提供AOP(Aspect-oriented programming)概念的实现方式。
4、Spring提供对持久层(Persistence)、事物(Transcation)的支持。
5、Spring供MVC Web框架的实现,并对一些常用的企业服务API(Application Interface)提供一致的模型封装。
6、Spring提供了对现存的各种框架(Structs、JSF、Hibernate、Ibatis、Webwork等)相整合的方案。
1.2 Spring quick start
这里推荐使用Maven项目工具创建Spring!
首先第一步创建Maven项目,只需要创建最普通的Maven项目即可,不需要使用骨架。
第二步,取个名字点击Finish即可
第三步,由于这里想要创建多个Module,所以建议把src全部删除
删除之后,我们就得到了一个非常干净的Maven项目。
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<!-- 使用spring-webmvc可以讲spring 6大核心一次性导入,不然需要写6个依赖-->
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- 测试使用junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
</dependencies>
下面还需要增加一些项目构建时候的配置
<build>
<!-- Java项目都要编译成class字节码文件运行,idea讲这些文件放在target中,每次编译都会更新,但不一定会把配置文件如mybatis中的mapper.xml复制一份放入target中,这段代码可以把所有的xml文件复制一份到target中。-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<!-- 由于SpringMVC支持的Java版本较低,所以在Maven中我们设定了使用Java8-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
可以点击右边Maven查看jar包是否都导入成功。
到现在基本配置都已完成,可以开始了
现在创建Module,一样是普通maven项目,这里取名为Spring01-IoC
public class Hello {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("Hello,"+ name );
}
}
在resource目录下创建文件为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">
<!--bean就是java对象 , 由Spring创建和管理-->
<!-- 通过id找到bean, 每个bean对应了一个class,class的位置是从java目录下开始的-->
<bean id="hello" class="Hello">
<!-- property属性设置,spring会调用类中的set方法进行值的注入-->
<!-- name属性必须是set方法后的名字-->
<property name="name" value="Spring"/>
</bean>
</beans>
使用junit测试
public class JunitTest {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Hello hello = context.getBean("hello", Hello.class);
hello.show();
}
}
//Hello,Spring
ApplicationContext 相当于一个工厂,我们并没有手动的new实例化对象,而是告诉了ApplicationContext 我们需要得到hello的这个类的对象,它就返回一个对象。
quick start 就完成了!
1.3 什么是Spring IoC 和 DI
再讲之前,先举个例子
比如说,在A类对象中,有B类对象的依赖。A类对象中的B 直接耦合死 为 B b = new B(); 这时候B类的控制权完全在A类手上,就是控制权在调用方。
当有新的需求来临的时,需要修改B类为C类时,我们只能在A类中修改原代码。这就已经违背了开闭原则。
为了使A类与B类解耦,我们首先想到的就是使用set方法,对象的实例化过程并不由A类控制,又服务方控制,想要注入什么样的实例对象就注入什么样的。
此时控制权就已经不在A类手上,反转到服务方了,这就实现了控制反转,而实现控制反转的方式就是使用了依赖注入set方法。
将上图的需求,修改为使用 IoC 的调用代码方式。就是将代码的控制权从调用方修改为被调用方,意味着,代码的调用权转移给被调用方(我们也称为服务方),不用修改调用方的代码,只要修改配置文件就实现对象的切换。将 A 类调用 B 类的对象修改为 C 类的对象,修改的是被调用方的配置文件的代码,所以代码的调用权转移到了被调用方。通过控制反转,我们可以实现增加模块或者移除模块统一由配置文件关联,所以增加或者移除模块配置 XML 配置文件即可。
这里就可利用Java的多态,可以使用父类的继承类注入,也可以使用接口的实现类注入。
所以
控制反转(IoC)= 依赖注入 + 面向接口的编程思想的实现
其实在spring quick start中就是用了set方法注入name,name的名字并不由hello来控制,而是由服务方决定。
1.4 IoC 加载的扩展
1.引用set注入
现在我们用User类封装了了一个用户的一些信息,有user的Id,Name和Record(依赖),Record 又封装了一些信息。都分别写好了无参构造和全参构造和getter&setter方法,重写toString()方法。
public class User {
private int UserId;
private String UserName;
private UserRecord userRecord;
public User() {
}
public User(int userId, String userName, UserRecord userRecord) {
UserId = userId;
UserName = userName;
this.userRecord = userRecord;
}
public int getUserId() {
return UserId;
}
public void setUserId(int userId) {
UserId = userId;
}
public String getUserName() {
return UserName;
}
public void setUserName(String userName) {
UserName = userName;
}
public UserRecord getUserRecord() {
return userRecord;
}
public void setUserRecord(UserRecord userRecord) {
this.userRecord = userRecord;
}
@Override
public String toString() {
return "User{" +
"UserId=" + UserId +
", UserName='" + UserName + '\'' +
", userRecord=" + userRecord +
'}';
}
}
public class UserRecord {
private int balance;
private String LastLoginLocation;
private String LastLoginTime;
private int LoginTimes;
public UserRecord() {
}
public UserRecord(int balance, String lastLoginLocation, String lastLoginTime, int loginTimes) {
this.balance = balance;
LastLoginLocation = lastLoginLocation;
LastLoginTime = lastLoginTime;
LoginTimes = loginTimes;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public String getLastLoginLocation() {
return LastLoginLocation;
}
public void setLastLoginLocation(String lastLoginLocation) {
LastLoginLocation = lastLoginLocation;
}
public String getLastLoginTime() {
return LastLoginTime;
}
public void setLastLoginTime(String lastLoginTime) {
LastLoginTime = lastLoginTime;
}
public int getLoginTimes() {
return LoginTimes;
}
public void setLoginTimes(int loginTimes) {
LoginTimes = loginTimes;
}
@Override
public String toString() {
return "UserRecord{" +
"balance=" + balance +
", LastLoginLocation='" + LastLoginLocation + '\'' +
", LastLoginTime='" + LastLoginTime + '\'' +
", LoginTimes=" + LoginTimes +
'}';
}
创建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">
<bean id="userRecord" class="com.dao.UserRecord">
<property name="balance" value="100"/>
<property name="loginTimes" value="1"/>
<property name="lastLoginLocation" value="ShangHai"/>
<property name="lastLoginTime" value="24:00"/>
</bean>
<bean id="user" class="com.dao.User">
<property name="userId" value="1"/>
<property name="userName" value="admin"/>
<property name="userRecord" ref="userRecord"/>
</bean>
</beans>
用property属性是使用set方法注入,value代表注入基本数据类型,ref代表注入引用数据类型,直接用生产该类的bean id即可。
下面来测试一下
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");//默认范围的是object类,后面加了User.class就返回User类
System.out.println(user);
}
//User{UserId=1, UserName='admin', userRecord=UserRecord{balance=100, LastLoginLocation='ShangHai', LastLoginTime='24:00', LoginTimes=1}}
2.constructor注入
我们还写了构造方法,Spring也支持构造方法注入。
<bean id="userRecord" class="com.dao.UserRecord">
<constructor-arg index="0" value="10"/>
<constructor-arg index="1" value="24:00"/>
<constructor-arg index="2" value="ShangHai"/>
<constructor-arg index="3" value="1"/>
</bean>
index表示写构造方法时的参数顺序,测试后还是一样。
3.多种数据结构注入
Spring也支持很多数据结构的注入
这里又List,Map,Set,Array等属性。
public class Student {
private String name;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
public void setName(String name) {
this.name = name;
}
public void setBooks(String[] books) {
this.books = books;
}
public void setHobbys(List<String> hobbys) {
this.hobbys = hobbys;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public void setGames(Set<String> games) {
this.games = games;
}
public void setWife(String wife) {
this.wife = wife;
}
public void setInfo(Properties info) {
this.info = info;
}
public void show(){
System.out.println("name="+ name
+ ",books="
);
for (String book:books){
System.out.print("<<"+book+">>\t");
}
System.out.println("\n爱好:"+hobbys);
System.out.println("card:"+card);
System.out.println("games:"+games);
System.out.println("wife:"+wife);
System.out.println("info:"+info);
}
}
<bean id="student" class="com.dao.Student">
<property name="name" value="小明"/>
<property name="books">
<array>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</array>
</property>
<property name="hobbys">
<list>
<value>听歌</value>
<value>看电影</value>
<value>爬山</value>
</list>
</property>
<property name="card">
<map>
<entry key="中国邮政" value="456456456465456"/>
<entry key="建设" value="1456682255511"/>
</map>
</property>
<property name="games">
<set>
<value>LOL</value>
<value>BOB</value>
<value>COC</value>
</set>
</property>
<property name="wife"><null/></property>
<property name="info">
<props>
<prop key="学号">20190604</prop>
<prop key="性别">男</prop>
<prop key="姓名">小明</prop>
</props>
</property>
</bean>
测试一下
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
student.show();
}
/*name=小明,books=
<<西游记>> <<红楼梦>> <<水浒传>>
爱好:[听歌, 看电影, 爬山]
card:{中国邮政=456456456465456, 建设=1456682255511}
games:[LOL, BOB, COC]
wife:null
info:{姓名=小明, 学号=20190604, 性别=男}
* */
4.P命名空间
在beans属性中新加入 导入约束 : xmlns:p="http://www.springframework.org/schema/p"
就是简化版的properties.
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
<bean id="userRecord2" class="com.dao.UserRecord" p:balance="10" p:lastLoginLocation="BeiJing" p:loginTimes="10" p:lastLoginTime="10:00"/>
</beans>
5.C命名空间
导入约束 : xmlns:c="http://www.springframework.org/schema/c"
<!--C(构造: Constructor)命名空间 , 属性依然要设置set方法-->
<bean id="userRecord2" class="com.dao.UserRecord" c:balance="10" c:lastLoginLocation="BeiJing" c:loginTimes="10" c:lastLoginTime="10:00"/>
测试代码和上面一样,就不多演示了。
6.bean属性中的其他参数
<bean id="admin" class="com.dao.User" scope="singleton" lazy-init="false" autowire="byName"/>
Scope作用域
有四种属性
Singleton
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型
Prototype
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例
Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例
Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例
lazy-init
设置bean对象是否懒加载,如果设为true,则应用第一次用到bean时才实例化对象,否则在初始化spring容器时加载*单例*bean对象。(*非单例不实例化**)*
abstract
设置bean是否为抽象类,默认abstract=“false”,如果设为true,将不能被实例化;
destroy-method
它的作用是在销毁bean之前可以执行指定的方法。注意:必须满足scope=“singleton”,并且destroy方法参数个数不能超过1,并且参数类型只能为boolean。
7.自动装配
自动装配直到是自动注入引用。
这个例子中User中有一个Dog的依赖。
public class Dog {
public void sound(){
System.out.println("wang- wang - wang");
}
}
public class User {
private Dog dog;
public User(Dog dog) {
this.dog = dog;
}
public User() {
}
public void setDog(Dog dog) {
this.dog = dog;
}
public void runDog(){
dog.sound();
}
}
现在我们配置xml 注意autowire
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dog" class="com.dao.Dog"/>
<bean id="user" class="com.dao.User" autowire="byName"/>
</beans>
测试
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
user.runDog();
}
//wang- wang - wang
autowire
byName
-
将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
-
去spring容器中寻找是否有此字符串名称id的对象。
-
如果有,就取出注入;如果没有,就报空指针异常。
byType
- 同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
8.使用注解开发
jdk1.5开始支持注解,spring2.5开始全面支持注解。
准备工作:利用注解的方式注入属性。
1、在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
2、开启属性注解支持!
<context:annotation-config/>
@Autowired&@Qualifier
- @Autowired是按类型自动转配的,不支持id匹配。
- 需要导入 spring-aop的包!
- @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
- @Qualifier不能单独使用。
<?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: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/>
<bean id="dog" class="com.dao.Dog"/>
<bean id="user" class="com.dao.User"/>
</beans>
在User类中加入注解
@Autowired
@Qualifier(value = "dog")
private Dog dog;
测试代码不变。
@Resource
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
- 其次再进行默认的byName方式进行装配;
- 如果以上都不成功,则按byType的方式自动装配。
- 都不成功,则报异常。
@Autowired与@Resource异同:
1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
3、@Resource(属于J2EE复返,并不属于spring),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
@Component
增加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:component-scan base-package="com.pojo"/>
</beans>
@Component("user")
@Scope("singleton") //相当于bean 标签的scope
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
@Value("admin")
// 相当于配置文件中 <property name="name" value="秦疆"/>
public String name;
}
@Test
public void test(){
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.name); //admin
}
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:web层
- @Service:service层
- @Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
@Configuration &@Bean
编写一个实体类
@Component //将这个类标注为Spring的一个组件,放到容器中!
public class Dog {
@Value("dogg")
public String name;
}
编写java配置文件
@Configuration //代表这是一个配置类 就等同于xml文件
//@Import(MyConfig2.class) //导入合并其他配置类,类似于配置文件中的 inculde 标签
public class MyConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public Dog dog(){
return new Dog();
}
}
测试,此次加载用的java配置类加载
@Test
public void test2(){
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(MyConfig.class);
Dog dog = (Dog) applicationContext.getBean("dog");
System.out.println(dog.name);//dogg
}
Reference
主要材料:《Spring 5核心原理与30个类手写实战》
ioc https://blog.csdn.net/weixin_40867255/article/details/91049459
ioc2:https://www.cnblogs.com/maigy/p/10808308.html
java:https://www.zhihu.com/question/353412842/answer/877602348
狂神笔记:https://mp.weixin.qq.com/s/VM6INdNB_hNfXCMq3UZgTQ
https://blog.csdn.net/a745233700/article/details/113840727
https://blog.csdn.net/weixin_44227923/article/details/87697194
spring bean 标签https://blog.csdn.net/ZixiangLi/article/details/87937819
狂神注解开发:https://mp.weixin.qq.com/s/dCeQwaQ-A97FiUxs7INlHw