spring学习1

前言

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 &lt; 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);


    }


         

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值