什么是Spring
这里的Spring指的是SpringForMark
官网介绍
下面是官网的介绍
Spring优势
Spring的优势是什么呢,b站老师给我们做出以下解释:
方便解耦,简化开发
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造
成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可
以更专注于上层的应用。
AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以
传智播客——专注于 Java、.Net 和 Php、网页平面设计工程师的培训
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
通过 AOP 轻松应付。
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,
提高开发效率和质量。
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可
做的事情。
方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz
等)的直接支持。
降低 JavaEE API 的使用难度
Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的
使用难度大为降低。
Java 源码是经典学习范例
Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以
及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
我们可以看到第一句是:“方便解耦,简化开发”
那什么是耦呢,耦在这里是指耦合
什么是耦合
耦合是指类与类之间的关联,在java中通常体现为“导入包”等操作
耦合是不可能完全消除的,我们能做的只能是降低耦合
这里为了更好的理解耦合的概念 写一个程序,这是一个使用jdbc访问数据库的程序
public static void main(String[] args) throws SQLException {
//注册驱动
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//获取链接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring_test","root","adminadmin");
//获取数据库处理对象
PreparedStatement ps = connection.prepareCall("select * from account");
//执行sql得到结果集
ResultSet rs = ps.executeQuery();
//遍历结果集
while (rs.next()){
System.out.println(rs.getString("name")+" "+rs.getString("money"));
}
rs.close();
ps.close();
connection.close();
}
可以看到在注册驱动时我们采用了DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
但如果这时,我们删除了msql的jar包会发生什么呢
这时我们会发现这是一个编译错误
但是如果我们通常注册驱动时是不会这样写的
我们通常采用的是通过反射来注册驱动
比如Class.forName("com.mysql.cj.jdbc.Driver");
此时运行会发现
我们的错误从编译错误变成了运行时的错误
小结一下:
对于平常创建对象的一些问题
我们对象创建通常是采用new的方式进行的,即
IAccountserver accountserver = new accountserverimpl();
但是我们采用这种方式,必然需要导入accountserverimpl这个类
如果删除类就会导致编译时报错
但是如果我们使用工厂模式的思想就可以解决这个问题
我们试着写一个工厂类
public class BeanFactory {
private static Properties propertie = new Properties();
static {
//读取配置文件
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
propertie.load(in);
} catch (IOException e) {
e.printStackTrace();
}
}
public static Object getbean(String beanname) throws Exception {
Object a= null;
//获取全限定类名
String classname=propertie.getProperty(beanname);
System.out.println(classname);
//通过反射来创建对象
a=Class.forName(classname).newInstance();
return a;
}
}
这就是读取配置文件再通过反射的方式来创建对象
此时就可以用以下代码来创建对象IAccountserver accountserver=(IAccountserver) BeanFactory.getbean("accountserver");
现在我们观察代码,发现已经成功的解耦了,但是还有个问题
我们修改主函数来观察一下
public static void main(String[] args) throws Exception {
for (int i = 0; i < 5; i++) {
IAccountserver accountserver=(IAccountserver) BeanFactory.getbean("accountserver");
System.out.println(accountserver);
}
}
最后输出的结果是这样的
这表示这创建的是多例对象
那如果要创建单例对象呢
我们更改工厂类的代码,主要是在获取之前先将对象创建好并存储到容器中,以遍调用
public class BeanFactory {
//读取配置文件
private static Properties propertie = new Properties();
//创建容器用于存储单例对象
private static Map<String,Object> objectMap;
static {
objectMap=new HashMap<String,Object>();
//读取文件
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
//加载文件
propertie.load(in);
} catch (IOException e) {
e.printStackTrace();
}
//获取所有关键字
Enumeration keys = propertie.keys();
//遍历关键字
while (keys.hasMoreElements()){
//枚举关键字
String key = keys.nextElement().toString();
try {
//创建对象,并存入容器中
objectMap.put(key,Class.forName(propertie.getProperty(key)).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/**
* 单例工厂模式创建javabean
* @param beanname
* @return
*/
public static Object getbean(String beanname){
return objectMap.get(beanname);
}
}
此时运行结果便成了:
spring基于配置文件的开发
1.导入jar包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
</dependencies>
2.编写配置文件:
<?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="accountServer" class="com.spring.server.impl.accountserverimpl"></bean>
<bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl"></bean>
</beans>
关于代码开头的约束可以在下面的网站中查找到
https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/core.html#beans
3.编写测试类:
public class Test {
public static void main(String[] args) throws Exception {
//获取核心容器对象
//ApplicationContext的三个实现类:
// ClassPathXmlApplicationContext -----类路径下的配置文件创建
// FileSystemApplicationContext -----磁盘上的配置文件
// AnnotationConfigApplicationContext -----基于注解
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取对象
IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer");
accountserver.saveAccount();
}
}
注意这里 IAccountserver 是一个自己创建的接口 定义了saveAccount方法
他的实现类 com.spring.server.impl.accountserverimpl实现了saveAccount方法,public void saveUser() { System.out.println("保存成功"); }
所以此时运行结果为
那么此时创建的对象是单例对象还是多例对象呢
我们更改一下测试程序
public class Test {
public static void main(String[] args) throws Exception {
//获取核心容器对象
//ApplicationContext的三个实现类:
// ClassPathXmlApplicationContext -----类路径下的配置文件创建
// FileSystemApplicationContext -----磁盘上的配置文件
// AnnotationConfigApplicationContext -----基于注解
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取对象
IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer");
accountserver.saveAccount();
IAccountserver accountserver2=ac.getBean("accountServer",IAccountserver.class);
accountserver2.saveAccount();
System.out.println(accountserver==accountserver2);
}
}
此时的运行结果为:
由于两个对象地址相同,所以证明此时创建的是单例对象
另外需要注意的是
ApplicationContext 在读取配置文件时,就采用立即加载的方式将配置的对象创建完成了
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
即这句执行完将立即创建配置文件里所有的对象
而我们采用BeanFactory是采用延迟加载的方式实现的
这里我们来验证一下
我们首先重写一下com.spring.server.impl.accountserverimpl的构造函数,让其执行构造函数时输出
public class accountserverimpl implements IAccountserver {
private IaccountDao dao = new AccountDaoImpl();
public accountserverimpl() {
System.out.println("创建了accountserverimpl");
}
@Override
public void saveAccount() {
dao.saveUser();
}
}
在试着在读取配置文件时打一个断点,然后在debug模式一步一步运行
由此可以证明ApplicationContext 是采用立即加载的方式
同理可以证明
所以BeanFactory采用的时延迟加载的方式
总结一下
ApplicationContext 由于在程序运行一开始就创建好了对象(对象只创建一次)所以适用于单例对象
BeanFactory由于程序运行到创建对象时才进行对象创建(对象可以创建0次或多次),所以适用于多例对象 **注意:**这里不是说BeanFactory创建的对象是多例的,而是延迟加载的方式更适合于多例对象的创建
Spring细节
spring管理的细节
1.创建bean对象的三种方式
2.bean对象的作用范围
3.ben对象的生命周期
一,创建bean的三种方式
<!-- 一,创建bean的三种方式-->
<!-- 1.使用默认构造函数创建 注意:没有无参构造函数时将无法创建 -->
<bean id="accountServer" class="com.spring.server.impl.accountserverimpl"></bean>
<!-- 2.使用工厂中的方法来创建对象 (使用某个类中的方法(非static方法)创建对象,并存入spring容器中) -->
<bean id="instanFactory" class="com.spring.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanFactory" factory-method="getAccountService"></bean>
<!-- 3.使用工厂中的静态方法创建对象 -->
<bean id="accountService" class="com.spring.factory.InstanceFactory" factory-method="getAccountService"></bean>
二,bean的作用范围调整
<!-- 二,bean的作用范围调整
bean标签的scope属性:
作用:用于指定bean的作用位置
value:
singleton:单例(默认值)
prototype:多例
request: web应用的请求范围
session: web应用的会话范围
global—session :集群环境的会话范围
-->
<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="singleton"></bean>
前两个可以简单的修改测试函数来查看区别
public static void main(String[] args) throws Exception {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取对象
IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer");
IAccountserver accountserver2=(IAccountserver)ac.getBean("accountServer");
System.out.println(accountserver2==accountserver);
}
先将创建bean的方式设置为单例创建即<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="singleton"></bean>
此时运行执行结果为
我们再试一下多例,即<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype"></bean>
此时运行的结果是:
globa—session的作用如下所示,在服务器众多的情况下,需要一个多个服务器公用的session即globa—session
三,bean的生命周期
1.单例对象的生命周期:
我们在对象里添加两个方法
public void init() {
System.out.println("初始化了");
}
public void close() {
System.out.println("销毁了");
}
并更改配置文件,指定初始化方法和销毁方法
<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="singleton" init-method="init" destroy-method="close"></bean>
执行方法运行结果为
执行主函数:
public static void main(String[] args) throws Exception {
//获取核心容器对象
//ApplicationContext的三个实现类:
// ClassPathXmlApplicationContext -----类路径下的配置文件创建
// FileSystemApplicationContext -----磁盘上的配置文件
// AnnotationConfigApplicationContext -----基于注解
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取对象
IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer");
accountserver.saveAccount();
ac.close();
}
分析一下 ,单例对象的生命周期的为
出生:创建容器时bean就被创建了
活着:只要容器在就一直活着
销毁:容器销毁,bean就销毁
总结:单例对象的的生命周期和容器相同
2.多例对象的生命周期
改变配置文件使用多例对象的方式
<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype" init-method="init" destroy-method="close"></bean>
此时运行结果为:
发现对象的销毁并没有受容器的影响。
再加上多例对象采用懒加载不难做出以下总结
分析一下 ,单例对象的生命周期的为
出生:使用对象时被创建
活着:只要对象在使用过程中就一直活着
销毁:被jvm垃圾回收
DI依赖注入
编写类:
public class accountserverimpl implements IAccountserver {
//如果经常改变的数据不适用于DI
private String name;
private Integer age;
private Date birthday;
public accountserverimpl() {
}
public accountserverimpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
@Override
public void saveAccount() {
System.out.println("accountserverimpl{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}');
}
构造函数注入
编写配置文件
<!--
在程序中依赖关系的管理将交给spring管理
依赖关系的维护:称之为依赖注入
依赖注入:
能注入的三种类型:
基本数据类型和String
其他bean(配置文件或注解配置过的)
复杂类型,集合类
注入的方式:三种
构造函数
set方法
注解提供
-->
<!-- 使用constructor-arg标签,采用构造函数注入,必须有有参的构造函数
具体有以下属性:
1.index 指定要插入的位置 很少单独使用
2.type 赋值全限定类名来指定数据类型导入 很少单独使用
3.name 通过形参的名称注入 最常用
==========上面三种都是来找插入的位置的,通常单独使用第三种方式进行===================
4.value 注入基本数据类型和String使用
5.ref 注入其他bean对象
-->
<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype">
<constructor-arg name="name" value="姓名"/>
<constructor-arg name="age" value="12"/>
<constructor-arg name="birthday" ref="day1"/>
</bean>
<bean id="day1" class="java.util.Date">
<constructor-arg name="year" value="128"/>
<constructor-arg name="month" value="10"/>
<constructor-arg name="date" value="20"/>
</bean>
此时运行main函数可以得到结果:
public class Test {
public static void main(String[] args) throws Exception {
//获取核心容器对象
//ApplicationContext的三个实现类:
// ClassPathXmlApplicationContext -----类路径下的配置文件创建
// FileSystemApplicationContext -----磁盘上的配置文件
// AnnotationConfigApplicationContext -----基于注解
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取对象
IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer");
accountserver.saveAccount();
ac.close();
}
}
这种构造函数方式注入的方式有一个弊端:即在不需要某些参数时也必须传入参数
set方法注入
1.为类创捷get和set方法
2.更改配置文件
<!-- set注入:使用set方法注入
使用标签property
属性值
name 通过形参的名称注入
value 注入基本数据类型和String使用
ref 注入其他bean对象
-->
<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype">
<property name="name" value="hhhhh"/>
<property name="age" value="15"/>
<property name="birthday" ref="day1"/>
</bean>
<bean id="day1" class="java.util.Date">
<constructor-arg name="year" value="128"/>
<constructor-arg name="month" value="10"/>
<constructor-arg name="date" value="20"/>
</bean>
这种方式较为灵活,但无法保证某些必须有值的成员有值
以上两种方式对比 set方式更加常用
复杂数据类型的注入
更改要创建的类
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties 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;
}
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);
}
更改配置文件
<!-- 复杂类型的注入/集合类型的注入
用于给List结构集合注入的标签:
list array set
用于个Map结构集合注入的标签:
map props
结构相同,标签可以互换
-->
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="myMap">
<props>
<prop key="testC">ccc</prop>
<prop key="testD">ddd</prop>
</props>
</property>
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
</bean>
此时运行便可以得到想要的结果
注意
用于给List结构集合注入的标签:
list array set
用于个Map结构集合注入的标签:
map props
结构相同,标签可以互换