spring(一):概述与基于XML的IOC配置
一、spring概述
1.1 spring是什么
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 **IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)**为内核,提供了展现层 SpringMVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
1.2 spring优势
- 方便解耦,简化开发
- AOP编程的支持
- 声明式事务的支持
- 方便程序的测试
- 方便集成各种框架
- 降低JavaEE API的使用难度
1.3 spring体系结构
二、IoC的概念和作用
2.1 程序耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。
划分模块的一个准则就是高内聚低耦合。
耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
我们在开发中,有些依赖关系是必须的,有些依赖关系可以通过优化代码来解除的
在一个经典的JDBC例子中,注册驱动有两种实现方式:
//第一种方式 DriverManager.registerDriver(new com.mysql.jdbc.Driver()); //第二种方式 Class.forName("com.mysql.cj.jdbc.Driver");
通常我们使用第二种,而不使用第一种,原因是我们的类依赖了数据库的具体驱动类(MySQL) ,如果这时候更换了数据库品牌(比如 Oracle) ,需要修改源码来重新数据库驱动。这显然不是我们想要的。
/** * @author konley * @date 2020-08-15 18:30 * 程序的耦合 */ public class JdbcDemo { public static void main(String[] args) throws SQLException, ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone = GMT"; String name = "root"; String psw = "123456"; Connection conn = DriverManager.getConnection(url,name,psw); PreparedStatement pstm = conn.prepareStatement("select * from account"); ResultSet rs = pstm.executeQuery(); while (rs.next()){ System.out.println(rs.getString("name")); } rs.close(); pstm.close(); conn.close(); } }
2.2 解决耦合
我们通常使用反射来注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
此处的参数只是一个字符串,而不是new一个类
好处是,我们的类中不再依赖具体的驱动类,此时就算删除 mysql 的驱动 jar 包,依然可以编译(不可以运行),同时也产生了一个问题是,mysql驱动的字符串已经写死了,解决这个问题通常使用配置文件配置
2.3 工厂模式解耦
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候, 让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
读取配置文件,创建和获取三层对象的类就是工厂
2.4 工厂模式代码实现
目录结构:
第一步 创建配置文件 bean.properties,存放类名和全限定类名信息
accountService=service.impl.AccountServiceImpl accountDao=dao.impl.AccountDaoImpl
第二步 创建工厂,factory类
/** * @author konley * @date 2020-08-15 21:21 * 一个创建bean对象的工厂:创建service和dao * bean : 可重用组件 * javabean :用java编写的可重用组件 不等于实体类,远大于实体类 */ public class BeanFactory { //description: 定义properties对象 private static Properties properties; //description: 定义一个map,用于存放我们要创建的对象,我们把它称之为容器 private static Map<String,Object> beans; //使用静态代码块为properties对象赋值 static { try { //实例化对象 properties = new Properties(); //获取properties文件的流 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); properties.load(in); //实例化容器 beans = new HashMap<String, Object>(); //取出配置文件中所有的key Enumeration<Object> keys = properties.keys(); //遍历枚举 while (keys.hasMoreElements()){ //取出每个key String key = keys.nextElement().toString(); //根据key获取value String beanPath = properties.getProperty(key); //反射创建对象 Object value = Class.forName(beanPath).newInstance(); //将对象存入容器 beans.put(key,value); } } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } } /** * description: 改造后的getBean方法 * @param beanName * @return java.lang.Object */ public static Object getBean(String beanName){ return beans.get(beanName); } /* public static Object getBean(String beanName) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Object bean = null; String beanPath = properties.getProperty(beanName); //newInstance每次都会创建一个新对象,这不是我们想要的 bean = Class.forName(beanPath).newInstance(); return bean; } */ }
第三步 创建dao接口以及其实现类
package dao; /** * @author konley * @date 2020-08-15 20:58 * 账户的持久层接口 */ public interface IAccountDao { /** * description: 模拟保存账户 * @param * @return void */ void saveAccount(); }
package dao.impl; import dao.IAccountDao; /** * @author konley * @date 2020-08-15 21:00 */ public class AccountDaoImpl implements IAccountDao { @Override public void saveAccount() { System.out.println("账户保存了"); } }
第四步 创建service接口以及其实现类
package service; /** * @author konley * @date 2020-08-15 20:54 * 操作账户业务层接口 */ public interface IAccountservice { /** * description: 模拟保存账户 * @param * @return void */ void saveAccount(); }
package service.impl; import dao.IAccountDao; import dao.impl.AccountDaoImpl; import factory.BeanFactory; import service.IAccountservice; /** * @author konley * @date 2020-08-15 20:56 * 账户业务层实现类 */ public class AccountServiceImpl implements IAccountservice { //private IAccountDao accountDao = new AccountDaoImpl(); private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao"); public AccountServiceImpl() throws IllegalAccessException, InstantiationException, ClassNotFoundException { } @Override public void saveAccount() { accountDao.saveAccount(); } }
注意,此时有了工厂后已经不需要使用new方法来创建dao对象,而是通过工厂,工厂读取配置文件后给我们一个对象
第五步 创建测试类
package ui; import factory.BeanFactory; import service.IAccountservice; import service.impl.AccountServiceImpl; /** * @author konley * @date 2020-08-15 21:10 * 模拟表现层,用于调用业务层(类似servlet) */ public class Client { public static void main(String[] args) throws Exception { //IAccountservice as = new AccountServiceImpl(); IAccountservice as = (IAccountservice) BeanFactory.getBean("accountService"); as.saveAccount(); } }
同理,此时不需要new方法创建service,而是通过工厂直接拿到,这就是工厂模式的代码实现思路
2.5 控制反转IoC
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
容器:在应用加载时创建一个Map,用于存放三层对象
**工厂:负责给我们从容器中获取指定对象的类 **
三、spring的IoC解耦
在2.4的部分,我们实现了一个简单的IoC解耦代码,而这里要使用的就是spring为我们提供的解耦,可以更加快捷的实现。
3.1 spring的环境搭建
项目目录:
使用maven工程创建项目,pom.xml如下
<?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>org.example</groupId> <artifactId>spring03spring</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> </dependencies> </project>
3.2 创建业务层service接口和实现类
package service; /** * @author konley * @date 2020-08-15 20:54 * 操作账户业务层接口 */ public interface IAccountservice { /** * description: 模拟保存账户 * @param * @return void */ void saveAccount(); }
package service.impl; import dao.IAccountDao; import dao.impl.AccountDaoImpl; import service.IAccountservice; /** * @author konley * @date 2020-08-15 20:56 * 账户业务层实现类 */ public class AccountServiceImpl implements IAccountservice { //此处有耦合,后期解决 private IAccountDao accountDao = new AccountDaoImpl(); @Override public void saveAccount() { accountDao.saveAccount(); } }
3.2 创建持久层dao接口和实现类
package dao; /** * @author konley * @date 2020-08-15 20:58 * 账户的持久层接口 */ public interface IAccountDao { /** * description: 模拟保存账户 * @param * @return void */ void saveAccount(); }
package dao.impl; import dao.IAccountDao; /** * @author konley * @date 2020-08-15 21:00 */ public class AccountDaoImpl implements IAccountDao { @Override public void saveAccount() { System.out.println("账户保存了"); } }
3.3 创建spring配置文件 beans.xml
此时spring将接管对象的创建
<bean>
标签:用于配置让spring创建对象,并且存入ioc容器中- id 属性 :对象的唯一标识符,相当于前面案例中properties的key
- class 属性 :对象的全限定类名
<?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 id="accountService" class="service.impl.AccountServiceImpl"></bean> <bean id="accountDao" class="dao.impl.AccountDaoImpl"></bean> </beans>
3.4 创建测试类
package ui; import dao.IAccountDao; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import service.IAccountservice; import service.impl.AccountServiceImpl; /** * @author konley * @date 2020-08-15 21:10 * 模拟表现层,用于调用业务层(servlet) */ public class Client { public static void main(String[] args) throws Exception { //1 根据xml文件获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2 根据id获取bean对象:有两种方式,自己强转和用带参字节码强制 IAccountservice as = (IAccountservice) ac.getBean("accountService"); IAccountDao dao = ac.getBean("accountDao",IAccountDao.class); System.out.println(as); System.out.println(dao); } }
3.5 spring中的工厂类结构
-
核心容器的两个接口
- ApplicationContext:单例对象适用,创建对象采取的策略是采用立即加载的方式,只要一读取就马上创建配置文件中配置的对象,常用
- BeanFactory:顶层接口,多例对象适用,创建对象采取的策略是延迟加载的方式,只有根据id获取对象时,才创建对象
-
ApplicationContext 接口的实现类
- ClassPathXmlApplicationContext:加载类路径下的配置文件,要求配置文件必须在类路径下,常用
- FileSystemXmlApplicationContext:加载磁盘任意路径下的路径的配置文件(需要有访问权限)
- AnnotationConfigApplicationContext:用于读取注解创建容器
四、bean标签细节
4.1 bean标签
作用
- 配置对象让spring创建
- 默认情况下调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功
属性
- id:给对象在容器中提供一个唯一标识,用于创建对象
- class:指定类的全限定类名,用于反射创建对象,默认无参构造
- scope:指定类的作用范围
- singleton:默认,单例,常用
- prototype:多例的
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围
- init-method:指定类中的初始化方法名称
- destroy-method:指定类的销毁方法名称
4.2 bean的作用范围和生命周期
单例对象:scope=“singleton”
单例对象生命周期 = 容器生命周期
1.当容器创建时,对象创建
2.只要容器还在,对象一直存活
3.容器销毁,对象销毁
多例对象:scope=“prototype”
1.当使用对象时,对象创建
2.对象只要是在使用中,就一直存活
3.当对象长时间不用时,java垃圾回收器销毁
4.3 三种创建bean的方式
第一种:默认构造函数创建,需要有默认构造函数
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
第二种:使用某个类(工厂)中的方法创建对象,并存入spring容器
创建一个工厂类:
package factory; import service.IAccountservice; import service.impl.AccountServiceImpl; /** * @author konley * @date 2020-08-16 10:50 * 模拟一个工厂类,该类存在于jar包中,我们无法通过修改源码的方式来提供默认构造函数 */ public class InstanceFactory { public IAccountservice getAccountService(){ return new AccountServiceImpl(); } }
<bean id="instanceFactory" class="factory.InstanceFactory"></bean> <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
第三种:使用某个静态类(工厂)中的方法创建对象,并存入spring容器**
创建一个静态工厂
package factory; import service.IAccountservice; import service.impl.AccountServiceImpl; /** * @author konley * @date 2020-08-16 10:50 * 模拟一个静态工厂类,该类存在于jar包中,我们无法通过修改源码的方式来提供默认构造函数 */ public class staticFactory { public static IAccountservice getAccountService(){ return new AccountServiceImpl(); } }
<bean id="accountService" class="factory.staticFactory" factory-method="getAccountService"></bean>
五、依赖注入DI
5.1 依赖注入的概念
依赖注入: Dependency Injection。 它是 spring 框架核心 ioc 的具体实现。
我们的程序在编写时, 通过控制反转, 把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。
ioc 解耦只是降低他们的依赖关系,但不会消除。 例如:我们的业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系, 在使用 spring 之后, 就让 spring 来维护了。
简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
5.2 构造函数注入
创建一个accountService类,里面有三个参数,提供构造方法
package service.impl; import service.IAccountservice; import java.util.Date; /** * @author konley * @date 2020-08-15 20:56 * 账户业务层实现类 */ public class AccountServiceImpl implements IAccountservice { //如果是经常变化的数据,并不适合注入 private String name; private Integer age; private Date birthday; //提供全参构造方法 public AccountServiceImpl(String name, Integer age, Date birthday) { this.name = name; this.age = age; this.birthday = birthday; } @Override public void saveAccount() { System.out.println("service的saveAccount方法执行了……"+name+","+age+","+birthday); } }
编写xml配置文件
<bean id="accountService" class="service.impl.AccountServiceImpl"> <constructor-arg name="name" value="小明"></constructor-arg> <constructor-arg name="age" value="19"></constructor-arg> <!--日期类型需要引用,而不能用value--> <constructor-arg name="birthday" ref="now"></constructor-arg> </bean> <!--配置一个日期对象--> <bean id="now" class="java.util.Date"></bean>
<constructor-arg>
标签说明:
- 查找参数,三选一
- type:数据类型
- index:指定参数索引赋值,从0开始
- name:给指定参数名称赋值,最常用
- 赋值或者引用值
- value:参数的值
- ref:在xml或者注解中引用spring配置过的其他bean类型
总结:构造函数注入时,必须全部参数都赋值,不能漏掉
5.3 Set注入(常用)
创建一个accountService类,里面有三个参数,不用提供构造方法,需要提供参数的set方法
package service.impl; import service.IAccountservice; import java.util.Date; /** * @author konley * @date 2020-08-15 20:56 * 账户业务层实现类 */ public class AccountServiceImpl2 implements IAccountservice { private String name; private Integer age; private Date birthday; public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public void saveAccount() { System.out.println("service的saveAccount方法执行了……"+name+","+age+","+birthday); } }
编写xml配置文件
<bean id="accountService2" class="service.impl.AccountServiceImpl2"> <property name="name" value="小军"/> <property name="age" value="21"></property> </bean>
<property>
标签说明:
- name:给指定参数名称赋值
- value:参数值
- ref:在xml或者注解中引用spring配置过的其他bean类型
总结:这种方式更加灵活且常用,比如上述可以不提供日期,不需要满参
5.4 复杂集合类型注入方法
这里以set方法注入为例
package service.impl; import service.IAccountservice; import java.lang.reflect.Array; import java.util.*; /** * @author konley * @date 2020-08-15 20:56 * 账户业务层实现类 */ public class AccountServiceImpl3 implements IAccountservice { private String[] myStrs; private List<String> myList; private Set<String> mySet; private Map<String,String> myMap; private Properties myprops; @Override public void saveAccount() { System.out.println(Arrays.toString(myStrs)); System.out.println(myList); System.out.println(mySet); System.out.println(myMap); System.out.println(myprops); } public void setMyStrs(String[] myStrs) { this.myStrs = myStrs; } public void setMyList(List<String> myList) { this.myList = myList; } public void setMySet(Set<String> mySet) { this.mySet = mySet; } public void setMyMap(Map<String, String> myMap) { this.myMap = myMap; } public void setMyprops(Properties myprops) { this.myprops = myprops; } }
编写xml配置文件
<!--复杂类的注入(使用set)--> <!-- 用于给list结构集合注入的标签 - list - array - set 用于给map结构集合注入的标签 - map - properties 同一种结构的类型,可以不区分子标签 --> <bean id="accountService3" class="service.impl.AccountServiceImpl3"> <property name="myStrs"> <array> <value>arrayA</value> <value>arrayB</value> <value>arrayC</value> </array> </property> <property name="myList"> <list> <value>ListA</value> <value>ListB</value> <value>ListC</value> </list> </property> <property name="mySet"> <set> <value>SetA</value> <value>SetB</value> <value>SetC</value> </set> </property> <property name="myMap"> <map> <entry key="mapKey1" value="value1"></entry> <entry key="mapKey2" value="value2"></entry> </map> </property> <property name="myprops"> <props> <prop key="propKey1">vaule1</prop> <prop key="propKey2">vaule2</prop> </props> </property> </bean>
5.5 测试类
package ui; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import service.IAccountservice; /** * @author konley * @date 2020-08-15 21:10 * 模拟表现层,用于调用业务层(servlet) */ public class Client { public static void main(String[] args) throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //1.测试构造函数注入 IAccountservice as1 = (IAccountservice) ac.getBean("accountService1"); System.out.println(as); as.saveAccount(); //2.测试set方法注入 IAccountservice as2 = (IAccountservice) ac.getBean("accountService2"); System.out.println(as2); as2.saveAccount(); //3.测试set方法注入复杂类型 IAccountservice as3 = (IAccountservice) ac.getBean("accountService3"); System.out.println(as3); as3.saveAccount(); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?