Spring

Spring框架

1、概念

https://blog.csdn.net/qq_33369905/article/details/106647330
https://blog.csdn.net/qq_33369905/article/details/105828924?spm=1001.2014.3001.5502

SpringFrameWork

Spring基础框架,可以视为Spring基础设施,基本上任何其他Spring项目都是以Spring Framework为基础

Spring Framework特性

非侵入式:使用Spring Framework开发应用程序时,Spring对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。 这就使得基于Spring Framework开发应用程序时结构清晰、简洁优雅。

控制反转: I0C-- -Inversion of Control, 翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。

面向切面编程: AOP- - Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。

容器: Spring I0C是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。

组件化: Spring 实现了使用简单的组件配置组合成一个复杂的应用。 在Spring中可以使用XML和Java注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不素的搭建超大型复杂应用系统。

声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。

一站式:在I0C和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在Spring Framework的基础上全部使用Spring来实现。

2、IOC容器(3-9)

IOC:Inversion Of Control 反转控制

DI:Dependency Injection 依赖注入

https://blog.csdn.net/saienenen/article/details/112723007

3、配置Spring的Bean对象

Bean对象不能是接口对象,必须要有实现类

Bean对象指向的对象必须要有无参构造函数

Bean对象的初始化方式是用的最多的是第二种方法

第一种方法:bean的id获取

先创建一个类

类的位置位于:com.lobo.HelloWorld.java

package com.lobo;
/**
 * className: HelloWorld
* author: MacieSerenity
* date: 2022-08-11 09:18 **/
public class HelloWorld { public void helloSpring(){ System.out.println("hello spring"); } }

再创建一个Spring的config配置文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kwde3xqJ-1690592254456)(Spring.assets/image-20220811092840662.png)]


<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="helloworld" class="com.lobo.HelloWorld">bean>

beans>

此时就将类的信息以Bean的形式注册到了Spring当中,交由Spring进行管理

然后进行测试:

package com.lobo.test;

import com.lobo.HelloWorld;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * className: HelloWorldTest
* author: MacieSerenity
* date: 2022-08-11 09:23 **/
public class HelloWorldTest { @Test public void test(){ //获取IOC容器 ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); //获取IOC容器中的Bean HelloWorld helloworld =(HelloWorld) ioc.getBean("helloworld"); helloworld.helloSpring(); } }

结果:

hello spring
Process finished with exit code 0

第二种方法:bean的类型获取

注意:根据类型获取bean时,要求配置文件中有且只有一个类型匹配的bean

否则会抛出异常:

没有匹配的Bean:NoSuchBeanDefinitionException

匹配到多个类型相等的Bean:NoUniqueBeanDefinitionException

Student student=ioc.getBean(Student.class);

第三种方式:根据id和类型获取

Student student =ioc.getBean("student",Student.class);//结合了前两种方式

问题

如果组件类实现了接口,根据接口类型可以获取bean吗?

可以,前提是bean唯一

如果一个接口有多个实现类,这些实现类都配置了bean,根据接口类可以获取bean吗?

不行,因为bean不唯一

根据类型来获取bean时,在满足bean唯一性的前提下,只是 对象 instanceof 指定类型 的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到该对象的Bean对象。

对象instanceof指定的类型:当前类型都可以通过这个这个类型本身  或者是 这个类型的实现类以及  这个类所继承的类(多态)的类型。

4、setter注入

DI:Dependency Injection 依赖注入。

依赖注入是IOC(依赖倒置)思想的一种实现方式。

通过成员变量的setter方法进行赋值:

在applicationContext.xml中写入:

<bean id="student" class="com.lobo.entity.Student" >
        <property name="sid" value="1001" />
        <property name="sname" value="makabaka" />
        <property name="age" value="12" />
        <property name="gender" value="man" />
bean>

这里对应的property指对应的属性,并使用属性的setter方法,对对象的属性进行赋值。

name:需要进行赋值的属性名称

value:属性的值

使用测试类获取该Bean,打印输出:

@Test
public void test(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student=ioc.getBean(Student.class);
    System.out.println(student);
}
Student(sid=1001, sname=makabaka, age=12, gender=man)

5、依赖注入

<bean id="studentConstructor" class="com.lobo.entity.Student">
        <constructor-arg value="1002" name="sid" />
        <constructor-arg value="李四" name="sname"/>
        <constructor-arg value="20" name="age"/>
        <constructor-arg value="man" name="gender" />
bean>

记得要在对应的Student类中匹配全参构造函数和无参构造函数

测试类:

 @Test
    public void testConstructor(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student=ioc.getBean("studentConstructor",Student.class);
        System.out.println(student);
    }
Student(sid=1002, sname=李四, age=20, gender=man)

5.1、给构造器属性赋值为null

使用标签

<bean id="student" class="com.lobo.entity.Student" >
        <property name="sid" value="1001" />
        <property name="sname" value="makabaka" />
        <property name="age">
        	<value>22value>
        property>
        <property name="gender">
        	<null>null>
        property>
bean>

5.2、给构造器属性赋值特殊符号

1、使用对应的实体

例如 value=""  ===> value="<王五>"

2、使用CDATA节

CDATA是XML文件中的一个特殊标签,因此不能能写在一个属性当中

CDATA中的内容会被原样打印

<bean id="studentCDATA" class="com.lobo.entity.Student">
        <property name="sid" value="1001" />
        <property name="sname">
            <value>>>><<<<]]>value>
        property>
        <property name="age" value="12" />
        <property name="gender" value="man" />
bean>
Student(sid=1001, sname=王五>>>><<<<, age=12, gender=man)

5.3、给 ‘类’ 类型的属性进行赋值

创建ClazzInfo类

package com.lobo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * className: ClazzInfo
* author: MacieSerenity
* date: 2022-08-11 11:26 **/
@Data @NoArgsConstructor @AllArgsConstructor @ToString public class ClazzInfo { private String clazzName; private Integer clazzId; }

更改Student类,往类中添加ClazzInfo

...
private ClazzInfo clazzInfo;
...

给类类型进行赋值,ref为引用类型,引用类型为clazzInfo,也就是下面的bean

 <bean id="studentInnerClazz" class="com.lobo.entity.Student">
        <property name="sid" value="1001" />
        <property name="sname">
            <value>>>><<<<]]>value>
        property>
        <property name="age" value="12" />
        <property name="gender" value="man" />
        <property name="clazzInfo" ref="clazzInfo" />
    bean>

    <bean id="clazzInfo" class="com.lobo.entity.ClazzInfo">
        <property name="clazzId" value="111" />
        <property name="clazzName" value="无息地西" />
    bean>
Student(sid=1001, sname=王五>>>><<<<, age=12, gender=man, clazzInfo=ClazzInfo(clazzName=无息地西, clazzId=111))

或者使用内部Bean的方式进行赋值

内部bean无法复用,只能被当前bean内部使用,不能直接通过IOC容器获取


<bean id="studentInnerBean" class="com.lobo.entity.Student">
    <property name="sid" value="1002"/>
    <property name="sname" value="sname" />
    <property name="age" value="23" />
    <property name="gender" value="woman" />
    <property name="clazzInfo">
        <bean id="clazzInnerBean" class="com.lobo.entity.ClazzInfo">
            <property name="clazzId" value="1003" />
            <property name="clazzName" value="xx班" />
        bean>
    property>
bean>

5.4、给String类型的数组进行赋值

在Student类中添加一个String类型的数组

private String[] hobby;

xml对应的配置方法

<property name="hobby">
    <array>
        <value>厚礼蟹value>
        <value>厚礼蟹value>
        <value>厚礼蟹value>
        <value>厚礼蟹value>
        <value>厚礼蟹value>
    array>
property>

5.5、给集合List类型属性进行赋值

第一种方法:list标签

ClazzInfo类中添加一个Student集合
private List<Student> students;

使用ref引用Student对象(注意这是之前的对象,不要再次引入其父对象造成堆栈溢出)

<bean id="clazzInfo" class="com.lobo.entity.ClazzInfo">
    <property name="clazzId" value="111"/>
    <property name="clazzName" value="无息地西"/>
    <property name="students">
        <list>
            <ref bean="studentCDATA" />
            <ref bean="student" />
            <ref bean="studentInnerClazz" />
            <ref bean="studentInnerBean" />
        list>
    property>
bean>

结果:

ClazzInfo(clazzName=无息地西, clazzId=111, 
students=[Student(sid=1001, sname=王五>>>><<<<, age=12, gender=man, hobby=null, clazzInfo=null), 
Student(sid=1001, sname=makabaka, age=12, gender=man, hobby=null, clazzInfo=null), 
Student(sid=1002, sname=sname, age=23, gender=woman, hobby=[厚礼蟹, 厚礼蟹, 厚礼蟹, 厚礼蟹, 厚礼蟹], clazzInfo=ClazzInfo(clazzName=xx班, clazzId=1003, students=null))])

或者

第二种方法:util


    <util:list id="studentList">
        <ref bean="studentCDATA" />
        <ref bean="student" />
        <ref bean="studentInnerBean" />
    util:list>
<bean id="clazzInfo" class="com.lobo.entity.ClazzInfo">
    <property name="clazzId" value="111"/>
    <property name="clazzName" value="无息地西"/>
    <property name="students" ref="studentList" />
bean>

5.6、给Map类型的属性进行赋值

第一种方式:map标签

添加Teacher类

package com.lobo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * className: Teacher
* author: MacieSerenity
* date: 2022-08-11 14:45 **/
@AllArgsConstructor @NoArgsConstructor @Data @ToString public class Teacher { private Integer tid; private String tname; }

然后在Student类中,添加关于Teacher的Map

private Map<String,Teacher> teacherMap;

配置文件:

<bean id="studentInnerBean" class="com.lobo.entity.Student">
        <property name="sid" value="1002"/>
        <property name="sname" value="sname"/>
        <property name="age" value="23"/>
        <property name="gender" value="woman"/>
        <property name="clazzInfo">
            <bean id="clazzInnerBean" class="com.lobo.entity.ClazzInfo">
                <property name="clazzId" value="1003"/>
                <property name="clazzName" value="xx班"/>
            bean>
        property>
        <property name="hobby">
            <array>
                <value>厚礼蟹value>
                <value>厚礼蟹value>
                <value>厚礼蟹value>
                <value>厚礼蟹value>
                <value>厚礼蟹value>
            array>
        property>
    使用Map标签,entry声明每个Map的键和值
        <property name="teacherMap">
            <map>
                <entry key="Wang" value-ref="teacherWang" />
                <entry key="Li" value-ref="teacherLi" />
            map>
        property>
    bean>

    <bean id="teacherWang" class="com.lobo.entity.Teacher">
        <property name="tid" value="10086" />
        <property name="tname" value="" />
    bean>

    <bean id="teacherLi" class="com.lobo.entity.Teacher">
        <property name="tid" value="10086" />
        <property name="tname" value="" />
    bean>

    @Test
    public void testInnerBean() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = ioc.getBean("studentInnerBean", Student.class);
        System.out.println(student);
    }

Student(sid=1002, sname=sname, age=23, gender=woman, hobby=[厚礼蟹, 厚礼蟹, 厚礼蟹, 厚礼蟹, 厚礼蟹], clazzInfo=ClazzInfo(clazzName=xx班, clazzId=1003, students=null), teacherMap={Wang=Teacher(tid=10086, tname=), Li=Teacher(tid=10086, tname=)})

第二种方式:util Map

<bean id="studentInnerBean" class="com.lobo.entity.Student">
        <property name="sid" value="1002"/>
        <property name="sname" value="sname"/>
        <property name="age" value="23"/>
        <property name="gender" value="woman"/>
        <property name="clazzInfo">
            <bean id="clazzInnerBean" class="com.lobo.entity.ClazzInfo">
                <property name="clazzId" value="1003"/>
                <property name="clazzName" value="xx班"/>
            bean>
        property>
        <property name="hobby">
            <array>
                <value>厚礼蟹value>
                <value>厚礼蟹value>
                <value>厚礼蟹value>
                <value>厚礼蟹value>
                <value>厚礼蟹value>
            array>
        property>
        <property name="teacherMap" ref="teacherMap" />
    bean>

    <util:map id="teacherMap">
        <entry key="10086" value-ref="teacherWang" />
        <entry key="10087" value-ref="teacherLi" />
    util:map>
beans>

5.7、p命名空间

 <bean id="StudentP" class="com.lobo.entity.Student"
          p:sid="11111"
          p:age="22"
          p:sname="王八"
          p:teacherMap-ref="teacherMap"
          p:clazzInfo-ref="clazzInfo"
    >bean>
 @Test
    public void testP() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = ioc.getBean("StudentP", Student.class);
        System.out.println(student);
    }

6、管理数据源与外部配置文件

6.1、德鲁伊数据库连接池介绍

https://blog.csdn.net/qq_62443736/article/details/123354828
https://blog.csdn.net/Good_omen/article/details/115356892

6.2、引入依赖

 <dependency>
     <groupId>com.alibabagroupId>
     <artifactId>druidartifactId>
     <version>1.1.21version>
 dependency>

6.3、配置spring-datasource.xml文件


<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-4.2.xsd">



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


    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />

        <property name="maxActive" value="8" />


    bean>

beans>

jdbc-config.properties

#jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatisstudy?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
jdbc.username=root
jdbc.password=li1473606768

测试类:

package com.lobo;

import com.alibaba.druid.pool.DruidDataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * className: DataSourceTest
* author: MacieSerenity
* date: 2022-08-11 15:22 **/
public class DataSourceTest { @Test public void testDatasource(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-datasource.xml"); DruidDataSource druidDataSource = ioc.getBean(DruidDataSource.class); System.out.println(druidDataSource); } }

遇到的问题:通配符的匹配很全面, 但无法找到元素 ‘context:property-placeholder‘ 的声明

通配符的匹配很全面, 但无法找到元素 ‘context:property-placeholder‘ 的声明
https://www.pudn.com/news/62d8cd9755398e076bb2c13b.html

#主要是开头标签的问题:
<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-4.2.xsd">

7、Bean的作用域

7.1、概念

在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义如下表:

取值 含义 创建时间
singleton(默认) 在IOC容器中,这个bean的对象始终为单实例 IOC容器初始化时
prototype 这个bean在IOC容器中有多个示例 获取bean时

如果是在WebApplicationContext环境下,还会有另外两个作用域(但不常用

取值 含义
request 在一个请求范围内有效
session 在一个回话范围内有效

创建一个bean指定scope=“singleton”(单例)

<bean id="student" class="com.lobo.entity.Student" scope="singleton">
    <property name="sid" value="100001"/>
    <property name="sname" value="hahah" />
bean>

编写测试类:

@Test
    public void testScope(){
        ApplicationContext ioc=new ClassPathXmlApplicationContext("spring-scope.xml");
        Student student1 = ioc.getBean(Student.class);
        Student student2 = ioc.getBean(Student.class);

        //比较内存地址
        //如果相同,说明创建的是单例
        System.out.println(student1==student2);
    }
结果:true

但是如果我们指定scope=“prototype”

<bean id="student" class="com.lobo.entity.Student" scope="prototype">
    <property name="sid" value="100001"/>
    <property name="sname" value="hahah" />
bean>

继续执行以上的测试,

结果:false

7.2、Bean的生命周期

如何测试Bean的生命周期?

创建User对象

package com.lobo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * className: User
* author: MacieSerenity
* date: 2022-08-11 16:49 **/
@Data @AllArgsConstructor @ToString public class User { private Integer id; private String username; private String password; private Integer age; User(){ System.out.println("生命周期-Bean被实例化-User的无参构造方法"); } public void setId(Integer id) { System.out.println("生命周期-Bean的依赖注入-User的setter方法"); this.id = id; } public void initMethod(){ System.out.println("生命周期-Bean的初始化"); } public void destroyMethod(){ System.out.println("生命周期-Bean的销毁"); } }

然后在xml配置文件中指定初始化的时候要执行的方法,和销毁时需要执行的方法

 <bean id="user" class="com.lobo.entity.User"
          init-method="initMethod"
          destroy-method="destroyMethod"
    >
        <property name="id" value="10086"/>
        <property name="username" value="jack" />
        <property name="password" value="123" />
        <property name="age" value="20" />
bean>

建立测试类和测试方法,使用ConfigurableApplicationContext容器进行测试,因为ApplicationContext容器不提供关闭的接口,需要向下转型为ConfigurableApplicationContext来进行关闭。(才能看到Bean的销毁过程)

package com.lobo;

import com.lobo.entity.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * className: BeanLifeCycleTest
* author: MacieSerenity
* date: 2022-08-11 16:55 **/
public class BeanLifeCycleTest { @Test public void test(){ ConfigurableApplicationContext ioc = new ClassPathXmlApplicationContext("spring-bean-lifecycle.xml"); //ApplicationContext没有提供IOC关闭的接口,需要向下转型 //转型为ConfigurableApplicationContext之后,ConfigurableApplicationContext提供了关闭IOC容器的方法 User bean = ioc.getBean(User.class); System.out.println(bean); ioc.close(); } }

运行结果:

生命周期-Bean被实例化-User的无参构造方法
生命周期-Bean的依赖注入-User的setter方法
生命周期-Bean的初始化
User(id=10086, username=jack, password=123, age=20)
生命周期-Bean的销毁

若Bean的作用域为单例模式时,生命周期的前三个步骤会在获取IOC容器时执行

若Bean的作用域为多例模式时,生命周期的前三个步骤会在获取Bean时执行

Bean的后置处理器

Bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且需要配置到IOC容器当中,需要注意的是,Bean后置处理器不是单独针对某一个Bean生效,而是对IOC容器中的所有Bean生效。

创建Bean的后置处理器需要实现BeanPostProcessor接口

package com.lobo.process;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * className: MyBeanPostProcessor
* author: MacieSerenity
* date: 2022-08-11 17:16 **/
public class MyBeanPostProcessor implements BeanPostProcessor { // 这个方法在Bean的生命周期初始化之前执行 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("生命周期-Bean的后置处理器postProcessBeforeInitialization"); return bean; } // 这个方法在Bean的生命周期初始化之后执行 @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("生命周期-Bean的后置处理器postProcessAfterInitialization"); return bean; } }

然后将这个类配置到spring的spring-bean-lifecycle.xml配置文件当中

    <bean id="MyBeanPostProcessor" class="com.lobo.process.MyBeanPostProcessor" />

依然是上面的测试类:

@Test
    public void test(){
        ConfigurableApplicationContext ioc = new ClassPathXmlApplicationContext("spring-bean-lifecycle.xml");
        //ApplicationContext没有提供IOC关闭的接口,需要向下转型
        //转型为ConfigurableApplicationContext之后,ConfigurableApplicationContext提供了关闭IOC容器的方法
        User bean = ioc.getBean(User.class);
        System.out.println(bean);
        ioc.close();
    }

运行结果:

生命周期-Bean被实例化-User的无参构造方法
生命周期-Bean的依赖注入-User的setter方法
生命周期-Bean的后置处理器postProcessBeforeInitialization
生命周期-Bean的初始化
生命周期-Bean的后置处理器postProcessAfterInitialization
User(id=10086, username=jack, password=123, age=20)
生命周期-Bean的销毁

8、FactoryBean

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的Bean不同,配置一个FactoryBean类型的Bean,在获取Bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。

通过这种机制,Spring可以帮我们把复杂的组件创建的详细过程和繁琐的细节都屏蔽起来,只把最简洁的使用界面展示给我们。

FactoryBean是一个接口,需要创建一个实体类来实现该接口

其中有三个方法

getObject():提供一个对象给IOC容器管理
getObjectType():设置所提供的对象的类型
isSingleton():所提供的对象是否是单例

当FactoryBean的实现类配置为一个Bean时,会将当前类中getObject()方法所返回的对象交个IOC容器管理。

spring-factory.xml


<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 class="com.lobo.factory.UserFactory" />
beans>

FactoryBean 实现类

package com.lobo.factory;

import com.lobo.entity.User;
import org.springframework.beans.factory.FactoryBean;

/**
 * className: UserFactory
* author: MacieSerenity
* date: 2022-08-11 18:50 **/
public class UserFactory implements FactoryBean<User> { @Override public User getObject() throws Exception { return new User(); } @Override public Class<?> getObjectType() { return User.class; } @Override public boolean isSingleton() { //return true return FactoryBean.super.isSingleton(); } }

测试类

@Test
    public void testFactoryBean(){
        ApplicationContext ioc=new ClassPathXmlApplicationContext("spring-factory.xml");
        User bean = ioc.getBean(User.class);
        System.out.println(bean);
    }

9、xml自动装配

自动装配:

根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所以来的类类型或者接口类型属性赋值。

9.1、模拟自动装配

创建controller、service和dao基本的三层架构

我们在类中添加一个setter和getter方法用于依赖反转,我们可以手动控制我们需要的Service类(当该类有多个实现时)。

UserController.java

package com.lobo.controller;
import com.lobo.service.UserService;
/**
 * className: UserController
* author: MacieSerenity
* date: 2022-08-11 19:21 **/
public class UserController { private UserService userService; public UserService getUserService() { return userService; } public void setUserService(UserService userService) { this.userService = userService; } public void saveUser(){ userService.useDao(); } }

UserService以及UserServiceImpl

package com.lobo.service;

/**
 * className: UserService
* author: MacieSerenity
* date: 2022-08-11 19:21 **/
public interface UserService { public void useDao(); } ---------------------------------- package com.lobo.service.impl; import com.lobo.dao.UserDao; import com.lobo.service.UserService; /** * className: UserServiceImpl
* author: MacieSerenity
* date: 2022-08-11 19:21 **/
public class UserServiceImpl implements UserService { private UserDao userDao; public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void useDao() { userDao.saveData(); } }

UserDao和UserDaoImpl

package com.lobo.dao;

/**
 * className: UserDao
* author: MacieSerenity
* date: 2022-08-11 19:22 **/
public interface UserDao { void saveData(); } --------------------------------- package com.lobo.dao.impl; import com.lobo.dao.UserDao; /** * className: UserDaoImpl
* author: MacieSerenity
* date: 2022-08-11 19:22 **/
public class UserDaoImpl implements UserDao { @Override public void saveData() { System.out.println("保存了数据"); } }

然后对配置文件进行修改:

创建spring-autowire.xml(名字是随便取的),注意这里的结构

使用依赖注入的方式,将私有的属性,引用为其他类型的Bean

自动装配的策略:

通过设置标签中的autowire属性设置自动装配的策略

no/default:表示不装配,bean中的属性不会自动匹配某个bean为属性赋值,此时属性使用默认值。

byType:根据需要赋值的属性的类型,在IOC容器中匹配某个bean为属性赋值

byName:将要赋值属性的属性名作为bean的id,在IOC容器中匹配某个bean为属性赋值

若通过类型没有找到任何一个类型匹配的bean,此时不装配,属性使用默认值

若通过类型找到了多个类型匹配的bean,此时会抛出异常:NoUniqueBeanDefinitionException

当使用byType实现自动装配时,IOC容器中有且只有一个类型匹配的bean能够为属性赋值。

示例:
<bean id="Controller" class="com.lobo.controller.UserController" autowire="byType/byName/no/defualt" />

<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="Controller" class="com.lobo.controller.UserController">
        <property name="userService" ref="Service">property>
    bean>

    <bean id="Service" class="com.lobo.service.impl.UserServiceImpl">
        <property name="userDao" ref="Dao">property>
    bean>

    <bean id="Dao" class="com.lobo.dao.impl.UserDaoImpl">bean>
beans>

测试:

package com.lobo;

import com.lobo.controller.UserController;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * className: AutoWireTest
* author: MacieSerenity
* date: 2022-08-12 10:04 **/
public class AutoWireTest { @Test public void autoWireTest(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-autowire.xml"); UserController bean = ioc.getBean(UserController.class); bean.saveUser(); } }

结果:

保存了数据

Process finished with exit code 0

9.2、基于注解+扫描的自动装配

注解和扫描

注解:

和XML配置文件一样,注解本身并不能执行,注解本身只是做一个标记,其具体功能是由框架检测到注解标记的位置,然后针对这个位置,按照注解标记的功能来执行相对的功能。

通过注解+扫描的bean的id,默认为类的小驼峰,即类的首字母为小写的结果

UserController => userController

可以通过给注解中加入值来自行命名:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TCOWKoQc-1690592254461)(Spring.assets/image-20220812144232303.png)]

扫描:

Spring为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,然后根据注解进行后续的操作。

创建新的项目结构其中包含:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aqYPYh9N-1690592254466)(Spring.assets/image-20220812140737214.png)]

常见注解:

@Component:将这个类标记为组件

@Controller:将这个类标记为控制层组件

@Service:将这个类标记为业务层组件

@Repository:将类标记为持久层组件

查看源码可以知道@Controller、@Service、@Repository这三个注解只是在@Component注解上起了三个新的名字。

对于Spring来说这三个注解只是给开发人员看的,便于分辨组件的作业和内容。

虽然作用一样,但是开发过程中为了代码的可读性,我们不能随意添加

根据之前的总结,我们只能将这些注解标记在实现类中,不能标记在接口上

所以:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cJSecU5U-1690592254471)(Spring.assets/image-20220812142129815.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pJLage9L-1690592254475)(Spring.assets/image-20220812142136024.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BEuhlpRG-1690592254480)(Spring.assets/image-20220812142145000.png)]

然后我们开启包扫描:

在spring配置文件中,使用context标签的component-scan来声明需要扫描的包的位置


<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-4.2.xsd">


    <context:component-scan base-package="com.lobo">
        
    context:component-scan>
beans>

注意头文件格式,可能会报错,如果遇到问题,可以查看 6.3下遇到的问题

context:component-scan标签中有两个子标签:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9yy7Tg2z-1690592254485)(Spring.assets/image-20220812142619868.png)]

context:exclude-filter:排除扫描
	type:设置排除扫描的方式
	type="annotation|assignable":
		annotation:根据注解类型排除,expression需要设置排除的注解的全类名
		assignable:根据类的类型进行排除,expression需要设置排除的类的全类名

context:include-filter:包含扫描(但是context:component-scan默认会将指定的包里面所有的内容进行扫描,需要在component-scan标签中设置use-default-filters="false",才能使用包含扫描)
	type:设置包含扫描的方式
		type="annotation|assignable":
		annotation:根据注解类型排除,expression需要设置包含的注解的全类名
		assignable:根据类的类型进行排除,expression需要设置包含的类的全类名

然后建立测试类,查看是否可以用IOC容器获取到注解的类

 @Test
    public void test(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc-annotation.xml");
        UserController userController = ioc.getBean(UserController.class);
        UserService userService = ioc.getBean(UserService.class);
        UserDao userDao = ioc.getBean(UserDao.class);

        System.out.println(userController);
        System.out.println(userService);
        System.out.println(userDao);
    }
}

结果:

com.lobo.controller.UserController@23282c25
com.lobo.service.impl.UserServiceImpl@7920ba90
com.lobo.dao.impl.UserDaoImpl@6b419da

9.3、注解的自动装配@Autowired

@Autowired:实现自动装配的注解

注解的实现过程:默认通过byType的方式在IOC容器中匹配某个类,为成员变量赋值

注意事项

1、此注解标记在需要自动装配的类上,标记后不需要设置成员的Setter/Getter方法
2、可以标记在成员变量的Set方法上
3、可以标记在为当前成员变量赋值的有参构造函数上

三种方式选一种即可。(建议直接加载需要装配的类的成员变量上方,第一种)
若配置过程中有多个类型匹配的类(一个类有多个实现),则会自动转为byName的方式,匹配名称相同的类,实现自动装配的效果。

即将要赋值的属性的属性名为bean的id匹配某个bean为属性赋值。
若byType和byName的方式都无法实现自动装配(IOC有多个类型匹配的bean,且bean的id和要赋值的属性的属性名称都不一致)则会返回NoUniqueBeanDefinitionException异常

此时可以在要赋值的成员变量上使用@Qualifier("bean的id")注解,指定一个bean的id为当前的变量赋值

#@Qualifier(" bean的id")

如果变量使用了@Autowired注解,但是没有在对应的类中使用@Service、@Controller注解时,会出现找不到Bean的情况,出现NoSuchBeanDefinitionException

10、AOP 代理模式

创建场景:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k09JWWiL-1690592254489)(Spring.assets/image-20220812155032092.png)]

创建计算器接口:

接口类:

package com.lobo.proxy;

/**
 * className: Calculator
* author: MacieSerenity
* date: 2022-08-12 15:53 **/
public interface Calculator { int add(int i,int j); int sub(int i,int j); int mul(int i,int j); int div(int i,int j); }

实现类:

package com.lobo.proxy.impl;

import com.lobo.proxy.Calculator;

/**
 * className: CalculatorImpl
* author: MacieSerenity
* date: 2022-08-12 15:54 **/
public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { System.out.println("模拟日志=>方法:add,参数:i="+i+",j="+j); int result=i+j; System.out.println("add方法内部,result="+result); System.out.println("模拟日志=>方法:add,参数:i="+i+",j="+j+",结果result="+result); return result; } @Override public int sub(int i, int j) { ... } @Override public int mul(int i, int j) { ... } @Override public int div(int i, int j) { ... } }

我们可以发现,若是我们想给每个方法都添加一个日志信息,会对类进行大量的修改,且修改的内容非常的重复,且分散在各个方法中,不利于维护。

解决思路:我们将这个日志的功能从方法中抽取出来

问题核心:代码抽取,解耦

代理模式

概念:

二十三种设计模式的一种,属于结构型模式。
其作用是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来—————实现解耦。
调用目标方法前先调用代理对象本身的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起,也有利于统一维护。

相当于对目标对象的再一次封装

代理前

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1IjCkZsN-1690592254494)(Spring.assets/image-20220812162422043.png)]

代理后:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LTH8fke3-1690592254499)(Spring.assets/image-20220812162433081.png)]

静态代理

编写代码实现静态代理模式(设计模式)

使用一个类来封装原本的实现类,在对应的方法中调用原本实现类的方法,并在此方法上添加额外的动作。

创建Calculator接口类,CalculatorImpl实现该接口的所有业务方法:

package com.lobo.proxy;

/**
 * className: Calculator
* author: MacieSerenity
* date: 2022-08-12 15:53 **/
public interface Calculator { int add(int i,int j); int sub(int i,int j); int mul(int i,int j); int div(int i,int j); } -------------------------------CalculatorImpl实现类 package com.lobo.proxy.impl; import com.lobo.proxy.Calculator; /** * className: CalculatorImpl
* author: MacieSerenity
* date: 2022-08-12 15:54 **/
public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result=i+j; System.out.println("add方法内部,result="+result); return result; } @Override public int sub(int i, int j) { int result=i-j; System.out.println("sub方法内部,result="+result); return result; } @Override public int mul(int i, int j) { int result=i*j; System.out.println("mul方法内部,result="+result); return result; } @Override public int div(int i, int j) { int result=i/j; System.out.println("div方法内部,result="+result); return result; } }

然后我们创建代理类,代理的目标对象(target)是指实现类,代理实现类中的方法:

package com.lobo.proxy.impl;

import com.lobo.proxy.Calculator;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * className: CalculatorStaticProxy
* author: MacieSerenity
* date: 2022-08-12 19:13 **/
@Data @AllArgsConstructor @NoArgsConstructor @ToString public class CalculatorStaticProxy implements Calculator { //代理的接口 private CalculatorImpl calculator; //代理接口的setter和getter方法,方便动态更改对象 public CalculatorImpl getCalculator() { return calculator; } public void setCalculator(CalculatorImpl calculator) { this.calculator = calculator; } @Override public int add(int i, int j) { int result=0; try{ //添加执行前的额外业务代码 System.out.println("添加执行前的额外业务代码"); //直接调用核心业务代码 result=calculator.add(i,j); //添加额外的业务 System.out.println("添加额外的业务"); }catch (Exception e){ //捕获异常 e.printStackTrace(); }finally { //完成后执行 } return result; } @Override public int sub(int i, int j) { return 0; } @Override public int mul(int i, int j) { return 0; } @Override public int div(int i, int j) { return 0; } }

此时我们创建测试方法:

package com.lobo;

import com.lobo.proxy.Calculator;
import com.lobo.proxy.impl.CalculatorImpl;
import com.lobo.proxy.impl.CalculatorStaticProxy;
import org.junit.Test;

/**
 * className: test
* author: MacieSerenity
* date: 2022-08-12 15:56 **/
public class test { @Test public void test(){ //代理前的对象 // Calculator calculator=new CalculatorImpl(); //创建代理对象 Calculator calculator=new CalculatorStaticProxy(new CalculatorImpl()); int result = calculator.add(1, 2); System.out.println("运行结果Result="+result); } }

运行结果:

添加执行前的额外业务代码
add方法内部,result=3
添加额外的业务
打印出结果Result=3

静态代理实现了解耦,但是不具备灵活性,若有其它的实现类,也要写出同样的代码来实现代理。

动态代理

https://blog.csdn.net/zhangcongyi420/article/details/125351123

动态生成目标类的代理类

动态代理分为两种。

1、JDK动态代理:要求必须要有接口,且最终生成的代理类会跟目标类实现相同的接口,最终生成的代理类在com.sun.proxy包下

必须实现 InvocationHandler 接口;
使用 Proxy.newProxyInstance 产生代理对象;
被代理的对象必须要实现接口;
内部采用asm技术动态生成字节码;

2、cglib动态代理:最终生成的代理类会继承目标类,且和目标类相同的包下

JDK动态代理

创建计算器接口类和实现类:

package com.lobo.proxy;

/**
 * className: Calculator
* author: MacieSerenity
* date: 2022-08-12 15:53 **/
public interface Calculator { int add(int i,int j); int sub(int i,int j); int mul(int i,int j); int div(int i,int j); } --------------------------------实现类 package com.lobo.proxy.impl; import com.lobo.proxy.Calculator; /** * className: CalculatorImpl
* author: MacieSerenity
* date: 2022-08-12 15:54 **/
public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result=i+j; System.out.println("add方法内部,result="+result); return result; } @Override public int sub(int i, int j) { int result=i-j; System.out.println("sub方法内部,result="+result); return result; } @Override public int mul(int i, int j) { int result=i*j; System.out.println("mul方法内部,result="+result); return result; } @Override public int div(int i, int j) { int result=i/j; System.out.println("div方法内部,result="+result); return result; } }

然后创建代理工厂的类

package com.lobo.proxy;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * className: ProxyFactory
* author: MacieSerenity
* date: 2022-08-12 19:46 **/
@AllArgsConstructor @Data public class ProxyFactory { private Object target; public Object getProxy(){ /** * 类加载器ClassLoader loader:指定加载动态生成的代理类的类加载器 * 类接口 Class[] interfaces;获取目标对象实现的所有接口的class对象数组 * 对象InvocationHandler h;设置代理类中的抽象方法如何重写 */ ClassLoader classloader = this.getClass().getClassLoader(); Class<?>[] interfaces=target.getClass().getInterfaces(); InvocationHandler h = new InvocationHandler() { //代理类中的方法如何执行 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result=null; try { //Object proxy表示代理对象 //Method method要执行的方法 //Object[] args要执行方法的参数列表 //执行调用前的方法 System.out.println("日志,方法"+method.getName()+",参数:"+ Arrays.toString(args)); //调用目标对象 result = method.invoke(target, args); //执行调用后的方法 System.out.println("日志,方法"+method.getName()+",结果:"+result); return result; } catch (Exception e) { System.out.println("日志,捕获到了异常:方法:"+method.getName()); e.printStackTrace(); } finally { System.out.println("日志,方法执行完毕,关闭资源等功能"); } return result; } }; return Proxy.newProxyInstance(classloader,interfaces,h); } }

注意这里的result=method.invoke(target,args)

invoke也就是执行的意思,利用反射机制,获取运行中的目标类,然后使用代理类来代理执行其目标的方法。可以明显发现,代理类增强了原有的功能,在原有功能之上,还可以在代码前和代码后,捕获异常以及最终执行等方面添加了新的功能。

测试类:

package com.lobo;

import com.lobo.proxy.Calculator;
import com.lobo.proxy.ProxyFactory;
import com.lobo.proxy.impl.CalculatorImpl;
import com.lobo.proxy.impl.CalculatorStaticProxy;
import org.junit.Test;

/**
 * className: test
* author: MacieSerenity
* date: 2022-08-12 15:56 **/
public class test { @Test public void test(){ //代理前的 // Calculator calculator=new CalculatorImpl(); Calculator calculator=new CalculatorStaticProxy(new CalculatorImpl()); int result = calculator.add(1, 2); System.out.println("运行结果Result="+result); } @Test public void testProxyFactory(){ ProxyFactory proxyFactory=new ProxyFactory(new CalculatorImpl()); Calculator calculator = (Calculator) proxyFactory.getProxy(); calculator.add(1,2); calculator.sub(2,1); // 测试捕获异常 // calculator.div(1,0); } }

AOP

Aspect Oriented Programming 面向切面编程,是一种设计思想,是对面向对象编程OOP的一种补充和完善,通过预编译方式和运行期间动态代理的方式实现在不修改源代码的情况下给程序动态统一的添加额外功能的一种技术。

OOP,Object Oriented Programming,面向对象编程

Oriented :面向…的

比如在控制事务的过程中,我们需要先打开事务,然后去执行事务,若事务中有异常,则回滚,否则就提交也需要面向切面编程的方式实现。

横切关注点

通俗一点来讲,对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

一个方法中可以有多个横切关注点,从核心业务代码中抽取出来的非核心业务代码的位置,例如上个例子的日志功能、事务的开启等。

切面

类是对物体特征的抽象,切面就是对横切关注点的抽象

通知

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类

环绕通知:是使用try...catch...finally...围绕整个被代理的目标方法,包括上面四种通知对应的所有位置。

横切关注点封装在切面中 切面中的每一个横切关注点就是一个通知

横切关注点就是非核心代码,这些核心代码放在一个类中,这个类叫切面,对于切面来说,这些横切关注点叫做通知

目标

被代理的目标对象

代理

向目标对象应用通知之后,创建的代理对象

连接点

抽取横切关注点的位置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CWHbvdAQ-1690592254503)(Spring.assets/image-20220813100109572.png)]

切入点

定位连接点的方式

如何理解以上关键词

我们需要对【目标】类进行分析,分析其核心功能点和非核心功能点,其中非核心功能点的位置,就是我们的【横切关注点】,我们将这每一个非核心业务的代码也就是【通知】进行提取,提取方法的这些位置叫做【连接点】,我们将多个【通知】封装到一个类中,这就是【切面】类。因为我们从【目标】类中提取出了这些【通知】,而我们需要讲这些【通知】在【代理对象】中与原本目标类中相同的位置进行插入,插入的位置叫做【连接点】,而【连接点】只是我们在逻辑上的一个叫法,我们插入通知的地方叫做【切入点】,实现代理的过程就是通过【代理对象】将【切面】和核心业务进行整合的过程。

作用

简化代码:把方法中固定位置的重复代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。

代码增强:把特点的功能封装到切面类当中,被套用了切面逻辑的方法,就是被切面给增强了。

Spring基于注解的AOP

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JdmigAqI-1690592254508)(Spring.assets/image-20220813143745341.png)]

动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口。因为动态代理要求代理对象和目标对象必须实现相同的接口

cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口。

Aspectj:本质上是静态代理,将代理逻辑【织入】被代理的目标类,得到字节码文件,所以最终效果是静态的。weaver就是织入器,Spring只是借用了Aspectj中的注解。

@Before、@After、@AfterReturning和@AfterThrowing

Calculator和CalculatorImpl

package com.lobo.proxy;

/**
 * className: Calculator
* author: MacieSerenity
* date: 2022-08-12 15:53 **/
public interface Calculator { int add(int i,int j); int sub(int i,int j); int mul(int i,int j); int div(int i,int j); } ------------------------------------实现类 package com.lobo.proxy.impl; import com.lobo.proxy.Calculator; import org.springframework.stereotype.Component; /** * className: CalculatorImpl
* author: MacieSerenity
* date: 2022-08-12 15:54 **/
@Component public class CalculatorImpl implements Calculator { @Override public int add(int i, int j) { int result=i+j; System.out.println("add方法内部,result="+result); return result; } @Override public int sub(int i, int j) { int result=i-j; System.out.println("sub方法内部,result="+result); return result; } @Override public int mul(int i, int j) { int result=i*j; System.out.println("mul方法内部,result="+result); return result; } @Override public int div(int i, int j) { int result=i/j; System.out.println("div方法内部,result="+result); return result; } }

一定要记得将实现类标记为@Component组件,否则会找不到Bean

创建切面类:

package com.lobo.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * className: LogAspect
* author: MacieSerenity
* date: 2022-08-13 15:16 **/
@Aspect @Component public class LogAspect { //切面类必须有@Aspect注解标记为切面,还需要用指定的注解和属性声明需要代理的方法 //声明一个公共的切入点表达式 @Pointcut("execution( * com.lobo.proxy.impl.CalculatorImpl.*(..))") public void pointCutMethod() { } //配置value属性,切入点表达式 //按照返回类型和访问类型、全类名、方法名、方法参数的方式进行代理 //@Before("execution(public int com.lobo.proxy.impl.CalculatorImpl.add(int ,int))") //不按返回类型和访问类型,只按照全类名进行定位,代理该类下的所有方法 //第一个*表示任意的返回类型和访问类型,第二个*号指任意方法,(..)表示任意的参数列表 //@Before("execution( * com.lobo.proxy.impl.CalculatorImpl.*(..))") //代理该包下的所有类的所有方法 //类名称也可以用*号代替,表示该包下的任何类和任何方法 //@Before("execution( * com.lobo.proxy.impl.*.*(..))") //使用公共的切入点表达式: @Before("pointCutMethod()") public void beforeAdviceMethod(JoinPoint joinPoint) { //可以在该方法的参数内添加一个JoinPoint对象,帮助我们获取切入点的信息 //获取连接点所对应的方法的方法名 Signature signature = joinPoint.getSignature(); //获取连接点方法的参数 Object[] args = joinPoint.getArgs(); System.out.println("LogAspect类中@Before的" + signature.getName() + "执行了,参数是:" + Arrays.toString(args)); } //@After("execution(public int com.lobo.proxy.impl.CalculatorImpl.add(int ,int))") //@After("execution( * com.lobo.proxy.impl.*.*(..))") //使用公共切入点表达式: @After("pointCutMethod()") public void afterAdviceMethod(JoinPoint joinPoint) { //获取连接点所对应的方法的方法名 Signature signature = joinPoint.getSignature(); //获取连接点方法的参数 Object[] args = joinPoint.getArgs(); System.out.println("LogAspect类中@After的" + signature.getName() + "执行了,参数是:" + Arrays.toString(args)); } //因为是在获取结果之后执行,所以一定能获取到执行的返回值 //获取返回值需要添加returning="返回值的自定义名称"属性,然后在参数栏内添加对应的Object类型的参数即可 //只要保持returning="result" ,且对应方法中接收结果的参数也叫result @AfterReturning(value = "pointCutMethod()",returning = "result") public void afterReturningMethod(JoinPoint joinPoint,Object result){ //获取连接点所对应的方法的方法名 Signature signature = joinPoint.getSignature(); //获取连接点方法的参数 Object[] args = joinPoint.getArgs(); System.out.println("LogAspect类中@AfterReturning的" + signature.getName() + "执行了,参数是:" + Arrays.toString(args)+",结果是:"+result); } //异常通知 //throwing 获取得到的异常的参数 //使用Throwable接收出现的异常的信息 @AfterThrowing(value = "pointCutMethod()",throwing = "exception") public void afterThrowingMethod(JoinPoint joinPoint,Throwable exception){ //获取连接点所对应的方法的方法名 Signature signature = joinPoint.getSignature(); //获取连接点方法的参数 Object[] args = joinPoint.getArgs(); System.out.println("LogAspect类中@AfterThrowing的" + signature.getName()+"遇到的异常是:"+exception); } }

配置文件要记得扫描切面类和实现类(目标类)


<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:aop="http://www.springframework.org/schema/aop"
       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 http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">




    <context:component-scan base-package="com.lobo">
    context:component-scan>


    <aop:aspectj-autoproxy />


beans>

测试类:

package com.lobo;

import com.lobo.aspect.LogAspect;
import com.lobo.proxy.Calculator;
import com.lobo.proxy.impl.CalculatorImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * className: test
* author: MacieSerenity
* date: 2022-08-13 15:10 **/
public class test { @Test public void test(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("aop-annotation.xml"); //获取代理对象,通过访问代理对象来创建目标对象,不能直接获取目标对象 // Calculator calculator = ioc.getBean(CalculatorImpl.class); // 错误:No qualifying bean of type 'com.lobo.proxy.impl.CalculatorImpl' available Calculator calculator = ioc.getBean(Calculator.class); String className = calculator.getClass().getName().toString(); System.out.println("当前运行的类名称为:"+className); calculator.add(1,1); calculator.div(1,1); } }

运行结果:

当前运行的类名称为:com.sun.proxy.$Proxy17
LogAspect类中@Beforeadd执行了,参数是:[1, 1]
add方法内部,result=2
LogAspect类中@AfterReturningadd执行了,参数是:[1, 1],结果是:2
LogAspect类中@Afteradd执行了,参数是:[1, 1]
LogAspect类中@Beforediv执行了,参数是:[1, 0]
LogAspect类中@AfterThrowingdiv遇到的异常是:java.lang.ArithmeticException: / by zero
LogAspect类中@Afterdiv执行了,参数是:[1, 0]

java.lang.ArithmeticException: / by zero

我们可以声明一个公共的切入点表达式

//声明一个公共的切入点表达式
    @Pointcut("execution( * com.lobo.proxy.impl.CalculatorImpl.*(..))")
    public void pointCutMethod(){}

然后将其放到@Before和@After注解的参数当中(注意,参数的内容为切入点表达式的方法名)

package com.lobo.aspect;

...
    
@Aspect
@Component
public class LogAspect {
//切面类必须有@Aspect注解标记为切面,还需要用指定的注解和属性声明需要代理的方法
    //声明一个公共的切入点表达式
    @Pointcut("execution( * com.lobo.proxy.impl.CalculatorImpl.*(..))")
    public void pointCutMethod(){}

    //使用公共的切入点表达式:<=================
    @Before("pointCutMethod()")
    public void beforeAdviceMethod(JoinPoint joinPoint){
       	...
    }
    //使用公共切入点表达式:<=================
    @After("pointCutMethod()")
    public void afterAdviceMethod(JoinPoint joinPoint){
        ...
    }
}

各个注解所在的切点位置

@Before:前置通知,在目标对象方法执行之前执行

@After:后置通知,在目标对象方法的finally中执行

@AfterReturning:返回通知,在返回值之后执行

注意:任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。

Spring5.3.x以前的执行顺序:

前置通知>目标操作>后置通知>返回或者异常通知

Spring5.3.x以后的执行顺序:

前置通知>目标操作>返回通知或者异常通知>后置通知

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xgPv7fnx-1690592254512)(Spring.assets/image-20220814090954423.png)]

根据我们的执行情况,可以判断出我们的Spring的版本号是:5.3.x以前的版本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QGK2kLEW-1690592254517)(Spring.assets/image-20220814091116767.png)]

可以看到虽然切面使用的是5.3.1,但是spring的版本是5.2.5

更改pom文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AmRVkQoe-1690592254522)(Spring.assets/image-20220814091246246.png)]

然后重新执行程序可以发现:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zq8FVUp1-1690592254526)(Spring.assets/image-20220814091304262.png)]

最后执行@After,与预期一致

当我们程序发生错误时,@Before和@After都执行了,说明@Before和@After不受异常的影响,所以是@Before在try{}语句块中,核心方法调用之前;而@After则是在finally语句中,无论是否发生异常,都会执行。

@Around 环绕通知

//手动设置所有的操作,使用ProceedingJoinPoint获取核心业务的执行
//且环绕通知的返回值必须要和核心代码的返回值一致
    @Around("pointCutMethod()")
    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
        //核心代码必须有返回值
        Object result=null;
        //表示目标对象的执行
        try {
            //这就是前置通知
            System.out.println("环绕通知-->前置通知");
            //表示核心业务代码的执行
            result=proceedingJoinPoint.proceed();
            //这就是后置通知的位置
            System.out.println("环绕通知-->返回通知");
        }catch (Throwable t){
            //这就是异常通知的位置
            System.out.println("环绕通知-->异常通知");
        }finally{
            //这就是后置通知的位置
            System.out.println("环绕通知-->后置通知");
        }
        //核心代码必须有返回值
        return result;
    }
环绕通知-->前置通知
LogAspect类中@Beforeadd执行了,参数是:[1, 1]
add方法内部,result=2
LogAspect类中@AfterReturningadd执行了,参数是:[1, 1],结果是:2
LogAspect类中@Afteradd执行了,参数是:[1, 1]
环绕通知-->返回通知
环绕通知-->后置通知

多个切面的执行顺序@Order

创建另一个切面对象

package com.lobo.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * className: ValidateAspect
* author: MacieSerenity
* date: 2022-08-14 09:31 **/
@Component @Aspect @Order(1) <===========================注意这里,默认是Integer的最大值,而越大优先级越小 public class ValidateAspect { //@Order(1)使用Order注解声明切面的优先级,数字越小,优先级越高 @Pointcut("execution(* com.lobo.proxy.impl.CalculatorImpl.*(..))") public void pointCut(){}; //可以使用其他类的公共切入点表达式 @Before("com.lobo.aspect.LogAspect.pointCutMethod()") public void BeforeMethod(){ System.out.println("ValidateAspect的前置通知"); } }

基于XML文件管理切面

将所有关于切面的注解删除,包括:@Before、@After、@AfterReturning和@AfterThrowing以及@Around

然后创建spring配置文件:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config>

        <aop:pointcut id="pointCut" expression="execution(* com.lobo.proxy.impl.CalculatorImpl.*(..))"/>


        <aop:aspect ref="loggerAspect" order="1">
            <aop:before method="beforeAdviceMethod" pointcut-ref="pointCut" />
            <aop:after method="afterAdviceMethod" pointcut-ref="pointCut" />

            <aop:after-returning method="afterReturningMethod" pointcut-ref="pointCut" returning="result"/>


            <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointCut" throwing="exception"/>
            <aop:around method="aroundMethod" pointcut-ref="pointCut" />
        aop:aspect>
    aop:config>
beans>

可以正常运行,运行结果一致

你可能感兴趣的:(spring,java,后端)