Spring
SSM
S-Spring------------------------>WEB+Service+DAO
S-SpringMVC------------------>WEB层
M-Mybatis--------------------->DAO(数据持久层)
Spring的特点
分层:
JavaSE/JavaEE
full-stack:每一层
轻量级
开源
Spring架构:
优势:
解耦合,简化开发
方便集成其余优秀框架
AOP(面向切面编程)的支持
声明事务的支持
Spring两大核心思想
IoC(Inverse of Control)控制反转
AOP(Aspect Oriented Programming)面向切面编程
IoC
现状:强耦合
描述:service业务层需要使用dao层的方法时,要通过new一个dao对象的形式,才能调用dao层的方法。如果dao层发生改变时,需要修改源码(service层的代码),耦合性过于紧密。
解决方案:
在service层与dao层之间添加一个factory,通过factory获取dao层的对象,断开了service层与dao层的耦合。
隐患:
虽然断开了service层和dao层的耦合,但是增加了factory和dao层之间的耦合,拆东墙补西墙。如果dao发生了改变,变成了dao1,仍然需要修改factory内的代码。
解决方案:
factory通过读取配置文件的方式,获取是哪个dao,如果dao发生改变,可以直接修改配置文件而不需要修改源码,从而达到解耦的目的
Spring入门(QuickStart)
1.maven项目配置依赖坐标
注意:如果本地仓库没有依赖,且没有连接网络的情况下,配置私服(前提是当前局域网内有私服供你获取),通过私服获取,一定要先配置私服再去配置依赖坐标,顺序反了会报错。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>Spring01</artifactId>
<version>1.0-SNAPSHOT</version>
<repositories>
<repository>
<!-- 依赖仓库id,不可重复。repositories可以配置多个仓库,如果ID重复后面配置会覆盖之前的配置 -->
<id>dependencies_Repositories</id>
<!-- 私服仓库地址,即nexus仓库组的地址 -->
<url>http://192.168.14.240:8081/repository/maven-public/</url>
<!-- 是否下载releases版本构件 -->
<releases>
<enabled>true</enabled>
</releases>
<!-- 是否下载snapshots版本构件 -->
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<!-- 配置插件从私服下载 -->
<pluginRepositories>
<pluginRepository>
<id>plugins_Repositories</id>
<url>http://192.168.14.240:8081/repository/maven-public/</url>
<!-- 是否下载release版本构件 -->
<releases>
<enabled>true</enabled>
</releases>
<!-- 是否下载snapshots版本构件 -->
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
</dependencies>
</project>
2.编写service的接口及实现类,
service接口:
package com.itheima.service;
public interface UserService {
void save();
}
service接口的实现类:
package com.itheima.service.impl;
import com.itheima.service.UserService;
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("runing...");
}
}
3.进行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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--创建spring控制的资源-->
<bean class="com.itheima.service.impl.UserServiceImpl" id="userService"></bean>
</beans>
4.编写测试代码
import com.itheima.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserApp {
public static void main(String[] args) {
//加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//获取资源
UserService userService = (UserService) ctx.getBean("userService");
userService.save();
}
}
最终控制台测试结果:
流程梳理:
Spring配置文件中bean标签下的属性
id
class
scope
name
init-method
destroy-method
factory-bean
factory-method
bean标签是用来定义Spring中的资源的
id:该bean的唯一标识,同一个Spring容器中不允许重复。后续可以通过该id进行获取对象。
class:全限定类名,用于反射创建对象。bean的类型
name:把Bean装配进Spring容器,bean可以定义多个名称,使用name属性完成,中间使用,分割。
<?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">
<!--创建spring控制的资源-->
<bean class="com.itheima.service.impl.UserServiceImpl" id="userService" name="xxx"></bean>
</beans>
修改测试代码将之前的userService修改为配置文件中的name值,进行测试
import com.itheima.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserApp {
public static void main(String[] args) {
//加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("ApplicationContext.xml");
// UserService userService = (UserService) ctx.getBean("userService");
UserService userService = (UserService) ctx.getBean("xxx");
userService.save();
}
}
测试结果:仍然可以正常运行
如果配置文件中并没有配置name属性,java测试代码中通过一个不存在的name值进行获取对象,会报错,报错信息如下:
scope属性:用来控制是单例模式还是多例模式,不写的话是有默认配置的,默认为singleton单例模式,一般也是使用默认配置。
该属性有两个取值:singleton(单例)和prototype(多例)。
spring配置文件信息(如果bean中没有写scope属性默认为singleton:单例模式)
<?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 class="com.itheima.domain.User" id="user"/>
<bean class="com.itheima.domain.Book" id="book" scope="prototype"/>
</beans>
单例模式singleton情况下,同一个项目中最多只有一个实例。
多例模式prototype情况下,同一个项目可以有 不止一个实例。
单例和多例测试代码
public void test01() {
app = new ClassPathXmlApplicationContext("beans-scope.xml");
User user = (User) app.getBean("user");
User user2 = (User) app.getBean("user");
System.out.println("user2 = " + user2);
System.out.println("user = " + user);
Object book = app.getBean("book");
Object book2 = app.getBean("book");
System.out.println("book = " + book);
System.out.println("book2 = " + book2);
}
运行结果:
解析:User是单例模式,最多只有一个实例,所以,两个对象的实例的内存地址相同
book为多例模式,每次获取到的实例都不相同,所以内存地址不同,即不是同一个实例。
单例模式只初始化了一次,user为默认单例模式
多例模式会初始化多次:book为多例模式
测试代码:
@Test
public void test05(){
app=new ClassPathXmlApplicationContext("beans-scope.xml");
User user = (User) app.getBean("user");
Book book1 = (Book) app.getBean("book");
Book book2 = (Book) app.getBean("book");
}
单例模式会在加载Spring配置文件的时候就初始化成功,见图:
此时并未初始化多例。
lazy-init:属性。默认值为false
单例模式下,会默认在加载配置文件的时候就已经初始化成功了,如果不想在刚刚加载配置文件时就初始化,而在需要使用的时候再进行初始化,需要开启懒加载。
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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.itheima.domain.User" id="user" lazy-init="true"/>
</beans>
懒加载默认值为false,即不开启懒加载,加载配置文件时就进行初始化。
多例模式,不会在加载配置文件的时候就进行初始化,所以懒加载对于多例模式无意义。即使设置懒加载为false仍然不会再加载配置文件的时候进行初始化。
测试代码:
@Test
public void test02() {
app = new ClassPathXmlApplicationContext("beans-lazy.xml");
Book book = (Book) app.getBean("book");
User user = (User) app.getBean("user");
User user1 = (User) app.getBean("user");
}
当前结论:Spring容器在创建的时候,只会初始化并装配所有非懒加载的单例Bean。
depends-on属性:指定顺序创建对象
目前user、car和book均为单例。默认情况下,初始化顺序为配置文件中的配置顺序。
<?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 class="com.itheima.domain.User" id="user"/>
<bean class="com.itheima.domain.Car" id="car"/>
<bean class="com.itheima.domain.Book" id="book"/>
</beans>
测试结果:
如果想要指定初始化顺序需要在bean标签上添加 一个属性depends-on。depends-on里面的顺序即要在初始化该bean之前需要 完成的初始化bean,且按照depends-on内的书写顺序完成初始化。
<?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 class="com.itheima.domain.Car" id="car" depends-on="user,book"/>
<bean class="com.itheima.domain.User" id="user"/>
<bean class="com.itheima.domain.Book" id="book"/>
</beans>
测试结果:按照指定的顺序进行初始化
init-method&destroy-mehtod属性(bean的声明周期)
init即初始化,destroy为销毁
package com.itheima.domain;
public class User {
public User(){
System.out.println("User创建成功……");
}
public void regist(){
System.out.println("User已经注册成功,身份证号是:xxxxxxxxxxx");
}
public void unRegist(){
System.out.println("User已经逝世,正在进行常规销毁工作");
}
}
init-method为初始化方法,方法名任意,但是需要在对应的类中存在,且不能带参数。
<?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 class="com.itheima.domain.User" id="user" init-method="regist" destroy-method="unRegist"/>
</beans>
测试代码:
@Test
public void test03() {
ApplicationContext app = new ClassPathXmlApplicationContext("beans-init-destory.xml");
Object user = app.getBean("user");
// ((ClassPathXmlApplicationContext) app).close();
}
运行结果:
执行完成之后,并未出现destroy方法执行。
原因:destroy方法在执行的时候jvm虚拟机已经退出了,未及时释放资源。并没有看到destroy结果。
如果想要看到效果,需要调用close方法,进行资源的释放。
测试代码:
@Test
public void test03() {
ApplicationContext app = new ClassPathXmlApplicationContext("beans-init-destory.xml");
Object user = app.getBean("user");
((ClassPathXmlApplicationContext) app).close();
}
测试结果:
测试多例模式下的生命周期
package com.itheima.domain;
public class Book {
public Book() {
System.out.println("Book创建成功……");
}
public void init(){
System.out.println("初始化中...");
}
public void destroy(){
System.out.println("摧毁中...");
}
}
Spring配置文件:Book为多例模式,且配有init和destroy方法
<?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 class="com.itheima.domain.Book" id="book" scope="prototype" init-method="init" destroy-method="destroy" />
</beans>
测试代码
//测试多例声明周期
@Test
public void test07() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans-init-destory.xml");
Book book = (Book) ctx.getBean("book");
ctx.close();
}
运行的测试结果
结果发现只是初始化成功了,即spring容器提供给了实例,但是并未销毁,说明多例模式下的销毁并不归属于Spring容器进行管理。
factory-bean & factory-method
传统方式获取一个对象
package com.itheima.domain;
public class Car {
private String Chassis; //底盘
private String engine; //发动机
private String gearbox; //变速箱
public Car() {
System.out.println("汽车创建成功……");
}
public void setChassis(String chassis) {
Chassis = chassis;
System.out.println("底盘装配成功……");
}
public void setEngine(String engine) {
this.engine = engine;
System.out.println("发动机装配成功……");
}
public void setGearbox(String gearbox) {
this.gearbox = gearbox;
System.out.println("变速箱装配成功……");
}
public void testChassis() {
System.out.println("底盘测试成功……");
}
public void testEngine() {
System.out.println("发动机测试成功……");
}
public void testGearbox() {
System.out.println("变速箱测试成功……");
}
public String getChassis() {
return Chassis;
}
public String getEngine() {
return engine;
}
public String getGearbox() {
return gearbox;
}
}
方式一:自己new一个
测试代码:
@Test
public void test01(){
//Car car = new Car();
//System.out.println("car = " + car); //car = com.itheima.domain.Car@694f9431
Car c = new Car();
c.setChassis("xxx");
c.setEngine("yyy");
c.setGearbox("zzz");
c.testChassis();
c.testEngine();
c.testGearbox();
}
测试结果
方式二:通过工厂获取,将复杂的代码交给工厂来完成
创建对象的过程较为复杂的时候,可以通过创建工厂对象的形式进行。
工厂代码
package com.itheima.factory;
import com.itheima.domain.Car;
public class CarFactory {
public Car getCar(){
Car c = new Car();
c.setChassis("xxx");
c.setEngine("yyy");
c.setGearbox("zzz");
c.testChassis();
c.testEngine();
c.testGearbox();
return c;
}
}
测试代码
@Test
public void test02(){
CarFactory cf = new CarFactory();
Car car = cf.getCar();
System.out.println("car = " + car); //car = com.itheima.domain.Car@694f9431
}
测试结果
也可以完成。
方式三:静态工厂
获取对象的方法改为静态,就可以不用new一个工厂对象了
package com.itheima.factory;
import com.itheima.domain.Car;
public class StaticCarFactory {
public static Car getCar(){
Car c = new Car();
c.setChassis("xxx");
c.setEngine("yyy");
c.setGearbox("zzz");
c.testChassis();
c.testEngine();
c.testGearbox();
return c;
}
}
测试代码
@Test
public void test03(){
Car car = StaticCarFactory.getCar();
}
运行结果
Spring创建对象
工厂类方式创建(静态工厂)
工厂代码:
package com.itheima.factory;
import com.itheima.domain.Book;
public class BookFactory {
public static Book getBook(){
return new Book();
}
}
Book类
package com.itheima.domain;
public class Book {
public Book() {
System.out.println("Book对象创建了");
}
public void show(){
System.out.println("reading...");
}
}
Spring 配置文件中配置
<bean class="com.itheima.factory.BookFactory" id="book" factory-method="getBook"/>
测试代码
@Test
public void test05() {
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
Book book = (Book) app.getBean("book");
book.show();
}
测试结果
实例化工厂
Spring配置文件中的配置
<bean class="com.itheima.factory.BookFactory2" id="bookFactory2"/>
<bean id="book" factory-method="getBook" factory-bean="bookFactory2"/>
实例化工厂
package com.itheima.factory;
import com.itheima.domain.Book;
public class BookFactory2 {
public Book getBook() {
return new Book();
}
}
测试代码
@Test
public void test06(){
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
Book book = (Book) app.getBean("book");
book.show();
}
测试结果
结论:Spring中也可以通过实例工厂/静态工厂获取对象。
IoC思想总结:
IoC控制反转
控制反转主要是反转对象的创建权
把对象的创建权从程序员自己new的方式,反转成了spring读取配置文件并创建的方式。
我们如果需要使用,直接找Spring容器获取即可。
DI依赖注入 DI(Dependency Injection)
IoC与DI
应用程序想要运行需要依赖资源,而资源在IoC容器中,由IoC进行提供,IoC将资源提供给应用程序的过程称之为注入。这是同一件事情站在不同的角度看待的。
站在应用程序的角度来讲:是等待Spring容器注入对应的资源。
站在Spring容器的角度来讲:是将资源注入到应用程序中。
依赖注入可以理解成IoC
的一种应用场景,反转的是对象间依赖关系维护权
。DI依赖注入,只是IoC在某个方面的一个具体实现,在依赖关系维护方面的一个实现。
依赖注入的方式:
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">
<bean class="com.itheima.dao.impl.UserDaoImpl" id="userDao"/>
<bean class="com.itheima.service.impl.UserServiceImpl" id="userService"/>
</beans>
service
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.domain.User;
import com.itheima.service.UserService;
import java.util.List;
public class UserServiceImpl implements UserService {
UserDao userDao;
public List<User> getAllUser() {
return userDao.getAllUser();
}
public User getUser(Integer id) {
return userDao.getUser(id);
}
}
userDao
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
import com.itheima.domain.User;
import java.util.ArrayList;
import java.util.List;
public class UserDaoImpl implements UserDao {
/**
* 查询所有用户
* @return 包含所有用户的集合
*/
public List<User> getAllUser(){
System.out.println("查询到所有用户……mysql数据库……");
return new ArrayList<User>();
}
/**
* 根据ID查询用户
* @param id 用户id
* @return 要查找的用户
*/
public User getUser(Integer id){
System.out.println("根据ID查询用户……mysql数据库……");
return new User();
}
}
测试程序:
public class DITest {
// 需求1:获取UserService实现类实例,调用器getAllUser方法,运行看会出现什么问题?
@Test
public void test01() {
ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) app.getBean("userService");
userService.getAllUser();
}
userService需要依赖userDao,但是userService在使用的时候,userDao还未完成初始化,此时的userDao无值为null,会引发空指针异常。
运行报错信息:
所以,要能正常运行,必须先完成userDao的初始化操作。
setter方式注入:
注意点:setter注入是需要访问当前bean的空参构造的,如果没有空参构造会报错。
报错信息
1.要先保证要注入的bean和使用的bean已经装配到了spring容器中
2.在需要使用注入的类中添加要注入的bean的属性及setter方法。
3.通过在bean标签中的添加property子标签完成注入
添加setter方法和属性
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.domain.User;
import com.itheima.service.UserService;
import java.util.List;
public class UserServiceImpl implements UserService {
UserDao userDao;
public List<User> getAllUser() {
return userDao.getAllUser();
}
public User getUser(Integer id) {
return userDao.getUser(id);
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.itheima.dao.impl.UserDaoImpl" id="userDao"/>
<bean class="com.itheima.service.impl.UserServiceImpl" id="userService">
<property name="userDao" ref="userDao"/>
</bean>
</beans>
测试代码
@Test
public void test01() {
ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) app.getBean("userService");
userService.getAllUser();
}
测试结果:成功
简化写法:P标签命名空间
<?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 class="com.itheima.dao.impl.UserDaoImpl" id="userDao"/>
<bean class="com.itheima.service.impl.UserServiceImpl" id="userService" p:userDao-ref="userDao"></bean>
</beans>
建议使用property子标签的形式。
构造器注入
Spring配置文件
<bean class="com.itheima.dao.impl.UserDaoImpl" id="userDao"/>
<bean class="com.itheima.service.impl.UserServiceImpl" id="service" >
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
<constructor-arg name="num" value="111"></constructor-arg>
<constructor-arg name="version" value="6"></constructor-arg>
</bean>
接口
package com.itheima.service;
import com.itheima.domain.User;
import java.util.List;
public interface UserService {
public List<User> getAllUser();
public User getUser(Integer id);
public void show();
}
实现类
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.domain.User;
import com.itheima.service.UserService;
import java.util.List;
public class UserServiceImpl implements UserService {
UserDao userDao;
int num;
String version;
public UserServiceImpl(UserDao userDao, int num, String version) {
this.userDao = userDao;
this.num = num;
this.version = version;
}
public List<User> getAllUser() {
return userDao.getAllUser();
}
public User getUser(Integer id) {
return userDao.getUser(id);
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void show(){
System.out.println(userDao+"..."+num+"..."+version);
}
}
测试代码
@Test
public void test04(){
ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
//UserServiceImpl service = (UserServiceImpl) app.getBean("service");
UserService service = (UserService) app.getBean("service");
service.show();
}