[Java]Spring框架学习笔记

Spring概述

Spring模块示意图:

Core Container:核心容器(IOC)
AOP+Aspects:面向切面编程模块
DataAccess/Integration:数据访问模块
Web:Spring开发Web应用模块

IOC

控制反转——控制并反转资源的获取方式,由程序员自己创建资源转变为由容器创建和设置资源。
DI: Dependency Injection 依赖注入
spring这个容器中,替你管理着一系列的类,前提是你需要将这些类交给spring容器进行管理,然后在你需要的时候,不是自己去定义,而是直接向spring容器索取,当spring容器知道你的需求之后,就会去它所管理的组件中进行查找,然后直接给你所需要的组件.

bean属性

id:bean的唯一标识
class:组件的全类名

bean的生命周期

通过scope属性设置bean的生命周期:
prototype 多实例:
1.在容器启动时不会创建实例,从容器中获取bean时才创建实例
2.每一次获取实例的结果都不一样
singleton 单实例:
1.在容器启动完成之前就创建好bean的实例
2.每一次获取实例的结果都是之前创建的实例
request:
Web环境下,对象与request生命周期一致
session:
Web环境下,对象与session生命周期一致
生命周期属性:
初始化方法:init-method
销毁方法:destroy-method

<!--
    单实例bean的生命周期:
        (容器开启时创建)构造器->初始化方法->销毁方法(容器关闭时销毁 ConfigurableApplicationContext中才有close方法)
    多实例bean的生命周期:
        获取bean(构造器->初始化方法)->容器关闭不会调用bean的销毁方法
    -->
    <bean id="book1" class="com.first.bean.Book" destroy-method="myDestory" init-method="myInit">
        <property name="bookName" value="xixixi"></property>
    </bean>

创建bean

1.创建一个空的bean:

<bean id="person1" class="com.first.bean.Person">
    </bean>
<!--
工厂模式:有一个专门帮我们创建对象的类帮我们创建对象
静态工厂:工厂本身不用创建对象,通过静态方法调用
实例工厂:工厂本身需要创建对象
-->

2.利用静态工厂创建bean:

<!--静态工厂-->
    <bean id="airPlane1" class="com.first.factory.AirPlaneStaticFactory" factory-method="getAirPlane">
    </bean>

3.利用实例工厂创建bean:

<!--实例工厂-->
    <bean id="airPlaneInstanceFactory" class="com.first.factory.AirPlaneInstanceFactory"></bean>
    <bean id="airPlane2" class="com.first.bean.Airplane" factory-bean="airPlaneInstanceFactory" factory-method="getAirPlane">
    </bean>

给bean注入值

1.set注入:
基本类型使用value注入,引用类型使用ref注入,默认注入null,若要给有默认值的属性注入null,则使用null标签.

<bean id="person1" class="com.first.bean.Person">
        <!--使用property标签为Person对象的属性赋值-->
        <property name="age" value="18"></property>
        <!--ref 引用外面的bean-->
        <property name="car" ref="car1"></property>
        <!--给有默认值的属性注入空值-->
        <property name="name">
            <null></null>
        </property>
    </bean>

2.使用有参构造器注入:
在bean中定义一个有参构造函数.

public Person(String name, Integer age, String gender, String email) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.email = email;
        System.out.println("调用有参构造器...");
    }

配置文件:

<bean id="person3" class="com.first.bean.Person">
        <!--调用有参构造器给bean赋值-->
        <constructor-arg name="age" value="20"></constructor-arg>
        <constructor-arg name="email" value="[email protected]"></constructor-arg>
        <constructor-arg name="gender" value="male"></constructor-arg>
        <constructor-arg name="name" value="hihi"></constructor-arg>
        <!--省略name属性时,必须严格按照参数顺序构造-->
    </bean>

3.名称空间注入
引入p名称空间:在beans标签中加入

xmlns:p="http://www.springframework.org/schema/p"
<!--名称空间:在xml中名称空间是用来防止标签重复的-->
    <bean id="person4" class="com.first.bean.Person" p:age="100" p:email="[email protected]" p:name="qwer" p:gender="female"></bean>

4.SPEL注入:

<!--SpEL(spring 表达式语言)测试-->
    <!--在SpEL中使用字面量-->
    <bean id="person4" class="com.first.bean.Person">
        <!--字面量:#{}--><!--使用运算符-->
        <property name="salary" value="#{12345.67*12}"></property>
        <!--引用其他bean的某个属性值-->
        <property name="name" value="#{book1.bookName}"></property>
        <!--引用其他bean-->
        <property name="car" value="#{car}"></property>
        <!--调用静态方法
        #{T(全类名).静态方法名(1,2)}
        -->
        <property name="email" value="#{T(java.util.UUID).randomUUID().toString().substring(0,5)}"></property>
        <!--调用其他非静态方法-->
        <property name="gender" value="#{book1.getBookName()}"></property>
    </bean>

5.注入list:

<bean id="person3" class="com.first.bean.Person">
        <property name="name" value="曹嘤嘤"></property>
        <property name="books">
            <list>
                <!--list标签体中添加每一个元素-->
                <bean id="book0001" class="com.first.bean.Book" p:author="吴承恩" p:bookName="西游记"></bean>
                <!--引用外部一个元素-->
                <ref bean="book0002"></ref>
            </list>
        </property>
    </bean>

6.注入map:

<bean id="person4" class="com.first.bean.Person">
        <property name="name" value="emmm"></property>
        <property name="maps">
            <map>
                <entry key="key01" value="张三"></entry>
                <entry key="key02" value="haha"></entry>
                <!--引用一个外部创建好的bean-->
                <entry key="key03" value-ref="car1"></entry>
            </map>
        </property>
    </bean>

使用utils名称空间注入:
引入utils名称空间

<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"
       <!--加上这一行-->
       xmlns:utils="http://www.springframework.org/schema/util"
       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-4.0.xsd">

定义一个map:

<utils:map id="myMap">
        <entry key="key1" value="掌声"></entry>
        <entry key="key2" value="29"></entry>
        <entry key="key3" value-ref="car1"></entry>
    </utils:map>

定义bean:

<bean id="person6" class="com.first.bean.Person">
        <property name="maps" ref="myMap">
        </property>
    </bean>

7.注入properties:

<bean id="person5" class="com.first.bean.Person">
        <property name="properties">
            <props>
                <prop key="username">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>
    </bean>

8.注入级联属性:

<bean id="person7" class="com.first.bean.Person">
        <property name="car" ref="car1"></property>
        <property name="car.price" value="500000"></property>
    </bean>

9.加载外部配置文件:

<context:property-placeholder location="classpath:jdbc.properties"/>

使用注解创建bean(官方推荐)

使用注解将组件快速的加入到容器中需要:

添加注解

spring有四个注解:
@Controller 控制器 给控制器(servlet)层的组件加
@Service 业务逻辑
@Repository 给数据库层(持久化,Dao层)的组件添加这个注解
@Component 给不属于以上几层的组件添加这个注解
注解可以随便加,spring底层不会验证组件是否如注解所说.

1.组件的id默认就是组件的类名首字母小写,设置组件id在注解后加括号,括号中设置id.
@Repository(“id”)
2.组件的作用域,默认是单例的
@Scope(value=“prototype”) //设置为多实例

引入context名称空间自动扫描

context:component-scan:自动组件扫描
base-package:指定扫描的基础包,spring会将基础包及下面所有包中加了注解的类自动扫描进ioc容器

<context:component-scan base-package="com.first"></context:component-scan>
   <!--使用context:exclude-filter指定扫描包时不包含的类-->
   <context:component-scan base-package="com.first">
       <!--扫描的时候可以排除一些不必要的组件
       type="annotation":指定排除规则,按照注解进行排除.标注了指定注解的类不要
       expression="":注解的全类名
       type="assignable":指定排除某个具体的类
       expression="":类的全类名
       上述两种最常用
       type="aspectj":aspectj表达式
       type="custom":自定义一个TypeFilter,自己写代码决定哪些使用
       type="regex":正则表达式
       -->
       <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
   </context:component-scan>
<!--使用context:include-filter指定扫描包时要包含的类-->
   <context:component-scan base-package="com.first" use-default-filters="false">
       <!--只扫描进入哪些组件,默认是全部扫描进来,
       此时要禁用默认的过滤规则:use-default-filters="false"
       -->
       <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
       <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
   </context:component-scan>

使用@Autowired注解实现根据类型自动装配

<!--@Autowired原理:
    1.先按照类型去容器中找到对应组件 bookService = ioc.getBean(BookService);
        1)找到一个:直接赋值
        2)没找到:抛异常
        3)找到多个:
            1)按照变量名作为id继续匹配-->
            <!--如果资源类型的bean不止一个,默认根据@Autowired注解标记的成员变量名作为id查找bean,进行装配    -->
            <!--如果根据成员变量名作为id还是找不到bean,可以使用@Qualifer注解明确指定目标bean的id    -->
            <!--@Autowired注解的required属性指定某个属性允许不被设置 required = false -->
//自动装配,自动的为这个属性赋值
    //@Qualifier:指定一个名字作为id,让spring别使用变量名作为id.
    @Qualifier("bookService")
    //@Autowired(required = false) 找不到就装配null
    @Autowired
    private BookService bookServiceExt2;
<!--Autowired和resource的区别:
    resource:扩展性更强,如果切换成另一个容器框架也能支持
    autowired:spring自己的注解-->

在方法参数上添加@Qualifer注解:

/**
     * 方法上有autowired,这个方法的每一个参数都会注入值,且会在创建bean时自动运行
     */
    @Autowired
    public void hahaha(BookDao bookDao, @Qualifier("bookService")BookService bookService){
        System.out.println("Spring运行了这个方法..." + bookDao + "," + bookService);
    }

一定要导入aop包,支持加注解模式

使用注解加入到容器中的组件,和使用配置加入到容器中的组件行为是默认一样的

Spring的单元测试

@RunWith(SpringJUnit4ClassRunner.class)//spring测试包
@ContextConfiguration(“classpath:applicationContext.xml”) //配置文件的地址

AOP

面向切面编程:指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行

术语解释

JoinPoint(连接点):目标对象中,所有可以增强的方法,就是spring允许你是通知(Advice)的地方,那可就真多了,基本每个方法的前、后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点。

Pointcut(切入点):目标对象中,已经被增强的方法。调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法。

Advice(通知/增强) :增强方法的代码、想要的功能。

Target(目标对象):被代理对象,被通知的对象,被增强的类对象。

Weaving(织入):将通知应用到连接点形成切入点的过程

Proxy(代理):将通知织入到目标对象之后形成的代理对象

aspect(切面):切入点+通知————通知(Advice)说明了干什么的内容(即方法体代码)和什么时候干(什么时候通过方法名中的before,after,around等就能知道),二切入点说明了在哪干(指定到底是哪个方法),切点表达式等定义。

使用步骤

1.导包
spring-aop + spring-aspects + springsource.org.aopalliance + springsource.org.aspectj.weaver
2.写配置
1,将目标类和切面类(封装了通知方法(在目标方法执行前后执行的方法))加入到ioc容器中
2.告诉Spring哪个是切面类
3.告诉Spring,切面类里面的每一个方法都是何时何地运行
4.开启基于注解的AOP功能(或使用配置文件)
3.测试
重要的用配置,不重要的用注解

基于注解的AOP

先引入AOP名称空间,再加上下面代码:
<aop:aspectj-autoproxy proxy-target-class="true"/>
开启使用注解。

将切面类加载入ioc容器

使用@Aspect注解可以将切面类加载入ioc容器
使用@Order(Integer)注解可以改变切面加载顺序,数值越小越优先

AOP的5个注解

5个通知注解:
前置通知:@Before():在目标方法之前运行
后置通知:@After():在目标方法结束之后运行
返回通知:@AfterReturning():在目标方法正常返回之后
异常通知:@AfterThrowing():在目标方法抛出异常之后运行
环绕通知:@Around():围绕方法执行

try{
           //@Before
           method.invoke();
           //@AfterReturning
      }catch(e){
          //@AfterThrowing
      }finally{
          //@After
  }

书写切点表达式

上述5个注解在()内设置value值可指定切点位置

/**
     * 方法切入点表达式:execution(访问权限符 返回值类型 方法签名)
     * 支持通配符:
     *  *:
     *      1.匹配一个或多个字符:execution(public int com.third.impl.MyMathCalculator.*(int,int))
     *      2.匹配任意一个参数:
     *      3.只能匹配一层路径
     *      4.权限位置不能用*号表示,想要表示任意权限,不写就行
     *  ..:
     *      1.匹配任意多个参数和任意类型参数
     *      2.匹配任意多层路径
     *  支持&&,||,!运算
     */
/**
     * 抽取可重用的切入点表达式:
     * 1.随便声明一个没有实现的返回void的方法
     * 2.给方法上标注@Pointcut注解
     */
    @Pointcut("execution(public int com.third.impl.MyMathCalculator.*(int,int))")
    public void hahaMyPoint(){};

得到方法返回值

使用@AfterReturning注解中的returning属性可以获取方法调用后的返回值.
eg:

/**
     * 使用AfterReturning注解中的returing来接收返回值,returing = 接收变量名称
     * @param joinPoint 连接点
     * @param result 返回值
     */
    @AfterReturning(value = "hahaMyPoint()",returning = "result")
    public static void logReturn(JoinPoint joinPoint,Object result){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[Logs][" + name +"]方法正常执行完成,结果为[" + result + "]");
    }

得到方法执行时的异常

/**
     * 在通知方法运行时,拿到目标方法的详细信息
     * 1.给通知方法的参数列表上添加JoinPoint参数:
     * 2.使用throwing告诉spring接收异常的变量
     * 3.Exception尽量往大了写,写小了便只能接收那一类的异常
     */
    @AfterThrowing(value = "hahaMyPoint()",throwing = "e")
    public static void logException(JoinPoint joinPoint, Exception e){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[Logs][" + name + "]方法出现异常,异常信息是:" + e.getMessage());
    }

环绕通知

环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
eg:

@Around("hahaMyPoint()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
        Object[] args = pjp.getArgs();
        Object proceed = null;
        try {
            System.out.println("方法前置通知");
            //就是利用反射调用目标方法
            proceed = pjp.proceed(args);
            System.out.println("方法正常执行通知");
        } catch (Exception e){
            System.out.println("方法异常执行通知");
            e.printStackTrace();
            throw new RuntimeException(e);//不加这行外界感受不到
        } finally {
            System.out.println("方法结束执行通知");
        }
        return proceed;
    }
按方法执行顺序输出结果

在使用除环绕通知外的其他四个通知时,方法的输出如下:

/**
     * 通知方法的执行顺序:
     * 正常执行:@Before(前置通知) =====> @After(后置通知) =========> @AfterReturning(正常返回)
     * 异常执行:@Before(前置通知) =====> @After(后置通知) =========> @AfterThrowing(方法执行异常)
     */
[Logs][add]方法开始执行,用的参数列表[[2, 13]]
[Logs][add]执行完成
[Logs][add]方法正常执行完成,结果为[15]

使用环绕通知时,方法的输出如下:

方法前置通知
方法正常执行通知
方法结束执行通知
15
环绕通知和其他通知混合时的执行顺序

环绕通知优于其他通知执行。
顺序:
(环绕前置->普通前置)->目标方法执行->环绕正常返回/异常执行->环绕后置->普通后置->普通返回或异常
有时可以普通前置在前面执行。

注意事项

对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。
在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。

多切面时的执行顺序

与栈的顺序一样,先进后出
环绕通知只影响当前切面。

基于配置文件的AOP

在bean配置文件中,所有的Spring AOP配置都必须定义在<aop:config>元素内部。对于每个切面而言,都要创建一个<aop:aspect>元素来为具体的切面实现引用后端bean实例。
eg:

<aop:config>
<!--        全局切入点-->
        <aop:pointcut id="allPointCut" expression="execution(public int com.third.impl.MyMathCalculator.*(int,int))"/>
<!--        指定切面-->
        <aop:aspect ref="logUtils">
<!--            配置切点表达式,当前切面能用-->
            <aop:pointcut id="myPointcut" expression="execution(public int com.third.impl.MyMathCalculator.*(int,int))"/>
<!--            配置前置方法-->
            <aop:before method="logStart" pointcut="execution(public int com.third.impl.MyMathCalculator.*(int,int))"></aop:before>
<!--            配置正常执行方法,使用外部引入切点-->
            <aop:after-returning method="logReturn" pointcut-ref="myPointcut" returning="result"></aop:after-returning>
<!--            配置异常执行方法-->
            <aop:after-throwing method="logException" pointcut-ref="myPointcut" throwing="e"></aop:after-throwing>
<!--            配置后置通知方法-->
            <aop:after method="logEnd" pointcut-ref="myPointcut"></aop:after>
        </aop:aspect>
<!--        <aop:aspect ref="validateAspect"></aop:aspect>-->
    </aop:config>

事务部分待补充
To be continue…

参考资料

1.https://blog.csdn.net/itcats_cn/article/details/81479185
2.https://www.bilibili.com/video/av71336532/

猜你喜欢

转载自blog.csdn.net/qq_41279172/article/details/103394307