Spring 学习1(Spring 5.0.2)
IoC 控制反转
例一:
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
}
上面的代码表示:业务层里调用持久层,同时现在的业务层在依赖持久层的接口和实现类。如果此时没有创建持久层的实现类,编译将不能通过。这种编译期依赖关系,可以通过代码优化解决。
例二:
要使用 JDBC 操作,先要注册驱动,可以用 DriverManager 的 registerDriver 方法或使用 Class.forName 方法
public class JdbcTest{
public static void main(String[] args){
//1.注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
//3.获取预处理 sql 语句对象
//4.获取结果集
//5.遍历结果集
}
}
这里没有使用 DriverManager.registerDriver(new com.mysql.jdbc.Driver()); ,因为这里的类依赖了数据库的具体驱动类(这里是MySQL驱动),如果日后更换了其他数据库(如Oracle),就要修改源码来使用新的数据库驱动,这显然是不合适的。
使用了反射来注册数据库驱动
Class.forName("com.mysql.jdbc.Driver"); //此时forName()里的只是一个字符串
这样类中就不再依赖具体的数据库驱动类,就是你现在没有MySQL的驱动jar包,还是可以编译的。当然,你既然没有相应的jar包程序是不能运行的了。
如果日后更换了其他数据库,还是要改源码,即修改这行字符串,解决方法就是使用配置文件来进行配置。
工厂模式解耦
在实际开发中,可以把三层结构的对象使用配置文件进行配置,当服务器应用加载时,让一个类中的方法,通过读取配置文件,把这些对象创建并且存起来。在后面要用到这些对象的时候,就直接拿来用。
解释控制反转
上面提到的把对象存起来,那就要用一个集合来存,使用 Map 这种键值对的形式来存。
应用加载时,创建一个 Map,用来存放三层对象。
这个 Map 称它为容器。
工厂负责给我们从容器中获取指定对象,这样我们获取对象的方式就改变了。
之前:想要获取对象就用 new 的方式,这是主动。
之后:去问工厂要,让工厂为我们查找或者创建对象。这是被动。
这种被动接收的方式获取读写的思想就是 IoC 控制反转,是 Spring 框架的核心之一,作用减少程序的耦合。
创建普通 java 项目演示,手动导入 Spring 的 jar 包
例一:账户的业务层和持久层
public interface IAccountService { //业务层接口
//保存账户
void saveAccount();
}
public class AccountServiceImpl implements IAccountService { //业务层实现类
//此处的依赖关系有待解决
private IAccountDao accountDao = new AccountDaoImpl();
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
public interface IAccountDao { //持久层接口
void saveAccount(); //保存账户
}
public class AccountDaoImpl implements IAccountDao {
//持久层实现类
@Override
public void saveAccount() {
System.out.println("保存了账户");
}
}
在 src 目录下创建一个 xml 文件,这里是bean.xml(名字随便起,但不可以有中文),创建后添加约束并配置 service 和 dao :
<?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 标签:用于配置让 spring 创建对象,并且存入 Ioc 容器之中 id 属性:对象的唯一标识。
class 属性:指定要创建对象的全限定类名 -->
<!-- 配置 service -->
<bean id="accountService" class="com.rgb3.service.AccountServiceImpl"> </bean>
<!-- 配置 dao -->
<bean id="accountDao" class="com.rgb3.dao.AccountDaoImpl"></bean>
</beans>
这里可能会遇到的情况
测试一下看看配置是否成功
public class NoteDemoApp {
public static void main(String[] args) {
//1.使用 ApplicationContext 接口,就是在获取 spring 容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据 bean 的 id 获取对象
IAccountService aService = (IAccountService) ac.getBean("accountService");
System.out.println("业务层的对象:"+aService);
IAccountDao aDao = (IAccountDao) ac.getBean("accountDao");
System.out.println("dao层的对象:"+aDao);
}
}
成功。上面几行红的警告网上查了下是log4j没有输出日志产生的警告。解决办法:在src文件夹下建立一个名为log4.properties的文件即可。
log4.properties 文件内容:
log4j.rootLogger=DEBUG , stdout
log4j.logger.org.mybatis=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p %d %C: %m%n
Spring 中工厂的类结构图:
BeanFactory 和 ApplicationContext 的区别
BeanFactory 是 Spring 容器中的顶层接口。 ApplicationContext 是它的子接口。
BeanFactory 和 ApplicationContext 的区别: 创建对象的时间点不一样。
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
BeanFactory:什么时候用什么时候创建对象。
ApplicationContext 接口的实现类
ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件 推荐用这种
FileSystemXmlApplicationContext:它是从硬盘路径上加载配置文件,配置文件可以在硬盘的任意位置。
AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建 Spring 容器,它用来读取注解。
bean.xml配置文件里面的 bean 标签介绍
用于配置,可以让 Spring 创建对象的。
默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
属性:
id:给对象在容器中提供一个唯一标识。用于获取对象。
class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
scope:指定对象的作用范围。
- singleton :默认值,单例的。
- prototype :多例的。
- request :WEB 项目中,Spring 创建一个 bean 的对象,将对象存入到 request 域中。
- session :WEB 项目中,Spring 创建一个 bean 的对象,将对象存入到 session 域中。
- global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么 globalSession 相当于 session。
init-method:指定类中的初始化方法名称。
destroy-method:指定类中销毁方法名称。
bean 的作用范围和生命周期
单例对象:scope=“singleton”
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期:
-
对象创建:当应用加载,创建容器时,对象就被创建了。
-
对象活着:只要容器在,对象一直活着。
-
对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
多例对象:scope=“prototype”
每次访问对象时,都会重新创建对象实例。
生命周期:
-
对象创建:当使用对象时,创建新的对象实例。
-
对象活着:只要对象在使用中,就一直活着。
-
对象死亡:当对象长时间不用时,被 java 的垃圾回收机制回收了。
实例化 bean 的3种方式
-
用默认无参构造函数
默认情况下:它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。
<bean id="accountService" class="com.rgb3.service.AccountServiceImpl"/>
-
创建一个工厂类,然后用工厂的静态方法创建对象。
public class StaticFactory { //模拟一个工厂 public static IAccountService createAccountService(){ return new AccountServiceImpl(); } }
<bean id="accountService" class="com.rgb3.factory.StaticFactory" factory-method="createAccountService"></bean>
-
Spring管理工厂,先创建工厂实例对象,再调用工厂的方法创建对象。
public class InstanceFactory { //模拟一个工厂 public IAccountService createAccountService(){ return new AccountServiceImpl(); } }
<bean id="instancFactory" class="com.rgb3.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instancFactory" factory-method="createAccountService"></bean>
先把工厂的创建交给 Spring 来管理。
然后再用工厂的 bean 来调用它里面的方法
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法。
Spring 的依赖注入
依赖注入是 IoC 控制反转的具体实现。
在写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。 IoC 解耦只是降低他们的依赖关系,但不会消除。如:业务层仍会调用持久层的方法。 这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。就是让Spring 把持久层对象传入业务层,而不用我们自己去获取。
构造函数注入
用类中的构造函数,给成员变量赋值。但是,赋值的操作不是我们自己做的,而是通过配置的方式,让 Spring 来帮我们注入。
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(name+","+age+","+birthday);
}
}
bean.xml
<bean id="accountService" class="com.rgb3.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="20"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
constructor-arg 标签 属性:
- index:指定参数在构造函数参数列表的索引位置
- type:指定参数在构造函数中的数据类型
- name:指定参数在构造函数中的名称
- value:它赋的值是基本数据类型和 String 类型
- ref:它赋的值是其他 bean 类型,必须是在配置文件中配置过的 bean
set 方法注入
在类中提供需要注入成员的 set 方法,实际开发中,此种方式用的较多。
public class AccountServiceImpl 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(name+","+age+","+birthday);
}
}
bean.xml
<bean id="accountService" class="com.rgb3.service.impl.AccountServiceImpl">
<property name="name" value="mike"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
property 标签 属性:
- name:找的是类中 set 方法后面的部分
- ref:给属性赋值是其他 bean 类型的
- value:给属性赋值是基本数据类型和 string 类型的
用 p 名称空间注入数据,本质上还是调用 set 方法
public class AccountServiceImpl 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(name+","+age+","+birthday);
}
}
bean.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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="accountService" class="com.rgb3.service.impl.AccountServiceImpl"
p:name="jay"
p:age="21"
p:birthday-ref="now"/>
<bean id="now" class="java.util.Date"></bean>
</beans>
注入集合
给类里的集合传值,用的也是 set 方法注入。以 List , Set , Map , Properties 为例。
public class AccountServiceImpl implements IAccountService {
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;
}
@Override
public void saveAccount() {
System.out.println("myStrs: "+Arrays.toString(myStrs));
System.out.println("myList: "+myList);
System.out.println("mySet: "+mySet);
System.out.println("myMap: "+myMap);
System.out.println("myProps: "+myProps);
}
}
List 结构的: array,list,set
Map 结构的 map,entry,props,prop
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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="accountService" class="com.rgb3.service.impl.AccountServiceImpl">
<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
<!--注入数组数据-->
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<!-- 注入 list 集合数据 -->
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<!-- 注入 set 集合数据 -->
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- 注入 Map 数据 -->
<property name="myMap">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
<!-- 注入 properties 数据 -->
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<!--两种方式-->
<entry key="testB">
<value>bbb</value>
</entry>
</map>
</property>
</bean>
</beans>