文章目录
1.入门:
1.1 起源和定义:
spring 全名:SpringFramework.
创始人 Rod Johnason在2002年指出当时的EJB框架的缺陷,并在2004年发布了Spring框架的第一个版本。
EJB(Enterprise Java Bean)企业级javabean的缺点:
1.运行环境苛刻
2.代码移植性差
总结:EJB是重量级的框架
- 定义:
spring是一个轻量级的javaEE解决方案,整合众多优秀的设计模式 - 轻量级:
1.对于运行环境是没有要求的
2.代码的移植性高:在服务器上不需要实现额外的接口。 - 整合设计模式:
整合了工厂、代理、策略、模板等设计模式。
1.2 工厂设计模式:
-
设计模式的概念:
广义概念:面向对象设计中,解决特定问题的经典代码
狭义概念:GOF 4人帮定义的23种设计模式 -
工厂设计模式的概念:
1.概念:通过工厂类创建对象
2.好处:可以解耦合
3.耦合:指的是代码间的强关联关系,一方的改变会影响另一方,这样不利于代码的维护 -
简单工厂的实现:
//如果UserServiceImpl变化,这个方法需要重新改写
public void test(){
//耦合情况
//UserService u = new UserServiceImpl();
//使用了工厂类,这里的代码已经没有耦合情况出现了
userService u = factory.factoryBean();
u.login("www","12345");
}
这是简单的工厂类,耦合其实转移到工厂类中去了。
//工厂类
public class factory {
public static userService factoryBean(){
return new userServiceImpl();
}
}
- 反射工厂的实现:
上面的简单工厂实现还存在耦合的原因是因为获取对象的方法是使用构造器来获取的,使用构造器就必须new一个对象,就一定会产生耦合的情况,针对这一问题,我们使用另一种获得对象的方法:反射。
通过反射的方法获取到这个类:
但是这里还是存在着耦合,如果有修改后还是需要修改代码。
如果我们将需要修改的地方放在配置文件中,需要修改时只需要修改配置文件即可!
如下代码显示,我们将配置写在配置文件里,并通过反射和io流获取到配置文件
这时我们只需要修改配置文件就可以了,耦合是不可能消除的,只能转移,但是修改配置文件比修改代码的成本小很多。
public class factoryBean {
private static Properties properties = new Properties();
static {
try {
// 获取io流
InputStream resourceAsStream = factoryBean.class.getResourceAsStream("/user.properties");
properties.load(resourceAsStream);
resourceAsStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static userService factoryBean(){
userService userService1 = null;
try {
//类似于键值对,一个键对应一个值,我们通过指定键获取值。
Class<?> aClass = Class.forName(properties.getProperty("userService"));
userService1 = (userService) aClass.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return new userServiceImpl();
}
}
工厂模式后的service实现类的代码:
public class userServiceImpl implements userService{
private userDao userDao = factoryBean.factoryDaoBean();
@Override
public void login() {
userDao.login();
}
}
1.3 通用工厂的设计:
问题:我们上面的简单工厂虽然解决了耦合的问题,但是代码太过冗余,并且只能适用于某一个功能,我们需要将这些代码抽象出来,形成一个任何类型都能用的抽象工厂!
注意这里的key就是配置文件中的键名,我们根据这个键名查找相应的值
public static Object factory(String key){
Object obj = null;
try {
Class<?> aClass = Class.forName(properties.getProperty(key));
obj = aClass.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return obj;
}
public class userServiceImpl implements userService{
private userDao userDao = (Factory.userDao) factoryBean.factory("userDaoService");
@Override
public void login() {
userDao.login();
}
}
这里也有想到可不可以使用泛型去定义,但发现无法定义泛型静态方法,因为静态方法是编译时类型,动态是运行时类型,而泛型代表了未知类型,因为是未知类型所以系统没办法赋值,也不能手动赋值。
2. Spring核心:
2.1 核心API:
- ApplicationContext:
作用:Spring提供ApplicationContext这个工厂,用于对象的创建。
好处:实现了解耦合。 - ApplicationContext接口类型:
这个是一个接口,为了屏蔽实现的差异。
非web环境:ClassPathXmlApplicationContext(用于main或者junit单元本地测试使用 )
web环境:XmlWebApplicatonContext
- 重量级资源:
因为ApplicationContext工厂的对象占用的内存很多
所以不会频繁的创建对象,一个应用只会创建一个工厂对象。
一定是线程安全的(多线程并发访问)
2.2 第一个spring程序:
1.创建一个实体类
2.编写配置文件
3.获取applicationContext进行操作
- 名词解释:
spring工厂创建的对象,叫做 bean 或者组件
<?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="person1" class="entity.person"/>
<bean id="person2" class="entity.person"/>
<bean id="dog1" class="entity.dog"/>
</beans>
- ApplicationContext的其他方法:
通过bean的名字获取bean,需要强制转换
person p = (person) a.getBean("person1");
System.out.println(p);
通过名字和bean的类型获取对象,不需要强转
person personP = a.getBean("person1", person.class);
System.out.println("可通过输入类对象指定获取bean的类型,这样就不用强转了:"+personP);
通过类型获取bean对象,但只能获得单个对象,如果配置文件中一个类存在多个bean,使用这个方法就会报错。
person bean = a.getBean(person.class);
System.out.println("通过指定bean的类获取对象,但是注意如果配置文件中存在多个bean对应一个类型,这里会报错:"+bean);
可通过 getBeanDefinitionCount() 方法获取当前配置文件下的bean的数量,通过getBeanDefinitionNames()方法获得当前配置文件下所有bean的名字。
System.out.println(a.getBeanDefinitionCount());
String[] beanDefinitionNames = a.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
可通过 getBeanNamesForType() 方法获得指定类型的所有bean的名字。
String[] beanNamesForType = a.getBeanNamesForType(person.class);
for (String s : beanNamesForType) {
System.out.println(s);
}
通过指定bean的名字来查找这个bean在不在当前spring的配置文件下。
这两个方法看上去没有什么区别,但最主要的区别是:第一个方法只能通过 id 值进行判断,第二个可以通过 id 和 name 值进行判断。
if (a.containsBeanDefinition("person1")) {
System.out.println(true);
}else{
System.out.println(false);
}
if (a.containsBean("dog1")) {
System.out.println("true");
}else {
System.out.println("false");
}
- 配置文件中注意的细节:
- 只配置class属性,不配置bean的id:
<bean class="entity.dog"/>
我们可以通过类型的 getBean 方法获得这个bean对象,因为我们没有设置id,那它是否有id呢?
@Test
public void test2(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
dog bean = applicationContext.getBean(dog.class);
System.out.println(bean);
for (String s : applicationContext.getBeanNamesForType(dog.class)) {
System.out.println(s);
}
}
答案是有的,spring会帮助我们生成一个默认的id:
应用场景:
如果这个bean只需要使用一次,那么可以省略id值;但如果这个bean要使用多次,或者被其他bean引用则需要设置id值。
- bean标签 的 name 属性
name的意思是别名,和id的作用类似,使用和id一致,都是使用getbean方法获得。
<bean id="dog" name="wqj" class="entity.dog"/>
区别:
1.id 只是唯一的,但是name可以不唯一并可定义多个。
2.在以前的xml文档的要求里,id只能以小写字母开头,不能以其他数值开头,但是name没有命名规范的要求。现在xml已经没有这个对id的要求了。
2.2.1 spring工厂底层实现思路:
- 先通过 ClassPathXmlApplicationContext 读取 applicationContext.xml 配置文件信息。
- 获得bean标签的相关信息(id和calss),通过输入的id名或者class名通过反射的方式创建对象。返回给方法中。
- 这里底层也是通过构造方法创建对象,而且反射可以获得私有的构造方法,就是说如果一个类的构造方法设置成了私有的,spring框架依旧可以获得这个类的对象;如果删除了这个类的构造方法,则会报错。
- 经过测试我发现不管是否使用到了配置文件中创建的bean对象,spring 框架都会先将这些对象创建出来,即使他们是同一类型的对象。
我们这里只使用了名为person1的对象
但是发现我们创建了5个bean对象他们的构造方法全被调用了,就是说spring为每个bean都创建了一个对象,即使有些bean的对象是相同的。
思考:spring这么方便,那所有的对象都要交给spring创建吗?
特例:理论上是的,但是实体对象(就是针对数据库中的数据的对象)是不会交给spring创建的。
2.3 注入:
定义:通过Spring工厂以及配置文件,为所创建对象的成员变量赋值
2.3.1 set注入:
- 为什么需要注入:
因为如果我们直接使用 get / set 方法在代码内赋值时,会产生耦合的问题。 - 使用方法:
在配置文件中直接对实体类中的变量进行赋值,这里的前提是需要实体类有get/set方法,如果没有这些方法会依旧报错。
<bean id="dog" class="entity.dog">
<property name="name" value="wqj"/>
<property name="age" value="21"/>
</bean>
- 原理:
在配置文件中,spring通过bean标签的配置创建相应的对象,使用property标签的配置在底层调用对应变量的 get/set 方法,完成对变量的赋值。
set注入都是根据 properties 标签进行注入,但是根据参数的类型不同,写法也不同,set注入大致分为两类:
- jdk内置类型:
对于 string 和 8 种基本类型:使用 value属性 进行值的设置。
对于 数组类型 和 list集合 类型的参数:通过 list 标签嵌套value标签进行赋值。
<property name="emails">
<list>
<value>1@qq.com</value>
<value>2@qq.com</value>
<value>3@qq.com</value>
</list>
</property>
- 对于 set集合类型 的参数:
通过 set 标签嵌套value标签进行赋值。这里我们嵌套value是因为我们的set集合中自己规定了存的参数类型为string。
<property name="friends">
<set>
<value>wyh</value>
<value>zfy</value>
<value>wyh</value>
</set>
</property>
- 对于 map集合类型 的参数:
map集合类型的参数先使用 map 标签,因为map是键值对的形式,所以一对键值对需要使用 entry 标签嵌套起来,一对键值对中又存在键和值;
其中键使用 key 标签,因为我们的key存的是String类型的参数,所以又用value标签进行嵌套。
值的话没有固定的标签格式,直接取决于存的值的参数类型。
<property name="food">
<map>
<entry>
<key><value>肉类</value></key>
<value>牛肉</value>
</entry>
</map>
</property>
- 对于 properties类型 的参数:
这个类型比较特殊,是特殊的map集合,但 properties 集合的键和值都是固定好的String类型,这里使用 prop 标签包裹,其中在标签中直接定义 key 属性设置属性值,在标签中直接编写对应的值。
这里的问题:为什么不使用 value 标签将字符串类型的值包裹呢,因为 properties 本来就固定好了键值的类型是String类型,所以spring帮我们省略了这一步。
<property name="p">
<props>
<prop key="key1">www</prop>
<prop key="key2">yyy</prop>
<prop key="key3">hhh</prop>
</props>
</property>
</bean>
- set注入用户自定义类型:
案例如下:我们需要使用自定义的userDao对象。
private userDao userDao;
public Factory.userDao getUserDao() {
return userDao;
}
public void setUserDao(Factory.userDao userDao) {
this.userDao = userDao;
}
@Override
public void login(person person) {
userDao.login(person.getName(),person.getAge());
}
第一种解决方法:
直接在内部创建引用
这个方法的问题是:会出现代码的冗余,因为可能有不止一个类调用使用userDao对象,按这样的写法每个类的配置文件中都要写这样一行,消耗jvm的性能。
<bean id="userService" class="Factory.userServiceImpl">
<property name="userDao">
<bean class="Factory.userDaoImpl"/>
</property>
</bean>
第二种解决方法:
将创建userDao对象的代码单拎出来,如果有需要的地方就直接使用 ref 标签引用此对象。
<bean id="userdao" class="Factory.userDaoImpl"/>
<bean id="userService" class="Factory.userServiceImpl">
<property name="userDao">
<ref bean="userdao"/>
</property>
</bean>
set注入简化:
- 简化value标签,将value属性写在properties属性中,注:这里的简化只能用于 8种基础类型 和 String 类型的参数使用中。
- 简化ref标签,将 ref 标签写在properties属性中。
- 使用 p 命名空间:作用是替代 properties 标签,直接通过 p命名空间对创建的对象的参数值进行设置,但是需要引用此命名空间的说明。
xmlns:p="http://www.springframework.org/schema/p"
<bean id="person" class="entity.person" p:name="wyh" p:age="21"/>
<bean id="userdao" class="Factory.userDaoImpl"/>
<bean id="userService" class="Factory.userServiceImpl" p:userDao-ref="userdao"/>