前言
1.OCP *什么是OCP OCP是软件七大原则中最基本的一个原则:开闭原则 对什么开:对扩展开放 对什么关闭:对修改关闭 *OCP原则是最基本、最核心的,其他原则都是为其服务的。 *OCP开闭原则的核心: 只要你在扩展系统功能的时候,没有修改以前写好的代码,那就是符合OCP 反之,若在扩展系统功能时,你修改了之前写好的代码,则该设计失败,因违背了OCP *当进行系统功能扩展时,若动了之前稳定的程序,修改了之前的程序,之前所有程序都需重新测试。这是不想看到的,因非常麻烦 2.依赖倒置原则(DIP) 上层不依赖下层,而应设计成 封装下层,下层向上层提供服务,上层不用管下层的具体实现细节, 即下层内部对上层来说 是透明的。 *依赖倒置原则的核心:倡导面向接口编程,面向抽象编程,不要面向具体编程。 *依赖倒置原则的目的: 降低程序的耦合度,提高扩展力。 *什么叫符合依赖倒置 上不依赖下。 *什么叫违背依赖倒置 上 依赖 下,就是违背。 只要“下”一改动,“上”就受到牵连。 3.当程序的设计既违背OCP,又违背DIP时,怎么办 可采用“控制反转”这种编程思想来解决这个问题。 4.什么是控制反转 控制反转(IoC:Inversion of Control) 反转的两件事: 1.在程序中不再采用 硬编码 的方式 来 new对象(new对象的权力交出去) 2.在程序中不再采用 硬编码 的方式 来 维护对象的关系了(对象之间关系的维护权交出去) 控制反转:是一种编程思想。或者一种设计模式。 5.Spring 框架 *spring框架实现了控制反转(IoC)的思想。 spring框架可以帮你new对象 spring框架可以帮你维护对象和对象之间的关系 *spring框架是一个实现了IoC的容器。 *控制反转的实现方式有多种,其中较重要的是:依赖注入(Dependency Injection,简称DI) *控制反转是思想。依赖注入是这种思想的具体实现方式。 *依赖注入DI,包括两种常见的方式: 1.set注入(执行set方法给属性赋值) 2.构造方法注入(执行构造方法给属性赋值) *依赖注入中“依赖”、“注入”分别是什么意思(我:通过注入的手段,使得两对象之间产生依赖关系。 依赖:两个对象的一种关系(顾名思义) 注入:一种手段。通过该手段,可让两对象之间产生关系。 依赖注入:A对象和B对象之间的关系,靠注入的手段来维护。而注入包括:set注入和构造注入
6.一个简单入门程序
*创建项目后,先将pom.xml中的仓库、依赖配置好。
*main中创建包及包下的类
*配置spring的配置文件,放在resources的根目录下,就相当于放在了类的根路径下。
*在spring配置文件中 配置bean,这样spring才能帮助我们管理对象
bean标签的两个重要属性: id:是这个bean的身份证号,不能重复,是唯一的标识
class:必须填写类的全路径,全限定类名。(带包名的类名)
*test中创建包及包下的测试类
//第一步:获取spring容器对象
//ApplicationContext 翻译为:应用上下文。其实是spring容器
//ApplicationContext 是一个接口
//ApplicationContext 接口下有很多实现类。其中有一个实现类是
ClassPathXmlApplicationContext
//ClassPathXmlApplicationContext 专门从类路径中加载spring配置文件的一个spring上下文对象。 //这行代码只要执行,就相当于启动了spring容器,
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//第二步:根据bean的id获取spring容器中的对象
Object userBean = applicationContext.getBean("userBean");
注意:1.每个bean 的id都必须是唯一的,不能重复。
2.//Spring 怎么实例化对象的?
//默认情况下spring会通过反射机制,调用 类的无参构造函数方法来实例化对象
//实现原理如下:
//Class cal = Class.forName("com.powernode.spring6.bean.User")
//cal.newInstance();
若类的定义中一个构造函数都没写,会有一个默认的无参构造函数,若只写了一个带参的构造函数,则报错。如果有写带参的构造函数,则必须同时也写无参的构造函数。
7.spring6启用Log4j2日志框架
第一步:引入Log4j2的依赖
<!--log4j2的依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
第二步:在类的根路径下提供log4j2.xml配置文件(文件名固定为:log4j2.xml,必须放在类根路径下)
<!-- log4j2.xml-->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<loggers>
<!--
level指定日志级别,从低到高的优先级:
ALL< TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
级别越低,日子信息越丰富,ALL则把所有信息都给你放日志;
级别越高,日志信息越少
-->
<root level="FATAL">
<appender-ref ref = "spring6log"/>
</root>
</loggers>
<appenders>
<!-- 输出日志信息到控制台-->
<console name ="spring6log" target = "SYSTEM_OUT">
<!-- 控制日志输出格式-->
<PatternLayout pattern ="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
</console>
</appenders>
</configuration>
如何使用:
//第一步:创建日志记录器对象
//获取FirstSpringTest类的日志记录器对象,也就是说只要是FirstSpringTest类中的代码执行记录日志的话,就输出相关的日志信息
Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
//第二步:记录日志,根据不同的级别来输出日志
logger.info("我是一条信息");
logger.debug("我是一条调试信息");
二、Spring对IoC的实现
2.1IoC(控制反转)
*控制反转是一种思想
*控制反转是为了降低程序耦合度,提高程序扩展力,达到OCP原则,达到DIP原则
*控制反转,反转的是什么?
1.将对象的创建权力交出去,交给第三方容器去负责
2.将对象和对象之间的关系的维护权交出去,交给第三方容器负责
*控制反转这种思想如何实现?
DI(dependency injection):依赖注入
2.2依赖注入
*依赖注入实现了控制反转的思想
*spring通过依赖注入的方式实现Bean的管理
*Bean管理指:Bean对象的创建,以及Bean对象中属性的赋值(或者叫Bean对象之间关系的维护)
*依赖注入:
依赖指的是 对象和对象之间的关联关系
注入指的是一种数据传递行为,通过注入行为来让对象和对象产生关系
*依赖注入常见的实现方式有两种:
1.set注入
2.构造注入
2.2.1set注入
*要对一个类set注入,首先该类的定义中要有set方法。
*set方法:
public class 某类1{
private 另一类2 变量名2;
.....
public void set___(另一类2 变量名2){
this.变量名2 = 变量名2;
}
}
*同时,要在spring的配置文件中:
<bean id="自己取,要唯一" class="某类1的全路径">
//name:set方法名去掉set,剩余单词,首字母小写
<property name="___" ref = "另一类2所对应的Bean的id/>
</bean>
//注:若set方法中的参数表 的 另一类2 是基本类型的话,ref要改为用 value
2.2.2set注入其他一些细节
*Date
<!--如果你硬要把Date当作简单类型的话,使用value赋值的话,这个日期字符串有格式要求-->
<!--在实际开发中,一般不会把Date当作简单类型。虽然它是简单类型。一般会采用ref给Date类型属性赋值-->
*数组注入
public class Lg {
private Family[] familiy;
//set方法
public void setFamiliy(Family[] familiy) {
this.familiy = familiy;
}
}
//spring配置文件
<bean id="f1" class="bean.Family">
<property name="name" value="父亲"/> //简单类型的要用value关键字
</bean>
<bean id="lg" class="bean.Lg">
<!--数组family的元素 不是 简单类型-->
<property name="familiy">
<array>
<ref bean="f1"/>
<ref bean="f2"/>
<ref bean="f3"/>
</array>
</property>
<!--若有另一数组的元素 是 简单类型-->
<property name="____">
<array>
<value>___</value>
<value>___</value>
<value>___</value>
</array>
</property>
</bean>
*List和set集合注入
public class Person {
//注入List集合
private List<String> names;
//注入Set集合
private Set<String> addrs;
public void setNames(List<String> names) {
this.names = names;
}
public void setAddrs(Set<String> addrs) {
this.addrs = addrs;
}
}
<bean id="person" class="bean.Person">
<property name="names">
<!-- list集合是 有序 可重复 -->
<list>
<value>lg</value>
<value>xy</value>
<value>家</value>
<value>家</value>
</list>
</property>
<property name="addrs">
<!-- set集合 无序 不可重复 -->
<set>
<value>广东省</value>
<value>重庆市</value>
</set>
</property>
</bean>
*注入Map和properties
//注入Map集合
private Map<Integer,String> phones;
public void setPhones(Map<Integer, String> phones) {
this.phones = phones;
}
//配置它的属性
<property name="phones" >
<!--注入Map集合-->
<map>
<!-- 如果key和value不是简单类型就用这个:-->
<!-- <entry key-ref=" " value-ref=""/>-->
<!-- 如果是简单类型就是key 和 value 如下-->
<entry key="1" value="888"/>
<entry key="2" value="666"/>
<entry key="3" value="888"/>
</map>
</property>
//注入属性类对象
//Properties本质上也是个Map集合
//Properties的父类是Hashtable,而Hashtable实现了Map接口
//虽然这也是个Map集合,和Map的注入方式有点像,但是不同
//Propertits的key和value只能是String类型
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
//在spring文件中配置属性
<property name="properties">
<props>
<prop key="lg">com.mysql.cj.jdbc</prop>
<prop key="xy">for</prop>
<prop key="lgdxfy">reve</prop>
</props>
</property>
*注入null和空字符串
public class Cat {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
//配置spring文件
<bean id="catBena" class="bean.Cat">
<!--不给属性注入,属性的默认值是null,表示空值-->
<!-- <property name="name" value="jj"></property>-->
<property name="age" value="3"></property>
</bean>
//手动注入null <property name="name" > <null/> </property>
//注入空字符串 <property name="name" value=""/>
*注入的值中刚好有特殊字符
public class Math {
private String result;
public void setResult(String result) {
this.result = result;
}
}
//相应配置
<bean id="mathBean" class="bean.Math">
<!--<property name="result" value="2<3"/> 会报错:与元素类型 "property" 相关联的 "value" 属性值不能包含 '<' 字符。-->
<!--第一中解决方案:使用 转义字符 代替特殊符号-->
<!-- <property name="result" value="2 < 3"/> -->
<!--第二种方案:使用 <![CDATA[]]> -->
<property name="result">
<!--该方案只能像这样使用value标签-->
<value><![CDATA[2<3]]></value>
</property>
</bean>
2.3 p命名空间注入
目的:简化配置。
使用p命名空间注入的两个前提条件:
*第一:在XML头部信息中添加p命名空间的配置信息
xmlns:p="http://www.springframework.org/schema/p"
*第二:p命名空间注入是基于setter方法的,所以需要对应的属性提供setter方法
public class Dog {
//简单类型
private String name;
private int age;
//非简单类型
private Date birth;
//p命名空间注入底层还是set注入,只不过p命名空间注入可以让spring配置更简单
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setBirth(Date birth) {
this.birth = birth;
}
}
//配置文件
<!--第一步:在spring的配置文件的头部添加配置信息:
xmlns:p="http://www.springframework.org/schema/p" -->
<!-- 使用 p:属性名=“属性值” -->
<bean id="dogBean" class="bean.Dog" p:age="3" p:name="tom" p:birth-ref="birthBean"/>
<!-- 这里获取的是当前时间-->
<bean id="birthBean" class="java.util.Date"/>
</beans>
2.4 c命名空间注入
c命名空间注入是简化构造注入的。
使用c命名空间注入的两个前提条件:
*第一:需在xml配置文件头部添加信息
*第二:需要提供构造方法
public class Peoplr {
private String name;
private int age;
private boolean sex;
//c命名空间是简化构造注入的。基于构造方法进行注入
public Peoplr(String name, int age, boolean sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
}
//配置
<!-- 第一步:在spring的配置文件头部添加 xmlns:c="http://www.springframework.org/schema/c" -->
<!-- 使用:
c:_0 下标方式
c:name 参数方式
-->
<bean id="p" class="bean.Peoplr" c:_0="lg" c:_1="23" c:_2="1">
</bean>
2.5 util命名空间
使用util命名空间可以让 配置复用
使用前提:在spring文件头部添加配置信息。如下:
xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
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
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd ">
public class MyDataSource1 implements DataSource {
//Properties属性类对象,这是一个Map集合,key和value都是String类
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
}
public class MyDataSource2 implements DataSource {
//Properties属性类对象,这是一个Map集合,key和value都是String类
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
//它们两的成员是一样的,当它们所想要配置的还都是一样的bean时(就bean的id不同),用util复用简单
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
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
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd ">
<!--引入util命名空间
在spring的配置文件头部添加:
xmlns:util="http://www.springframework.org/schema/util"
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
-->
<util:properties id="prop">
<prop key="driver">com.cj.host.lg</prop>
<prop key="url">com.jdbc.host://888</prop>
<prop key="username">root</prop>
<prop key="password">8888888</prop>
</util:properties>
<!--因 源1,2的属性的name都是properties,故使用util 复用出一个 properties ,设定id为“prop”
于是,在源1,2的bean 配置中, 属性property name="properties"直接用 ref=“___”-->
<!-- 数据源1-->
<bean id="ds1" class="com.powernode.spring6.jdbc.MyDataSource1">
<property name="properties" ref="prop"/>
</bean>
<!-- 数据源2-->
<bean id="ds2" class="com.powernode.spring6.jdbc.MyDataSource2">
<property name="properties" ref="prop"/>
</bean>
</beans>
主要是针对集合的对象使用的。
2.6基于XML的自动装配
*spring还可以完成自动化的注入,自动化注入又称自动装配。可根据名字进行自动装配,也可根据类型。
2.6.1根据名称自动装配
public class OrderDao {
private static final Logger logger = LoggerFactory.getLogger(OrderDao.class);
public void insert(){
logger.info("美团外卖保存信息。。");
}
}
public class OrderService {
private OrderDao order;
public void setOrderDao(OrderDao orde) {
this.order = orde;
}
public void generate(){//生成订单的业务方法
order.insert();
}
}
<!--根据名字自动装配-->
<!--自动装配是基于set注入实现的-->
<bean id="osBean" class="com.powernode.spring6.service.OrderService" autowire="byName"/>
<!--id一般也叫做bean 的名称-->
<!--根据名字进行自动装配时,被注入的对象的bean的id不能随便写,怎么写:set方法的方法名去掉set,剩余单词首字母小写-->
<bean id="orderDao" class="com.powernode.spring6.dao.OrderDao"/>
*需要特别注意的是:根据名字进行自动装配时,被注入的对象的bean的id的写法。
2.6.2根据类型进行自动装配
public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert(){
logger.info("数据库正在保存信息");
}
}
public class VipDao {
private static final Logger logger = LoggerFactory.getLogger("VipDao");
public void insert(){
logger.info("正在保存vip信息");
}
}
public class CustomerService {
private UserDao userDao;
private VipDao vipDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setVipDao(VipDao vipDao) {
this.vipDao = vipDao;
}
public void save(){
userDao.insert();
vipDao.insert();
}
}
<!--根据类型 进行自动装配-->
<!--也是基于set方法的-->
<!--根据类型进行自动装配时,在有效的配置文件中,某种类型的实例只能有一个-->
<bean class="com.powernode.spring6.dao.UserDao"></bean>
<bean class="com.powernode.spring6.dao.VipDao"></bean>
<bean id="cs" class="com.powernode.spring6.service.CustomerService" autowire="byType"></bean>
2.7spring引入外部属性配置文件
*我们都知道编写数据源的时候是需要连接数据库的信息的,例如:driver url username password 等信息。这些信息可以单独写到一个属性配置文件里吗?这样更方便用户修改。--可
*第一步:写一个数据源类,提供相关属性
jc.driverClass=com.mysql.cj.jdbc.Driver
jc.url=jdbc:mysql://localhost:88888888/spring6
jc.username=root
jc.password=654321
public class MyDateSource implements DataSource {//可以把数据源交给spring容器来管理
private String driver;
private String url;
private String username;
private String password;
public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--
引入外部的properties文件
第一步:引入context命名空间
第二步:使用标签context:property-placeholder的location属性来指定属性配置文件的路径
location默认从类的根路径下开始加载资源
-->
<context:property-placeholder location="jdbc"/>
<!--配置数据源-->
<bean id="ds" class="com.powernode.spring6.jdbc.MyDateSource">
<!--怎么取值呢?第三步:${key}-->
<property name="driver" value="${jc.driverClass}"></property>
<property name="url" value="${jc.url}"></property>
<property name="username" value="${jc.username}"></property>
<property name="password" value="${jc.password}"></property>
</bean>
</beans>
3.bean作用域
3.1单例和多例
1.spring默认情况下是如何管理Bean的: 默认情况下Bean是单例的。(单例:singleton) 在spring上下文初始化的时候实例化。new ClassPathXmlApplicationContext("spring-scope.xml") 每一次调用getBean()方法时,都返回那个单例的对象(同一个)。 2.当将bean的scope属性设置为prototype时:(prototype:原型) bean是多例的。 spring上下文初始化时,并不会初始化这些 prototype的bean,即加载配置文件时,不会new一个bean出来 每一次调用getBean()方法时,实例化该bean对象。
scope属性的两个值:singleton-单例(默认情况下都是单例);prototype-多例
scope属性的一些另外的值:request-一个请求对应一个bean;session-一个会话一个bean
request和session仅限于在WEB应用中使用。
其实scope还有其他属性值:global session、application、websocket、自定义scope
3.2自定义scope
线程级别的scope,在同一个线程中,获取的Bean都是同一个。跨线程则是不同对象:
第一步:自定义scope。(实现scope接口)
spring内置了线程范围的类:org.springframework.context.support.SimpleThreadScope,可直接用。
第二步:将自定义的scope注册到spring容器中。
4.GoF之工厂模式
*设计模式:一种可以被重复利用的解决方案。
*GoF(gang of four--四人帮)《设计模式》一书由四个人合力著作而成。
*该书有23种设计模式,除这些外,还有其他模式,如JavaEE的设计模式(DAO,MVC模式)
&工厂模式是 解决 对象创建 问题的,所以工厂模式属于创建型设计模式。这里为何学习工厂模式?因为spring框架底层使用了大量的工厂模式。
4.1工厂模式的三种形态
1.简单工厂模式(Simple Factory)不属于23种设计模式之一。简单工厂模式又称:静态工厂方法模式。 简单工厂模式是工厂方法模式的一种特殊实现。
2.工厂方法模式(Factory Method):23种设计模式之一
3.抽象工厂模式(Abstract Factory):23种设计模式之一
4.2简单工厂模式
简单工厂模式的角色包括三个:
*抽象产品角色
*具体产品角色
*工厂类角色
简单工厂模式的代码:
package com.powernode.simple.factory;
//抽象类角色
public abstract class Weapon {
public abstract void attack();
}
package com.powernode.simple.factory;
//具体类角色
public class Tank extends Weapon{
@Override
public void attack() {
System.out.println("坦克开大炮!!!");
}
}
package com.powernode.simple.factory;
//工厂类角色
/* 静态方法。要获取什么产品?就看你传什么参数,传Tank获取坦克;传Fighter获取歼20,传Dagger获取匕首
* 简单工厂模式种有一个静态方法,所以被称为:静态方法工厂模式
* */
public class WeaponFactory {
public static Weapon get(String weaponType){
if ("TANK".equals(weaponType)) {
return new Tank();
}else if ("FIGHTER".equals(weaponType)){
return new Fighter();
}else if ("DAGGER".equals(weaponType)){
return new Dagger();
}else {
throw new RuntimeException("不支持该武器的生产。我们是卖鱼的");
}
}
}
package com.powernode.simple.factory;
//这是客户端程序。
public class Test {
public static void main(String[] args) {
// 需要坦克
// 对于客户端来说,坦克的生产细节,客户端不需要关心,客户只需向工厂索要即可
// 简单工厂模式达到了什么呢?职责分离。客户端不需要关心产品的生产细节。
// 客户端只负责消费,工厂负责生产。一个负责生产,一个负责消费。生产者和消费者分离了。这就是简单工厂模式的作用
Weapon tank = WeaponFactory.get("TANK");
tank.attack();
// 需要歼20
& 简单工厂模式解决的问题:(优点)
客户端程序不需要关心 对象的创建细节,需要 哪个对象时,只需要 向工厂索要即可,初步实现了责任的分离。
客户端 只负责 ”消费“;工厂 负责”生产“;生产和消费 分离。
&简单工厂模式的 缺点:
*缺点1:假设现在要扩展一个新的产品,WeaponFactory工厂类的代码是需要修改的,违背OCP
*缺点2:工厂类的责任比较重大,不能出现任何问题,因为这个工厂负责所有产品的生产,称为 全能类。 甚至有人把它称为 上帝类。
这个工厂类一旦出问题,整个系统必然全部崩溃。(不用把鸡蛋放到同一个篮子里)
4.3工厂方法模式
1、工厂方法模式 可以解决简单工厂模式当中的OCP问题 怎么解决:一个工厂只生产一种产品 这样工厂就不是全能类了 另外,也符合OCP 2.工厂方法模式中的角色: *抽象类产品 Weapon *具体类产品 Tank Dagger *抽象工厂角色WeaponFactory *具体工厂角色Tank Factory 3.工厂方法模式的优点: 当你扩展一个产品的时候,符合OCP原则,因为只需要添加两个类,一个类是具体产品类,一个类是具体工厂类。都是添加类,没有修改之前的代码,所以符合OCP。 *一个调用者想创建一个对象,只要知道其名称就可以了。 *扩展性高。如果想增加一个产品,只要扩展一个工厂类就可。 *屏蔽产品的具体实现,调用者只关心产品的接口。 4.工厂方法模式的缺点: 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加, 在一定程度上 增加了系统的复杂度,同时也增加了系统具体类的依赖。
5.Bean的实例化方式
spring为Bean提供了多种实例化方式,通常包括四种方式。
1.通过构造方法实例化
2.通过简单工厂模式实例化
3.通过factory-bean实例化
4.通过Factory-Bean接口实例化
1.构造方法形式
package com.powernode.spring6.bean;
public class springBean {
}
<!--spring提供的第一种实例化方式:在spring配置文件中直接配置类全路径,spring会自动调用类中的无参数构造方法来实例化bean-->
<bean id="sb" class="com.powernode.spring6.bean.springBean"/>
若类定义中没有自己写的无参构造方法的话,它默认有这么个无参构造方法:public 类名 {}//括号内为空
需要注意的是,加载spring的xml配置文件时,文件中所有bean的无参数构造方法都会被执行
2.简单工厂模式:factory-method
告诉spring框架 调用 哪个类 的 哪个方法 来获取bean。 故只需 class , factory-method
package com.powernode.spring6.bean;
public class Star {
public Star()
{
System.out.println("star的无参构造方法执行");
}
}
public class StarFactory {
//简单工厂方法模式中:有个静态方法
public static Star get(){
//该star对象最终实际上创建的时候还是我们负责new的对象
return new Star();
}
}
<!--spring提供的第二种实例化方式:通过简单工厂模式。你需要在spring配置文件中公司spring框架,调用哪个类的哪个方法获取bean-->
<!--factory-method属性指定的是工厂类当中的静态方法,也就是告诉spring框架,调用这个方法,可以获取bean-->
<bean id="star" class="com.powernode.spring6.bean.StarFactory" factory-method="get"/>
3.工厂方法实例化(factory-bean)
package com.powernode.spring6.bean;
public class Gun {
public Gun() {
System.out.println("一枪毙了你");
}
}
public class GunFactory {
//工厂方法模式中,具体工厂角色 的方法是:实例方法
public Gun get() {
//实际上new这个对象还是程序员new的
return new Gun();
}
}
<!--spring提供的第三种实例化方式:工厂方法模式。通过factory-bean属性+factory-method属性共同完成-->
<!--告诉spring框架,调用 哪个对象 的 哪个方法 来获取Bean-->
<bean id="gunfactory" class="com.powernode.spring6.bean.GunFactory"/>
<!-- 以下配置很关键! factory-bean属性告诉spring调用哪个对象,factory-method告诉spring调用该对象的哪个方法-->
<bean id="gun" factory-bean="gunfactory" factory-method="get"/>
bean的创建是两个。 先要创建一个工厂的bean,再用该bean即其对应的get方法 来创建另一个bean。
对比:简单工厂方式创建2个类,1个bean;工厂方法方式创建2个类,2个bean.
简单工厂 的工厂有静态方法,故指定工厂类,及factory-method即可;
工厂方法 的工厂 是 实例方法,得先 有工厂类 的bean对象,才能去指定factory-bean,及factory-method。
4.Factory-Bean接口实例化
上面第三种方式中,factory-bean是我们自定义的,factory-method也是我们自己定义的。
在Spring中,当你编写的类 直接实现FactoryBean接口(implements FactoryBean)后,factory-bean就不需要指定了,factory-method也不需指定了。
factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向getObject()方法。
package com.powernode.spring6.bean;
public class Person {//普通的Bean
public Person() {
System.out.println("person无参构造方法执行了");
}
}
import org.springframework.beans.factory.FactoryBean;
public class PersonFactory implements FactoryBean {
//PersonFactory也是个Bean。只是较特殊,叫工厂Bean
//通过工厂Bean。可获得一个普通的Bean
@Override
public Object getObject() throws Exception {
//最终这个bean的创建还是程序员自己new的。
return new Person();
}
@Override
public Class<?> getObjectType() {
return null;
}
}
<!--spring提供的第4种实例化方式:通过FactoryBean接口实现-->
<!--该方式实际上是第三种的简化。-->
<!--由于你编写的类实现了FactoryBean接口,所以这个类是一个特殊的类,不需你手动指定:factory-bean、factory-method-->
<!--通过一个特殊的Bean:工厂Bean。来返回一个普通的Bean Person对象-->
<bean id="person" class="com.powernode.spring6.bean.PersonFactory"/>
5.1BeanFactory 和 FactoryBean的区别
BeanFactory:
Spring IoC容器的顶级对象,BeanFactory被翻译为”Bean工厂“,在Spring IoC容器中,Bean工厂负责创建Bean对象。 BeanFactory是工厂。
FactoryBean:
它是一个Bean,是一个能够 辅助Spring实例化其他Bean对象的一个Bean。
在Spring中,Bean可被分为两类:
*第一类:普通Bean
*第二类:工厂Bean(记住:工厂Bean也是一种Bean,只不过其较特殊,它可辅助Spring实例化其他对象)
6.bean的生命周期
6.1什么是bean的生命周期
Spring是管理Bean对象的工厂。它负责对象的创建和销毁等。
生命周期是:对象从创建开始到最终销毁的整个过程。
6.2Bean的生命周期之五步
Bean的生命周期可粗略划分为五大步:
1.实例化Bean
2.Bean属性赋值
3.初始化Bean
4.使用Bean
5.销毁Bean
package com.powernode.spring6.bean;
//Bean的生命周期按照5步的话:
/*
* 1.实例化Bean(调用无参数构造方法)
* 2.Bean属性赋值(调用set方法)
3.初始化Bean(会调用Bean的init方法。注意:这个init方法需要自己写,自己配)
4.使用Bean
5.销毁Bean(会调用Bean的destory方法。注意:destory方法需要自己写,自己配)
* */
public class User {
private String name;
//这个方法需要自己写,自己配。名字随意
public void destroyBean(){
System.out.println("第五步:销毁Bean");
}
//这个方法需要自己写,自己配。名字随意
public void initBean(){
System.out.println("第三步:初始化Bean");
}
public void setName(String name) {
System.out.println("第二步:给对象的属性赋值");
this.name = name;
}
public User() {
System.out.println("第一步:user无参数构造方法执行");
}
}
<!--需要手动指定初始化方法和销毁方法-->
<bean id="user" class="com.powernode.spring6.bean.User"
init-method="initBean" destroy-method="destroyBean">
<property name="name" value="lg"/>
</bean>
@Test
public void testBeanLifeLiveFive(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println("第四步:使用bean"+user);
// 注意:必须手动关闭spring容器,这样才会销毁Bean
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();
6.3Bean的生命周期之7步
在以上的5步中,第三步是初始化Bean,如果你还想在Bena 初始化前 和 初始化后 添加代码,可以加入”Bean后处理器”。
编写一个类实现BeanPostProcessor类,并且重写before和after的方法:
1.实例化Bean
2.Bean属性赋值
3.执行“Bean后处理器”的before方法
4.初始化Bean
5.执行“Bean后处理器”的after方法
6.使用Bean
7.销毁Bean
7步就是比6.2五步的代码中,多写一个类实现BeanPostProcessor接口,并overridet它before和after方法,再在配置文件中多配个bean
package com.powernode.spring6.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
//日志Bean后处理器
public class LogBeanPostProcessor implements BeanPostProcessor{
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行Bean后处理器的before方法");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
// 方法有两个参数:1.刚创建的Bena对象;2.bean的名字
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行Bean后处理器的after方法");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
<!--配置Bean后处理器-->
<!--注意:这个Bean后处理器将作用于整个配置文件的所有bean-->
<bean class="com.powernode.spring6.bean.LogBeanPostProcessor"/>
6.3Bean不同的作用域有不同的管理方式
Spring容器只对scope = singleton的Bean进行完整的生命周期管理。
如果scope = prototype的Bean,Spring容器只负责将该bean初始化完毕,等客户端程序一旦获取到该Bean后,Spring容器就不再管理该对象的生命周期了。
6.4 自己new的对象如何让Spring管理
public void testRegisterBean(){
// 自己new的对象
Student student = new Student();
System.out.println(student);
// 将以上自己new的对象纳入spring容器来管理。半路上交由spring来管理
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerSingleton("studentBean",student);
// 从spring容器中获取
Object studentBean = factory.getBean("studentBean");
System.out.println(studentBean);
7.Bean的循环依赖问题
7.1什么是循环依赖
A对象中有B属性,B对象中有A属性。这就是循环依赖。我依赖你,你依赖我。
7.2循环依赖之单例和set模式下
package com.powernode.spring.bean;
public class Husband {
private String name;
private Wife wife;
public void setName(String name) {
this.name = name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
public class Wife {
private String name;
private Husband husband;
public void setName(String name) {
this.name = name;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
<!--singleton+set模式下的循环依赖是没有任何问题的-->
<!--singleton表示在整个Spring容器中是单例的,独一无二的对象-->
<bean id="husbandBean" class="com.powernode.spring.bean.Husband" scope="singleton">
<property name="name" value="gdf"></property>
<property name="wife" ref="wifeBean"></property>
</bean>
<bean id="wifeBean" class="com.powernode.spring.bean.Wife" scope="singleton">
<property name="name" value="lxy"></property>
<property name="husband" ref="husbandBean"></property>
</bean>
7.2解决循环依赖的本质
在singleton + setter 模式下,为什么循环依赖不会出现问题?spring是如何应对的?
主要原因是,在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:
第一个阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行“曝光”(不等属性赋值就曝光--我有对象了,你可以用我,虽现在没赋值,但我是唯一的,之后会赋值)
第二个阶段:Bean“曝光”之后,再进行属性的赋值(调用set方法)
核心解决方案:实例化对象和对象的属性赋值 分为两个阶段来完成的。
注意:只有在scope=singleton的情况下,Bean才会采取提前“曝光”的措施。
7.3循环依赖之多例和set模式下
<!--prototype+set模式下的循环依赖,存在问题,会出现异常!-->
<!--BeanCurrentlyInCreationException:当前的Bean正处于创建中异常-->
<!--注意:当两个Bean的scope都是prototype时,才出现异常。若其中任何一个的scope为singleton,就不会出现异常-->
<bean id="husbandBean" class="com.powernode.spring.bean.Husband" scope="singleton">
<property name="name" value="a"></property>
<property name="wife" ref="wifeBean"></property>
</bean>
<bean id="wifeBean" class="com.powernode.spring.bean.Wife" scope="prototype">
<property name="name" value="b"></property>
<property name="husband" ref="husbandBean"></property>
</bean>
7.4循环依赖之构造方式
莫得说,会出错,无法解决。
<!--构造注入,这种方式下循环依赖是否有问题-->
<!--注意:基于构造注入的方式下产生的循环依赖也是无法解决的。所以编写代码时一定要注意-->
<bean id="husbandBean" class="com.powernode.spring.bean2.Husband" scope="singleton">
<constructor-arg name="name" value="a"></constructor-arg>
<constructor-arg name="wife" ref="wifeBean"></constructor-arg>
</bean>
<bean id="wifeBean" class="com.powernode.spring.bean2.Wife" scope="singleton">
<constructor-arg name="name" value="b"></constructor-arg>
<constructor-arg name="husband" ref="husbandBean"></constructor-arg>
</bean>
7.5Spring解决循环依赖的机理
Spring为什么可以解决singleton + setter模式下的依赖循环?
根本原因在于:这种方式可以做到将”实例化Bean“ 和 ”给Bean属性赋值“ 这两个动作分开去完成。
实例化Bean的时候:调用无参数构造方法完成。此时可以先不给属性赋值,可以提前将该Bean对象”曝光“给外界
给Bean属性赋值的时候:调用Setter方法来完成
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合中去(我们可以称之为缓存),所有的单例Bean全部实例化完成后,以后我们再慢慢调用Setter方法给属性赋值。这样就解决了循环依赖的问题。
8.回顾反射机制
8.1调用方法的四要素
* 分析:调用一个方法, 当中含有几个要素?四要素。 * 第一要素:调用哪个对象 * 第二要素:调用哪个方法 * 第三要素:调用方法的时候传什么参数 * 第四要素:方法执行之后的返回结果 * 调用哪个对象的哪个方法,传什么参数,返回什么值 * * 即使是使用反射机制来调用方法,也同样需要具备这四个元素。
public class SomeService {
public void doSome() {
System.out.println("public void doSome()执行");
}
public String doSome(String s){
System.out.println("public String doSome(String s)执行");
return s;
}
public String doSome(String a,int i){
System.out.println("public String doSome(String a,int i)执行");
return a + i;
}
}
public class Test {
public static void main(String[] args) {
// 不使用反射机制下调用这些方法
SomeService someService = new SomeService();
someService.doSome();
/*
* 分析:调用一个方法, 当中含有几个要素?四要素。
* 第一要素:调用哪个对象
* 第二要素:调用哪个方法
* 第三要素:调用方法的时候传什么参数
* 第四要素:方法执行之后的返回结果
* 调用哪个对象的哪个方法,传什么参数,返回什么值
*
* 即使是使用反射机制来调用方法,也同样需要具备这四个元素。
* */
String zs = someService.doSome("zs");
System.out.println(zs);
String ls = someService.doSome("ls", 9);
System.out.println(ls);
}
}
8.2反射调用方法
1.用Class.forName(”类名全路径“)获取到类对象aclazz;
2.用得到的类对象aclazz.getDeclaredMethod(方法名,方法的参数表的类)获取方法
3.用得到的类对象aclazz来创建一个对象。Object obj = aclazz.newInstance();
4.调用所得的方法 方法对象.invoke(对象,传参)得到返回值
import java.lang.reflect.Method;
public class test2 {
public static void main(String[] args) throws Exception {
// 使用反射机制怎么调用方法
// 首先获取类
Class<?> aClass = Class.forName("com.powernode.reflect.SomeService");
// 获取方法
Method doSomeMethod = aClass.getDeclaredMethod("doSome", String.class, int.class);
// 调用方法
// 四要素:调用哪个对象的哪个方法,传什么参数,返回什么值
// obj要素:哪个对象
// doSomeMethod:哪个方法
// ”ls“,250 要素:传什么参数
// returnValue 要素:返回值
Object obj = aClass.newInstance();
Object returnValue = doSomeMethod.invoke(obj, "ls", 250);
System.out.println(returnValue);
}
}
8.3SpringDI核心实现
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class test4 {
/*
* 需求:
* 假设有已知信息:
* 1.有一个类,类名:com.powernode.reflect.User
* 2.这个类符合javabean规范(属性私有化,对外提供公开的setter和getter方法)
* 3.还知道这个类当中有一个属性,属性名是:age
* 4.并知道属性age的类型是int
* 请使用反射机制调用set方法,给User对象的属性age赋值
* */
public static void main(String[] args) throws Exception{
String className = "com.powernode.reflect.User";
String propertyName = "age";
// 通过反射机制调用setAge(int)方法
// 获取类
Class<?> aClass = Class.forName(className);
// 获取方法名
String setMethodName = "set"+propertyName.toUpperCase().charAt(0)+propertyName.substring(1);
// 根据 属性名 来获取 属性类型
Field field = aClass.getDeclaredField(propertyName);
// 获取方法
Method setMethod = aClass.getDeclaredMethod(setMethodName, field.getType());
// 准备对象
Object obj = aClass.newInstance();
// 调用方法
setMethod.invoke(obj,23);//obj这个对象就会调用setMethod了
System.out.println(obj);
}