Spring家族-spring基础知识

看了一段时间视频教程,还是需要总结一下,不然很容易忘的,笔记如下。

一、基本概念

1、三大框架SSM:Spring、SpringMvc、Mybaits,现在用的比较多是springboot

2、程序间的依赖关系:类之间的依赖、方法间的依赖,spring理念就是最大限度的降低耦合

3、IOC(控制反转)

        IoC本质:一个java对象的生命周期(创建、销毁)统统交给框架,而不在由程序员控制(即不在由程序员 通过编码,例如new Object,创建对象),程序员只需要关注自己的业务逻辑就行,需要对象直接从容器中获取即可。但是想提一点:框架如何知道你需要哪些对象呢?框架是不知道,所以还得需要程序员告诉框架,我后面需要用到哪些对象,你要提前帮我创建好

IoC作用:降低程序间的耦合度,耦合度不能消除,只能降低

4、AOP

        AOP面向切面编程,是一种编程思想,它减少重复代码,提高开发效率,维护方便(这也是动态代理模式优点)

        AOP底层是利用动态代理,Spring实现动态代理方式有两种:jdk proxy和cglib

        Spring 的AOP中有很多相关术语,这里用通俗的话进行说明(老外就是喜欢创造名词):

名词 解释
Aspect(切面) 实际就是动态代理模式中代理对象(增强的类)
Pointcut(切入点) 实际是被代理对象中的方法,要被增强的方法
Advice(通知) 实际为代理对象中增强逻辑的那部分代码。有前置通知(Before)、后置通知(AfterReturning)、异常通知、最终通知(After)、环绕通知(指方法前后都增强)

        我个人感觉,Spring实现的AOP并不是很优雅,不方便,增加了用户的使用难度。

扫描二维码关注公众号,回复: 13216636 查看本文章

5、动态代理

        在java中使用动态代理,一般分为两种:基于接口的动态代理(使用jdk提供的Proxy类)和基于子类的动态代理(使用第三方cglib Enhance,但是子类不能是final类型的类,因为cglib是通过继承方式实现子类来增强

6、自动装配

        通常我们自己设置属性名称以及属性值(例如:<property name="name" value="123"></property>)这个是手动装配。那么自动装配就是spring框架根据属性名称或者属性类型自动识别并且注入,即用户不需要手动设置property等则为自动装配。

<?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">

<!-- 自动装配 byName方式, 类中的属性名字必须与bean id一样 -->
<bean id="emp" class="com.example.Employee" autowire="byName">
  <!-- <properties name="dept" ref="dept"/> 手动状态 -->
</bean>

<!-- 自动装配 byType方式 -->
<bean id="emp2" class="com.example.Employee" autowire="byType">
  <!-- <properties name="dept" ref="dept"/> 手动状态 -->
</bean>

<bean id="dept" class="com.example.Department">
</bean>

</beans>

二、Spring容器接口

Spring创建容器有两个接口:AppliactionContext和BeanFactory,两者对比:

接口 特点 方法
AppliactionContext

1、创建对象采用立即加载策略,即立刻实例化对象

2、创建的对象是单例的

以下三个类均实现了该接口

ClassPathXmlApplicationContext:读取classpath路径下的xml文件

FileSystemXmlApplicationContext:读取文件系统下xml文件

AnnotationConfigApplicationContext:用于读取注解创建容器

BeanFactory

创建对象采用延迟加载

AppliactionContext继承了BeanFactory

ClassPathResource classPathResource = new ClassPathResource("beans.xml");
BeanFactory beanFactory = new XmlBeanFactory(classPathResource);

三、Spring Bean对象

3.1、bean的分类

Spring中有两种Bean:普通bean,工厂bean(FactoryBean)

普通bean:用于的定义普通类

 工厂bean:实现接口FactoryBean的类,就是工厂bean,本质上就是工厂模式,注入类型和获取到的类型不一致,真正的实例对象是在getBean的时候才会创建

如下代码所示:

//蜘蛛侠玩具
public class SpiderMan {

    @Override
    public String toString() {
        return "This is Spider Man!";
    }
}

//玩具工厂, 生产SpiderMan
public class ToyFactory implements FactoryBean<SpiderMan> {
    @Override
    public SpiderMan getObject() throws Exception {
        return new SpiderMan();
    }

    @Override
    public Class<?> getObjectType() {
        return SpiderMan.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
<?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="toyFactory" class="com.example.ToyFactory"/>

</beans>
public class MyTest {
    @Test
    public void aaa() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        // 这里只能写SpiderMan,不能写ToyFactory.class,会报错
        SpiderMan spiderMan = context.getBean("toyFactory", SpiderMan.class);
        System.out.println(spiderMan);
    }

}

源码底层是怎么实现的呢?

1)通过上面的配置可知,注入到容器的时候,类型为ToyFactory类型

2)在获取真正实例对象的时候,框架进行了判断,如果容器中bean,实现了FactoryBean接口,则去调用接口中getObject方法创建真的实例对象,所以在这个地方可能性能有一些损失 

3.2、bean对象创建方式

        Spring提供两种方式创建bean对象:一种是xml方式,一种是注解方式(jdk1.8以后),目前使用的最多还是注解方式。关于注解方式,可参考《spring家族-spring基础知识注解版

3.2.1、基于xml三种创建方式

在idea中resources目录右键,选择xml,spring config就可以创建xml文件

基于xml文件有三种方式:直接使用构造方法、利用工厂创建对象、利用工厂静态方法创建对象

<!-- 构造方法 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
</bean>

<!-- 利用工厂 -->
<bean id="myInstanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="myInstanceFactory" factory-method="getAccountService"></bean>

<!-- 利用工厂静态方法 -->
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>

3.2.2、将第三方一个纯java普通类注入到spring容器

        有些时候引入一个第三方jar包,需要将某个普通java类(非接口,没有任何注解,纯java类)注入的容器中,那么我们应该如何做呢?xml方式和注解方式均可

通过xml方式:

1)在idea中resources目录右键,选择xml,spring config就可以创建xml文件,名字随意,这里叫spring-bean.xml

2)xml文件内容

<?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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!--
     spring的属性加载器,加载properties文件/yml文件中的属性
     通过获取配置文件中的配置项
    -->
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:application.properties"/>
        <property name="fileEncoding" value="utf-8" />
    </bean>

    <!--
        <bean id="yamlProperties" class="org.springframework.beans.factory.config.YamlPropertiesFactoryBean">
            <property name="resources" value="classpath:application-demo.yml"/>
        </bean>
        <context:property-placeholder properties-ref="yamlProperties"/>
    -->
    <bean name="zkClient" class="com.example.ZKClientImpl">
        <constructor-arg name="zkServers" value="${zk.address}"/>
        <constructor-arg name="connectionTimeout" value="${zk.port}"/>
    </bean>
    <!--
    <bean name="" class="com.example.ZKClientImpl">
        <constructor-arg name="zkServers" value="127.0.0.1"/>
        <constructor-arg name="connectionTimeout" value="2181"/>
    </bean>
    -->
</beans>

3)在springboot启动进行注入,注意这里classpath,指的是resources根目录

public class DemoApplication {
    public static void main(String[] args) {
                ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-bean.xml");
        ZKClientImpl client = context.getBean("zkClient", ZKClientImpl.class);
        System.out.println(client);
        context.close();
    }

}

通过注解方式:

注解方式比较简单,只需要创建一个配置类,用@Configuration标注,然后用@Bean标注一个方法,具体内容如下:

@Configuration
public class ZkClientConfig {
    @Value("${zkClusters.address}")
    private String cluster;
    @Value("${zkClusters.timeout}")
    private int timeout;

    private ZkClientService zkClient;

    @Bean
    ZkClientService createZkClient() {
        zkClient = new ZKClientImpl(cluster, timeout);
        return zkClient;
    }

    @PreDestroy
    private void destroy() {
        zkClient.close();
    }
}

3.3、bean对象作用域范围 

在xml文件中bean节点下面有一个属性scope,代表作用范围,取值如下:

singleton

单例的 默认值

prototype

多例的

request

作用于web应用请求范围

session

作用于web应用会话范围

global-session

作用于集群环境的会话范围(全局范围),如果不是集群则同session属性

3.4、bean对象生命周期(重点)

3.4.1、生命周期

        声明周期,大体流程可以规划为:实例化(即new)--> ②属性赋值(执行setter方法)-->③ 初始化(执行 init-method)--> ④使用bean对象 --> ⑤销毁 (执行destroy-method)。注意:init-method、destroy-method是在bean标签中的一个属性,这两个属性可以指定类中的方法,我们也可以用注解@PostConstruct,@PreDestroy进行标注初始化和销毁的两个方法

3.4.2、生命周期扩展性

        在面试的时候,上面5步骤可能不够完善,面试官可能会追问,这个流程有什么扩展性?答案是有的。

        经过扩展后,声明周期变为:实例化(即new)-->属性赋值(执行setter方法)--> bean后置处理器的before方法 --> 属性后置处理afterPropertiesSet -->初始化(执行 init-method)-->  bean后置处理器的after方法 --> 使用bean对象 --> 销毁 (执行destroy-method)

        这个地方需要去理解,千万不要死记硬背,我曾经尝试过背,但过一段时间就又忘了。后来我突然想明白了一件事情:

1)原始的生命周期5个状态中,哪个步骤可用于扩展(增强)呢?

答:实例化,这个阶段不行,这个过程完全由jvm操作,我们没有办法介入

       ④使用bean对象,对象都在使用了,我们在这个时候去扩展已经来不及了

       ⑤销毁,对象都要销毁了,我们也没必要在去扩展了

        所以剩下只有②属性赋值,③ 初始化

2)属性赋值阶段扩展-接口InitializingBean

        通常,在这个阶段扩展要在现有属性赋值之后执行。如果存在后置处理器,则先执行后置处理器before方法,再后执行afterPropertiesSet

public interface InitializingBean {
	void afterPropertiesSet() throws Exception;
}

3)初始化阶段扩展-接口BeanPostProcressor

        初始化阶段,我们可以在初始化前(具体在属性赋值后)和初始化后进行扩展。Spring为我们提供了一个接口BeanPostProcressor(后置处理器)。

        这里是BeanPostProcessor接口,不是BeanFactoryPostProcessor

注意事项:

1)这个接口需要单独定义一个实现类,并且注入到Spring中

2)以后所有Bean在初始化阶段,前后,都会执行后置处理器中的before,after方法

3)后置处理可以有多个

3.4.3、生命周期扩展性应用

// 创建bean,实现InitializingBean接口
public class Orders implements InitializingBean {
    private String oname;

    public Orders() {
        System.out.println("第一步,执行Orders无参数构造");
    }

    public void setOname(String oname) {
        this.oname = oname;
        System.out.println("第二步,Set方法");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("第四步,AfterProperties方法");
    }

    public void initMethod() {
        System.out.println("第五步,initMethod方法");
    }

    public void destroyMethod() {
        System.out.println("第八步,destroyMethod方法");
    }

    @Override
    public String toString() {
        return "Orders{" +
                "oname='" + oname + '\'' +
                '}';
    }
}
//创建Bean后置处理器,实现BeanPostProcessor接口
public class MyBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //这里根据bean类型,做一些特殊操作
        /*
         *  if (bean instance XXX && beanName.startsWith("AAA")) {
         *   //进行增强处理
         *   return bean;
         * } else {
         *   //do nothing
         *   return bean;
         * }
         */
        System.out.println("第三步,后置处理器前置方法");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第六步,后置处理器后置方法");
        return bean;
    }
}
<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 -->
    <bean id="order" class="com.example.Orders" init-method="initMethod" destroy-method="destroyMethod">
        <property name="oname" value="jd-order"/>
    </bean>

    <!-- 注入后置处理器 -->
    <bean id="myBeanProcessor" class="com.example.MyBeanProcessor"/>
</beans>
public class MyTest {

    @Test
    public void testLife() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Orders order = context.getBean("order", Orders.class);
        System.out.println("第7步,应用bean对象,order="+order);
        context.close();
    }
}

/*
输出打印:
第一步,执行Orders无参数构造
第二步,Set方法
第三步,后置处理器前置方法
第四步,AfterProperties方法
第五步,initMethod方法
第六步,后置处理器后置方法
第7步,应用bean对象,order=Orders{oname='jd-order'}
第八步,destroyMethod方法

*/

        注意bean生命周期最后一个小知识点:假设分别注入A,B,C三个对象,执行构造器方法顺序是A,B,C,然而执行销毁方法顺序是反的,C,B,A。

四、IOC注入 - xml方式

        上面说了IoC实际是把对象的创建、属性初始化工作交给框架。这里的IoC注入实际上就是属性初始化流程。那么spring有两种注入方式:基于xml文件和基于注解的。

4.1、基于xml的注入

基于xml注入分为:基于构造方法和基于setter方法

<!-- 基于构造方法 其中有一个引用类型Date -->
<bean id="date-now" class="java.util.Date"></bean>
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
    <constructor-arg name="name" value="test"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
    <constructor-arg name="birthday" ref="date-now"></constructor-arg>
</bean>


<!-- 基于setter方法 -->
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
    <!-- java中代码为setUserName函数 -->
    <property name="userName" value="TEST"></property> 
    
    <!-- java中代码为setAge函数 -->
    <property name="age" value="19"></property>

    <!-- java中代码为setBirthday函数 -->
    <property name="birthday" ref="date-now"></property>
</bean>

当然也支持复杂类型注入,例如Map,List等,例如:

<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
        <property name="myMap"> <!-- Map类型 -->
            <map>
                <entry key="test1" value="value1"></entry>
                <entry key="test2"><value>value2</value></entry>
            </map>
        </property>
        <property name="myProps">  <!-- Properties类型 -->
            <props>
                <prop key="myProps1">props1-value1</prop>
                <prop key="myProps2">props2-value2</prop>
            </props>
        </property>
        <property name="myStrs"> <!-- 数组类型 -->
            <array>
                <value>Array-AAA</value>
                <value>Array-bbb</value>
            </array>
        </property>
        <property name="myList"> <!-- List类型 -->
            <list>
                <value>List-AAA</value>
                <value>List-bbb</value>
            </list>
        </property>
        <property name="mySet"> <!-- Set类型 -->
            <set>
                <value>Set-AAA</value>
                <value>Set-bbb</value>
            </set>
        </property>
</bean>

 五、Spring AOP

步骤:

  1. 定义要增强功能的类,例如class A,对于xml文件来说定义一个bean
  2. 定义如何增强功能类,例如class B,用于增强class A的功能,对于xml文件来说定义一个bean
  3. 定义切面以及切入点aop:config、aop:aspect以及通知类型

aop:before - - 前置通知

                method属性:用于指定Logger类中哪个方法是前置通知

                pointcut属性:用于指定切入点表达式,该表达式的含义是对业务层中那些方法增强

aop:after-returning - - 后置通知

aop:after-throwing  - - 异常通知

aop:after 最终通知

aop:round环绕通知

六、切入点表达式

切入点表达式作用:用于表示对类中某个方法进行增强

切入表达式格式:execution([权限修饰符][返回类型][类全路径][方法名称][参数列表])

切入点表达式举例:

  1. 全通配写法: *.*..*.*(..) ==> 用于整包 整类 所有方法增强
  2. 返回值可以使用通配符,表示任意返回值,例如:* com.itheima.service.impl.AccountServiceImpl.saveAccount()
  3. 包名可以使用通配符,表示任意包,但是有几级包就需要写几个*,例如:* *.*.*.*.AccountServiceImpl.saveAccount()  也可以写成  * *..AccountServiceImpl.saveAccount()
  4. 函数有形参,可以用..用于表示,例如:* com.itheima.service.impl.*.*(..)

xml文件中定义切入点表达式,可以被通知类型中进行引用

<aop:pointcut id=”” expression=””>

猜你喜欢

转载自blog.csdn.net/xxb249/article/details/103196658