系列文章
SSM之Spring 01 —— 第一个Spring程序、配置说明、IOC、DI、自动装配、注解/Java方式开发
SSM之Spring 02 —— 动态代理、AOP、Spring-MyBatis、声明式事务
文章目录
1、Spring
1.1、简介
-
Spring:春天的意思,给软件行业带来春天!
-
2002年,首次推出Spring框架的雏形:Interface21框架
-
Spring框架即以Interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版
-
Rod Johnso,Srping Framework 创始人,著名作者。他是悉尼大学的博士,然而专业不是计算机,而是音乐学,很难想象Rod Johnso的学历。
-
Srping的理念:使现有技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架!
SSH:Struct2 + Spring + Hibernate
SSM:SpringMVC +Spring + MyBatis
官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-p-namespace
下载地址:https://repo.spring.io/release/org/springframework/spring/
GitHub:https://github.com/spring-projects/spring-framework/releases/tag/v5.2.13.RELEASE
maven:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.3</version>
</dependency>
1.2、优点
- Spring是一个开源的免费的框架(容器)!
- Spring是一个轻量级的(导包即用)、非入侵式的(导包后不改变现有项目)框架!
- 控制反转(IOC)、面向切面(AOP)
- 支持事务的处理,对框架整合的支持!
总的来说:Spring就是一个轻量级的控制反转(IOC)和面向切面(AOP)的框架!
1.3、组成
1.4、拓展
现代化的Java开发,就是基于Spring的开发!!
- SpringBoot:一个快速开发的脚手架,基于SpringBoot能快速开发单个微服务,和maven一样也是约定大于配置。
- SpringCloud:基于SpringBoot实现的,用于协调。
现在大多数公司都在使用SpringBoot开发,而学习SpringBoot的前提,需要完全掌握Spring和SpringMVC。
Spring配置十分繁琐,违背了原本的理念,人称"配置地狱",直到SpringBoot,配置简化。
2、IOC理论推导
IOC原型
考虑下面情形,当我们业务层实现类ServiceImpl调用DAO层时,如果用户需求发生改变,一开始是默认调用,后面用MySQL,再后面用Oracle,那么我们就会不断修改代码,这显然是不合理的。
//---------DAO层------
public interface UserDao {
public void getUser();
}
public class UserDaoImpl implements UserDao {
public void getUser() {
System.out.println("默认的getUser");
}
}
public class UserDaoMySQLImpl implements UserDao {
public void getUser() {
System.out.println("MySQL的getUser");
}
}
public class UserDaoOracle implements UserDao {
public void getUser() {
System.out.println("Oracle的getUser");
}
}
//--------service层----------
public interface UserService {
public void getUser();
}
public class UserServiceImpl implements UserService {
//这部分会不断修改
private UserDao userDao = new UserDaoImpl();
//private UserDao userDao = new UserDaoMySQLImpl();
//private UserDao userDao = new UserDaoOracle();
public void getUser() {
userDao.getUser();
}
}
改进方法:我们通过set来对UserDao接口进行注入。
public class UserServiceImpl implements UserService {
//这部分会不断修改
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void getUser() {
userDao.getUser();
}
}
革命性变化
- 之前,程序是主动创建对象,控制权在程序员手上!
- 使用set注入后,程序不再具有主动性,而是变成被动的接受对象。
- 控制权从程序员手上到用户手上,这就是控制反转。
- 这种思想,从本质上解决了问题,使得程序员不再去管理对象的创建,系统的耦合性大大降低,可以更加专注在业务的实现上,这就是IOC的原型。
IOC本质
控制反转是一种通过XML(或注解)并通过第三方去生产或获取特定对象的方式,在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
控制反转(Inversion of Control)是一种设计思想,依赖注入(DI)是实现IoC的一种方式,所谓控制反转就是获得依赖对象的方式反转了。
我们采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
3、Hello Spring
1、第一个Spring程序
项目结构:
1、创建实体类Hello.java
package com.zcy.pojo;
public class Hello {
private String str;
public void setStr(String str) {
this.str = str;
}
public String getStr() {
return str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
2、在resources目录下新建一个Spring配置文件,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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
现在beans.xml就是一个容器,存放各种对象
这里控制反转,由Spring帮我们创建对象,而不是程序,对象创建只需要通过配置文件操作
bean标签相当于帮我们创建了对象,等价于
Hello hello = new Hello(),id就是变量名,class是对象类型
property标签相当于帮我们给hello对象的属性 str 赋值为 Hello World
-->
<bean id="hello" class="com.zcy.pojo.Hello">
<property name="str" value="Hello World"/>
</bean>
</beans>
3、测试
import com.zcy.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
//加载配置文件,拿到容器
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//这时没有new,对象已经被Spring创建好了,
//由Spring托管的类,都可以通过这种方式获取Spring创建好的对象
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.getStr());
}
}
结果:
当我们的类由Spring托管时,左边看得到叶子标记
2、修改之前IOC原型
添加一个Spring配置文件,将三个DAO实现类和一个Service实现类交给Spring。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 三个DAO实现类,它们都没成员变量,无需property标签 -->
<bean id="userDaoImpl" class="com.zcy.dao.UserDaoImpl"/>
<bean id="userDaoMySQLImpl" class="com.zcy.dao.UserDaoMySQLImpl"/>
<bean id="userDaoOracle" class="com.zcy.dao.UserDaoOracle"/>
<!-- service的实现类,有一个UserDao成员变量,需要property标签 -->
<bean id="userServiceImpl" class="com.zcy.service.UserServiceImpl">
<!-- 用ref引用Spring已经托管对象,以后userServiceImpl修改成员只需在配置文件改 -->
<property name="userDao" ref="userDaoImpl"/>
</bean>
</beans>
如果有不同需求,只需修改配置文件,而测试代码永远不变!
import com.zcy.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestIOC {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl");
userServiceImpl.getUser();
}
}
3、思考问题?
- Hello对象是谁创建的?hello对象在我们获得容器时就被Spring创建。
- Hello对象的属性怎么设置?由Spring容器(beans.xml)设置
这个过程就叫控制反转:
- 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制的,使用Spring后,对象是由Spring来创建的;
- 反转:程序本身不创建对象,而变为被动的接收对象;
- 依赖注入:就是利用set方法来注入的(如果我们去掉Hello中的set方法,Spring就无法生效)
- IOC是一种编程思想,由主动的编程变为被动的接收
OK,到了现在,我们彻底不用再去程序中改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,一句话,就是对象由Spring创建、管理、分配!
4、IOC创建对象的方式
1、默认使用无参构造创建对象!
2、使用有参构造:
//自己写好对应的set\get,有参构造、无参构造,下面三个注解是Lombok,不用写
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
private String password;
}
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 方式一:通过参数名来设置 (推荐) -->
<bean id="user" class="com.zcy.pojo.User">
<constructor-arg name="name" value="小白"/>
<constructor-arg name="password" value="111111"/>
</bean>
<!-- 方式二:通过类型匹配来设置,但所有同类型都会被赋值,不推荐-->
<bean id="user" class="com.zcy.pojo.User">
<constructor-arg type="java.lang.String" value="小白"/>
</bean>
<!-- 方式三:通过下标来设置 -->
<bean id="user" class="com.zcy.pojo.User">
<constructor-arg index="0" value="小红"/>
<constructor-arg index="1" value="333333"/>
</bean>
</beans>
配置文件加载时,容器(ApplicationContext)中管理的对象就已经初始化了。
5、Spring配置说明
5.1、Alias 别名
为bean标签中id起别名
<alias name="user" alias="u1"/>
调用时
User user = (User) context.getBean("u1");
5.2、Bean的配置
id是bean的唯一标识,相当于对象名;class是bean对象对应的全限定名(包名+类型);name也是别名,而且比Alias更强大,不仅可以起多个别名,它的分隔符也很随意。
<bean id="user" class="com.zcy.pojo.User" name="u2 u3,u4;u5">
<constructor-arg name="name" value="小白"/>
<constructor-arg name="password" value="111111"/>
</bean>
5.3、import
import一般用于团队使用,它能将其他配置文件导入合并。假设现在项目由多个人开发,每个人负责不同的类,而不同的类就需要注册在不同的bean中,我们可以利用import将所有人的bean.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
</beans>
6、依赖注入
依赖注入:
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中所有属性由容器注入
6.1、构造器注入
就是前面有参构造的方法
6.2、Set方式注入(重点)
【实体类】
1、复杂类型
public class Address {
private String address;
...实现get/set方法
}
2、真实测试对象
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 wifi;
private Properties info;
....实现get、set方法
}
3、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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.zcy.pojo.Address">
<property name="address" value="湖南长沙"/>
</bean>
<bean id="student" class="com.zcy.pojo.Student">
<!-- 基本类型,普通注入 -->
<property name="name" value="李华"/>
<!-- 注入Spring托管的类,bean注入 -->
<property name="address" ref="address"/>
<!-- 数组注入 -->
<property name="books">
<array>
<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="111111222222223333"/>
<entry key="学生卡" value="11122223333"/>
</map>
</property>
<!-- Set注入 -->
<property name="games">
<set>
<value>英雄联盟</value>
<value>皇室战争</value>
</set>
</property>
<!-- null值注入 -->
<property name="wifi">
<null/>
</property>
<!-- Properties注入,配置文件 -->
<property name="info">
<props>
<prop key="driver">jdbc:mysql://localhost:3306/mybatis</prop>
<prop key="url">useUnicode=true&characterEncoding=UTF-8&useSSL=false</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
</beans>
6.3、扩展方式注入
需要导入xml约束(放在最上面的beans标签里)
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
p命名空间等价于property,c命名空间等价于构造器注入,但主要还是用前面Set注入。
<!-- p 命名空间注入-->
<bean id="student3" class="com.zcy.pojo.Student"
p:name="小白" p:address-ref="address"/>
<!-- c 命名空间注入 -->
<bean id="address2" class="com.zcy.pojo.Address"
c:address="北京"/>
6.4、Bean的作用域
1、singleton(默认):单例模式,每次从容器中get到的Bean都是同一个对象
<bean id="address" class="com.zcy.pojo.Address" scope="singleton">
<property name="address" value="湖南长沙"/>
</bean>
2、prototype:原型模式,每次从容器中get到的Bean都不是同一个对象
<bean id="address" class="com.zcy.pojo.Address" scope="prototype">
<property name="address" value="湖南长沙"/>
</bean>
3、request、session、application等都是Web开发才用
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Address address1 = context.getBean("address", Address.class);
Address address2 = context.getBean("address", Address.class);
System.out.println(address1 == address2);
//单例结果为true,原型结果为false
}
7、Bean的自动装配
- 自动装配是Spring满足Bean依赖的一种方式
- Spring会在上下文中自动寻找,并自动给Bean装配属性!
Spring有三种装配方式:
- xml装配,就是之前的那些方式。
- java装配,比较麻烦。
- 隐式自动装配【重要】
7.1、ByName 自动装配
设置自动装配autowire属性为byName后,people1会在容器中上下文查找和自己对象set方法后面的值对应的Bean id。
通俗的说,就是会根据自己setCat和setDog方法,找ID为cat和dog的Bean。因此需要保证所有Bean的id唯一,并且这个Bean要和自动注入的属性的set方法名一致!(例如 setCat233就无法找到Cat)
<bean id="cat" class="com.zcy.pojo.Cat"/>
<bean id="dog" class="com.zcy.pojo.Dog"/>
<bean id="people1" class="com.zcy.pojo.People" autowire="byName"/>
7.2、ByType 自动装配
设置自动装配autowire属性为byType后,people2会在容器中上下文查找和自己对象属性类型相同的Bean。
通俗的说,就是会找class为Cat和Dog的Bean。因此需要保证所有Bean的class唯一,这个Bean要和自动注入的属性的类型一致。
<bean id="cat" class="com.zcy.pojo.Cat"/>
<bean id="dog" class="com.zcy.pojo.Dog"/>
<bean id="people1" class="com.zcy.pojo.People" autowire="byName"/>
7.3、注解实现自动装配
需要先导入context命名空间以及注解标签:
增加的内容:
xmlns:context="http://www.springframework.org/schema/context"
这两行是在引号内的
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
<context:annotation-config/>
完整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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="cat" class="com.zcy.pojo.Cat"/>
<bean id="dog1" class="com.zcy.pojo.Dog"/>
<bean id="dog2" class="com.zcy.pojo.Dog"/>
<bean id="people" class="com.zcy.pojo.People"/>
</beans>
在People类上使用注解:
public class People {
@Autowired(required = false)
private Cat cat;
@Autowired
@Qualifier("dog1")
private Dog dog;
public void setDog(@Nullable Dog dog) {
this.dog = dog;
}
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
}
-
注解@Autowired:等同于bean中设置属性 autowire=byType,即默认按类型自动装配。该注解的参数required = false,表明该变量可以为null值,等价于在set方法的参数里添加注解@Nullable。
-
注解@Autowired可以写在成员变量或者set方法上,如果写在成员变量上,则可以省略对应的set方法。
-
注解@Qualifier("")常搭配@Autowired,由于@Autowired是默认按类型查找,当出现相同class时,用@Qualifier("")指定bean的id(相当于按类型查找失败,就按名字查找)
-
@Resource是Java内置的注解[但依然需要导入context],是@Autowired和@Qualifier的结合,它先通过名字查再通过类型查。但我们一般还是用的@Autowired。
8、使用注解开发
在Spring4之后,要使用注解开发,首先必须要保证AOP的包导入了,其次要导入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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 加载注解驱动,写了这个才能用注解(针对所有范围) -->
<context:annotation-config/>
<!-- 指定要扫描的包,这个包下的注解才会生效 -->
<context:component-scan base-package="com.zcy.pojo"/>
</beans>
1、Bean
放在类上
//这里可以看成等价于<bean id="user" class="com.zcy.pojo.User"/>
//为了让Spring扫描,以便注册该bean
@Component
@Component的衍生:我们在Web开发中,常按照MVC三层架构进行分层,所以在不同层@Component的名字不一样,但它们效果是相同的,都是代表将某个类注册到Spring中,装配Bean。
- Dao层,用@Repository
- Service层,用@Service
- Controller层,用@Controller
2、属性赋值
在属性上加@Value(“江小白”),但一般也就给普通类型赋值,它无法引用其他类。
@Component
@Scope("singleton")
public class User {
@Value("江小白")
private String userName;
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserName() {
return userName;
}
}
3、自动装配
- @AutoWired:通过类型自动装配,如果AutoWired不能识别,需要加上@Qualifier(value=“xxx”)
- @Nullable:方法的参数前加这个注解,表明可以为null
- @Resource:自动先通过名字,再通过类型查找。
4、作用域
@Scope("singleton")//在类上写
5、小结
xml与注解:
-
xml更加万能,适用于各种场合,维护简单方便!
-
注解,无法引用其他类,维护相对复杂。
-
xml与注解的实践:
-
xml用来管理Bean,里面就写Bean标签最好。
-
注解只负责完成属性注入,但碰到复杂的属性最好也都在xml完成。
-
使用过程中一定要注意加入下面代码才能使用注解!!
<!-- 加载注解驱动,写了这个才能用注解(针对所有范围) --> <context:annotation-config/> <!-- 可选项:指定要扫描的包,这个包下的注解就会生效 --> <context:component-scan base-package="com.zcy"/>
-
9、使用Java方式配置Spring
我们现在可以完全不适用Spring的XML配置,全权交给Java来做。
JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能!
在SpringBoot中,这种纯Java方式配置随处可见!
实体类 User.java
@Component//这个注解表明,该类由Spring接管,注册到容器中
public class User {
@Value("小明")//属性注入
private String name;
...get/set方法
}
配置类 MyConfig.java
//@Configuration本质也是@Component,由Spring接管
//同时@Configuration还表明当前类替代了配置文件beans.xml
@Configuration
//扫描包,等价于<context:component-scan base-package="com.zcy"/>
@ComponentScan("com.zcy")
@Import(MyConfig2.class)//导入其他配置类
public class MyConfig {
//注册一个bean,相当于之前我们写的bean标签
//方法名就是id属性,返回值就是class属性
@Bean
public User getUser(){
return new User();//返回的对象就被注入到bean中
}
}
测试类 ConfigTest.java
public class ConfigTest {
@Test
public void test(){
// 如果完全使用配置类去做,我们就不用ClassPathXmlApplicationContext(加载配置文件),
// 而用AnnotationConfigApplicationContext(加载配置类)
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
//这里的getUser就是相当于id属性
User user = context.getBean("getUser", User.class);
System.out.println(user);
}
}