SpringBasic

Spring!

三层和MVC

  • 理解1:两种不同的设计逻辑

  • 三层:Controller,service,dao

  • MVC:

    运行流程:

    前端发送给Controller请求,C接收请求将需求交给Model,model完成业务逻辑并访问数据库,将得到的数据返回给C,C要将数据交给View打包成视图,再返回到C,由C返回给客户端。

    image-20230705210004961

    此版本其实是上一版本,在前后端不分离的时期,View视图层使用JSP写的。

    后端将数据封装成JSP视图后,再一起发送到浏览器用于展示(有点后端前移的意思)。

    model中,由javabean实现,其中有实体bean,业务bean,即包括三层中的业务逻辑层和数据访问层

  • 又一理解(来自黑马):

    • 三层分为表现层,业务层,持久层

    • 每层的框架分别是SpringMVC,Spring Framework,Mybatis

    • 表现层中的SpringMVC分为C,M,V,其中的面向业务层

两种理解似乎是一致的:第一种理解的Model层可以涵盖service层和dao层。第二种理解不过是把三层中Controller层按照业务流程分开三步

是否要经过view层渲染?在前后端分离开发模式下似乎淘汰了

最新理解:

springMVC就是Controller层的框架

古早前后端不分离版本:springMVC还要用JSP负责写view层。

最新版本前后端分离中:springMVC不需要JSP写view,而是要将内容转化成JSON,交由前端解析+渲染

spring框架与用servlet开发:

关系暂不明

  • 入门开发步骤

    1. 创建Spring6父工程(项目project),工程下有多个Spring子模块(module)

      父工程下new module:每个module是一个功能或组件。相对独立:有自己的文件解构:配置文件,资源文件。。。可以独立的测试和部署

      微服务?

    2. 创建类,定义属性和方法

    3. 按照Spring要求,创建配置文件(xml格式)

    4. 在Spring配置文件中配置相关信息——声明bean对象

    5. 测试

      • 加载spring配置文件,用创建对象的方式

      • 由此文件对象,获取想要创建的对象(文件里的bean)

      • 使用对象调用方法实现逻辑

  • 入门程序分析

    1. 程序执行了对象的无参构造方法

    2. 没有new,怎么创建的对象——反射

      • 在加载配置文件bean.xml时进行解析,目的是获得bean标签的属性值。关键属性值:id和class

      • 使用反射,根据class类全路径创建对象(Class字节码文件映射到Class对象)

        1
        Class c = Class.forName(class属性值)

        由此Class对象获取类对象

        1
        User user = (User)c.getDeclaredConstructor().newInstance();

        于是反射的结果任然是创造对象

        正常是由对象实体编译到class文件,反射是由class文件试图回到对象实体:

        这就有一个问题:

        原本的类没有用到全部的属性和方法,属性或许有默认值还有价值,但你没用到的方法是否不会参与编译呢?

        结果是所有东西都参与编译——javaC编译时不清楚哪个用了没有。

  1. 配置的bean对象放在Map集合中,k:String=bean对象的唯一标识:id,v:BeanDefinition=bean的表述信息类,内含许多属性,包括唯一路径,是否为单例,作用域,name…人为描述——刻画想要怎样的bean对象
  • Log4j2:Apache下开源的日志记录组件

    • 日志信息有优先级,不同级别的信息表示此条日志的重要性,级别高的会自动屏蔽级别低的日志

      1. TRACE
      2. DEBUG
      3. INFO
      4. WARN
      5. ERROR
      6. FATAL
    • 日志信息输出位置:可选在控制台or文件中

    • 日志信息输出格式可调:日期形式…(log4j2只是一个日志框架,其中许多参数赋默认值,却可调整)

    • 分为自动写入和手动写入

      手动写入:也是通过调用对象的方法来实现(为什么不设置成静态?)

      1
      2
      3
      4
      private Logger logger = LoggerFactory.getLogger(TestUser.class)

      logger.info("sth")
      logger.warn("sth")。。。

      没想到不分层的逻辑也不new对象,也用了工厂模式+反射

      一旦new就会耦合,就要避免

    • log4j是日志框架的实现,slf4j是日志框架的接口,向下处理实现的操作对上屏蔽。

      在运行时通过反射找到具体的日志实现

      1
      2
      3
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j2-impl</artifactId>
      <version>2.19.0</version>

      此依赖就是slf4j到log4j2的桥接器坐标

IoC

一种设计思想--是用来设计出松耦合的程序

权力由上层交给下层,下层交给IoC容器

思想:将Java对象的实例化和初始化交给IoC容器,而避免手动new对象,容器内即负责实例化和初始化对象,也管理对象之间的依赖关系(like A需要调用B的方法,只要AB都在IoC中,容器即可完成A对B的调用)

在IoC容器中的对象成为Spring Bean,这和new出来的对象没有区别

Ioc流程说明:

  1. xml配置文件

    1
    2
    3
    4
    <bean id="myBean" class="com.example.MyBean">
    <!-- 设置属性值 -->
    <property name="propertyName" value="propertyValue" />
    </bean>

    其中定义了id和BeanDefinition(底层是k-v)

  2. 通过接口实现类从map中读取需要的bean(接口定义要读取需要的bean,实现类里区分从xml或者注解读取)。

    image-20230707192615033

  3. 将读取的bean的id和Definition加载到IoC容器,接下来进行实例化

  4. Spring中通过BeanFactory+反射创建对象

  5. 对象初始化——成为最终对象

  6. user user = (user)context.getBean("id");
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
       
    获取真对象
    - Bean管理:对象的创建过程和Bean属性的注入过程

    - IoC容器要对读取的beanDefinition实例化,而IoC的两种实现方式有:

    1. BeanFactory(程序内部使用的接口,不面向开发人员)

    2. ApplicationContext:BeanFactory的子接口,是BeanFactory的上层

    ![image-20230707194632359](C:\Users\吴松林\AppData\Roaming\Typora\typora-user-images\image-20230707194632359.png)

    3. ApplicationContext:此接口的实现类们实现了从不同位置,不同方式创建IoC容器

    ![image-20230707195239359](C:\Users\吴松林\AppData\Roaming\Typora\typora-user-images\image-20230707195239359.png)

    ClassPathXml...从类文件下找到配置文件

    FileSystemXml...从文件系统路径下找到配置文件

    ConfigurableApplication...子接口,包含扩展方法,功能更加强大

    web...为web应用准备

    - 从IoC容器获取Bean对象的三种方式:(根据id,根据class,两者同时)

    ```java
    ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
    iocDemo1 demo1 = (iocDemo1) context.getBean("Demo1");
    System.out.println(demo1);
    iocDemo1 demo2 = context.getBean(iocDemo1.class);
    System.out.println(demo2);
    iocDemo1 demo3 = context.getBean("Demo1",iocDemo1.class);
    System.out.println(demo3);

有冲突,会报错——配置文件可以同class,但不能据此找bean

  • 从IoC容器获取Bean对象也可以多态——容器中有实现类的bean,从容器中get的依据是接口的class。

    但由于获取bean对象返回的都是唯一对象,于是如果接口存在多个实现类则Spring无法挑选

    • 而且只能根据接口.class来获取,不能用接口名

    • 源代码:扫描context里bean对象时使用instanceof方法选出和传入class匹配的bean

      而传id自然只能由id扫描到自身,无法扫描到实现类了

  • 配置文件里可以有id相同的bean,也可以有同一接口的不同实现类的bean,在配置文件中不会报错,但挑选方式(getBean)时要避免相应的错误

DI依赖注入

指Spring创建对象的过程中,将对象依赖的属性通过配置进行注入 配置?

常见注入方法:set注入和构造注入

注入引用类型的属性,实际就是维护bean对象之间的关系

无论是用setter,还是构造方法,在原生代码中都需要new获取对象,于是在Spring中就是需要配置bean

  1. 根据Setter:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class iocDemo2 implements Person{

    String name;
    String age;

    public void setName(String name) {
    this.name = name;
    }

    public void setAge(String age) {
    this.age = age;
    }
    1
    2
    3
    4
    5
    6
        <bean id="Demo2" class="org.example02.iocDemo2">
    <property name="age" value="21"></property>
    <property name="name" value="wsl"></property>
    </bean>
    name表示setter方法中后单词小写
    要求类文件中有set方法(以及无参构造/或者没有构造器,默认无参)
  2. 根据构造器

    1
    2
    3
    4
    5
        <bean id="Demo2" class="org.example02.iocDemo2">
    <constructor-arg name="age" value="22"></constructor-arg>
    <constructor-arg name="name" value="wsl"></constructor-arg>
    </bean>
    要求类文件中有含参构造
  • 特殊值处理:

    1. null值:

      由于xml文件里,属性的标准写法是k=“v”,于是无论右侧是不是字符串都只能按照字符串写。

      表示null用内嵌标签:

      1
      2
      3
      4
       <constructor-arg name="birthday">
      <null>
      </null>
      </constructor-arg>
    2. 标签冲突(<>):

      转义!

      &lt;表示<, &gt; 表示>

      或者:CDATA区/CDATA节

      ​ C表示Character纯文本,不会被识别为其他有含义的符号

      1
      2
      3
      <constructor-arg name="name">
      <value><![CDATA[a<b]]></value>
      </constructor-arg>
  • 注入的属性是引用数据类型(对象)

    方法:

    1. 引用外部bean

      用property标签的ref属性,将别的bean对象的id传进来,就能识别到是哪个对象

    2. 内部bean

      在bean内部某个property中定义需要的bean对象

      image-20230708094017029

    3. 级联属性配置

      在bean中像设置引用对象的某个属性时使用

      1
      2
      3
      4
      5
      6
      7
          <bean id="Demo2" class="org.example02.iocDemo2">
      <property name="age" value="21"></property>
      <property name="name" value="wsl"></property>
      <property name="main" ref="main"></property>
      <property name="main.mainName" value="www"></property>
      </bean>
      但前提是:Demo2中的main属性需要设置get方法
  • 注入的属性是引用数据类型(数组)

    image-20230708100044822
  • 注入的属性是引用数据类型(List<引用>)

image-20230708100447939

  • 注入的属性是引用数据类型(List<基本>)

    那么list标签内就用value即可

  • 注入的属性是引用数据类型(Map<引用>)

    property属性标签—>map集合标签—>entry个例标签—>key标签 and ref标签

    image-20230708102644334
  • 注入的属性是引用数据类型(Map<基本>)

    property属性标签—>map集合标签—>entry个例标签—>key标签 and value标签

  • 简便办法——用工具标签

    • 前提:在bean.xml配置文件中引入

      1
      2
      3
      4
      5
      6
      7
      8
      <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:util="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/beans/spring-util.xsd"
      >
      • xmlns(xml nameSpace )用于指定默认的命名空间——bean标签
      • xsi 是 XML Schema Instance 的简写。前面指定命名空间,后面在schemaLocation中声明xsi的属性:格式是:namespaceURI schemaLocation
      image-20230708105144561 image-20230708105209418
    • image-20230708105241006

  • P命名空间——

    1. 配置导入P空间文件

      image-20230708105934770

    2. p注入

      image-20230708110139958

      不写property,而是在bean标签内由p:属性注入

      上面的配置就像是给bean标签定义属性p,而p又识别出bean对象的属性作为自己的属性

      在p:后,每个bean对象的属性都有本身和ref两种,用于确定是注入字面量还是引用类型

  • 引入外部属性文件——

    需求背景是:某些特定的值比较固定,且逻辑上可以和别的区分开,于是写到外部再引入进来

    ​ 例如数据库容器配置文件。

    做法:

    1. 确保项目里有JDBC对myBatis的依赖

    2. 编写jdbc.properties,内部写连接数据库的必要信息(id,密码,路径,驱动…)

    3. 引入命名空间,做法与引入一致,在配置文件中用context引入属性文件

      image-20230708112024055

    4. 然后就可以通过配置文件名来调用其里面k-v形式的内容

      image-20230708112205072

  • bean的作用域——scope属性限制是单例还是多例

    1. scope=”singleton”:在IoC容器中,这个bean对象始终为单例

      在IoC容器初始化时创建

    2. scope=”prototype”:在IoC容器中,这个bean对象可以有多个

      在获取bean时创建

  • 单例:(默认)

    • 每次请求获取该对象,都会得到同一个对象实例(context.forName(…)
    • 常常用在表示程序中共享的某种资源或状态(对程序任何位置都是一致的)
    • 可以节省资源
  • 多例:

    • 每次请求获取对象实例时都会创建一个新的对象实例(独立的).
    • 常常用于表示具有不同状态或不同上下文的对象

bean 生命周期

用property 写法

  1. bean对象创建(调用无参构造)
  2. 给bean对象设置相关属性(通过setter将property注入)
  3. bean后置处理器(初始化之前)
  4. bean对象的初始化
  5. bean后置处理器(初始化之后)
  6. bean对象创建完成,可以使用
  7. bean对象销毁(配置指定销毁方法)(需要依赖IoC关闭方法)
  8. IoC容器关闭
  • 后置处理器

    是一种特殊的Bean,可以对其他Bean进行定制化的处理。

    关键两个方法:

    1. postProcessBeforeInitialization(Object bean,String beanName){}

      在目标bean初始化方法执行前调用

      可以修改属性值,执行初始化之前的准备工作等

    2. postProcessAfterInitialzation(Object bean,String beanName){}

      初始化之后执行…

    作用:拦截所有的Bean创建过程(是这个bean所在配置文件的所有bean)

FactoryBean机制

  • 是一种特殊的bean,获取该bean时,创建的实例是其getObject()方法中返回的别的实例bean。这样做会把复杂组件创建的详细过程和繁琐细节都屏蔽。,并且给bean添加更多的定制和控制操作。
  • 是Spring整合第三方框架的常用机制
1
2
3
4
5
6
7
8
9
10
public class myFactoryBean implements FactoryBean<Person> {
@Override
public Person getObject() throws Exception {
return new Person();
}
@Override
public Class<?> getObjectType() {
return Person.class;
}
}

基于xml配置文件的自动注入

1
2
3
<bean id="controller" class="Three.Controller" autowire="byType">
<property name="name" value="controller"></property>
</bean>

某些要手动注入的基本数据类型可以手动写入

某些在同一个bean文件下的引用数据类型可以自动装配

  • byType:根据声明类型识别
  • byName:根据声明name识别

自动注入要求开启setter方法,其实是通过setter方法注入的属性(property)

基于注解的Bean管理

注解:

注解是代码中的一种特殊标记,可以在编译,类加载,运行时被读取并执行相应的处理。通过注解:在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。

作用范围:类上,属性上,方法上

初步思路:

  1. 引入依赖
  2. 在bean.xml文件中开启组件扫描(确定IoC容器要扫描那些个包的注解)
    • image-20230708171511383
    • 也可以用某些语法设置排除某些包,包括某些包
  3. 使用注解定义bean
  4. 依赖注入

依赖注入的注解:

  1. @Autowired

    默认是根据类型自动装配

    源码:

    image-20230708172003366

    @Target()元注解表示限制此注解的作用范围

    @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})

    constructor:构造函数

    method:普通方法

    parameter:参数

    field:成员变量

    annotation_type:其他注解

    Type:类,接口,枚举类型上

  • required()参数表示

    required() 表示被注入的对象是否要求存在(即自动注入的必要性)

    @Autowired定义在哪里:

  1. 定义在类属性上(常规)

  2. 定义在setter方法上

    image-20230708173221479
  3. 定义在构造方法上

    image-20230708173345132
  4. 定义在形参上

    image-20230708173449310
  5. 类中只有一个含参构造时,可以不写@Autowired

  6. 由于原本自动注入默认是根据类型识别,但如果一个下层接口有多个实现类,获取要再根据name注入。

    image-20230708174024755

    value表示要注入的类的名字——默认是类名开头小写。

  7. @Resource

    是JDK自带的注解,java版本在8-11即可不用引入依赖

    @R可以和JavaEE,Spring框架一起使用,因为是自带的,@A是Spring框架特定的注解,更丰富更灵活,和Spring更适合

    1. @R默认按照name查找,name找不到再按照类型查找。

      如果@R()参数中没有指定name,根据属性名查找,再找不到根据类型…

    2. @R只能标注在字段属性上,和setter方法上,@A更多

    3. @R由于直接根据name,不需要@Qualifier从多中选一

全注解开发:

用配置类来代替bean.xml配置文件

配置类:

image-20230708191338655

测试类:

image-20230708191426017

手写IoC容器

反射

  • 步骤

    1. 获取类的class对象(字节码文件——包含类的解构和元数据信息,可以映射成一个类对象,于是可以调用方法)

      方法:

      • 类名.class

        image-20230708192430303

      • 对象.getClass()

        image-20230708192438909

      • Class.forName(“全路径”)

        全路径指在此模块中找到此类的路径:用.分离

        image-20230708192446339

    2. 由class类实例化成对象

      image-20230708192913516

      1
      2
      3
      Class<?> clazz = MyClass.class; // MyClass为要实例化的类的字节码文件
      Constructor<?> constructor = clazz.getConstructor(parameterTypes); // parameterTypes为构造函数的参数类型数组
      Object obj = constructor.newInstance(arguments); // arguments为构造函数的实际参数数组
      • getConstructor()不会获取原本类中声明为private的构造方法,只会拿到public

      • getDeclaredConstructor()会

      • 获取的构造器对象,拥有几个属于构造器独特的方法——

        image-20230709093748273

    3. 反射时默认用的无参构造(原本类中也默认有无参构造)

      1
      2
      3
      Constructor<reflect.car> constructor1 = carClass.getConstructor();
      reflect.car car1 = constructor1.newInstance();
      System.out.println(car1);

      可以使用有参构造(条件是原本类中也有含参构造)

      1
      2
      Constructor<car> constructor = carClass.getConstructor(String.class, String.class, String.class);
      car car = constructor.newInstance("马自达", "阿特兹", "混动红"); System.out.println(car);

      注意:如果原本类的构造是private的,不仅需要用declaredConstructor,还要设置可行性为true

image-20230709100824508

1
2
3
Constructor<car> constructor = carClass.getDeclaredConstructor(String.class, String.class, String.class);
constructor.setAccessible(true);
car car = constructor.newInstance("马自达", "阿特兹", "混动红");
  1. 由class类获取属性(参数)

    1. 常规方法只能获取public的属性

      1
      Field[] fields = carClass.getFields();

      image-20230709102137763

    2. 得到所有的属性:(public/private)

      1
      Field[] fields = carClass.getDeclaredFields();
    3. 设置属性值:

      1
      2
      3
      4
      5
      6
      Field[] fields = carClass.getDeclaredFields();
      for (Field f:fields) {
      if(f.getName().equals("subName")){
      f.set(car,"昂克赛拉");
      }
      }

      注意:如果subName是private的,需要先setAccessible(ture)

      image-20230709103111254

      感觉很怪——从属性对象中调用方法设置类的属性,有点倒反天罡

      不过毕竟这个属性来自字节码文件,并不属于哪一个对象

    4. 得到所有方法,以及执行某个方法:

      1
      2
      3
      4
      5
      6
      7
      8
      for (Method m:methods
      ) {
      System.out.println(m.getName());
      if(m.getName().equals("say")){
      m.setAccessible(true);
      m.invoke(car,"say it!");
      }
      }

      invoke中第一个参数为执行的对象,后面的可变参数是m的参数

细说编译过程:

  • Java总览:

  • Java编译器是唯一能识别.java文件的存在——导致其必然作用

  • Java编译器的作用是:

    1. 检查符合规范

    2. 编译成class文件

  • JDK和 JRE

    • Java Development Kit(Java开发工具包)——包含Java从开发到运行的各种工具

    • Java Running Environment(Java运行环境)——只包含运行必要的工具(包括基础类库,JVM)

    • 二者的差集就和开发相关:

      关键模块:

      1. JavaC:用于编译源代码生成class文件

      2. JavaP:用于反编译,根据class文件,反解析出其中信息

      3. javadoc:生成java文档

    • 编译阶段:源码通过javac到达.class

    • 运行阶段:.class文件在JVM中运行

  • Java虚拟机只认.class字节码文件,所以只要能有合适的编译器把某种语言写的源代码编译成字节码,JVM就是通用的

    Kolin,Scala成也JVM,败也JVM

  • Lombok,AspectJ等直接修改字节码文件来修改程序:Lombok将注解生成字节码,AspectJ是在编译时由注解确定织入代码的切点和内容,进行对.class文件的字节码增强技术

字节码增强技术:
  • 本质就是指通过修改已编译的字节码文件来实现对程序行为的增强或修改的技术。

  • 据说可以实现热部署

编译!

  • 常见编译器:javac(自带),ecj(Eclipse Compiler for Java)

  • 流程:

    1. 词法分析&语法分析

      • 词法分析·:

      • 语法分析:(抽象语法树:Abstract Ayntax Tress AST)

        WC!数据标注的短语结构树!文本挖掘抄袭Javac!

      • 所以起名有问题逃不过词法分析或语法分析,例如数据类型错会在第一个报错,变量名起冲突会在第二个报错

    2. 填充符号表

      • 由符号地址(位置)和其信息构成的map

      • 作用是明确任何符号的作用范围(关联)、重复声明、类型匹配

      • 任何符号:类名、方法名、变量名

      • Attention:类的信息包括有无构造器,于是对于没有写构造器的类,会在此添加默认无参构造。

开写!

@Bean注解:

  • 步骤:

    1. 创建两个自定义注解

      @Bean:类似@Component,标记此类也要加入IoC容器,成为bean对象

      @Di:类似@Autowired,标记此处需要被注入某个bean

    2. 创建bean容器接口:手写ApplicationContext,定义方法,返回bean对象

    3. 实现bean容器接口:**手写AnnotationConfigApplicationContext()**,

      具体要做:

      1. 根据规则加载bean

        规则:扫描某个包和其子包,看类上是否有@Bean注解,如果有就把这个类通过反射实例化

      2. 返回bean对象

  • 细说怎么扫描文件夹——反射构造bean对象并添加到IoC中

    1. 参数接收的是com.itheima.Spring类似的路径,需要转成com/itheima/Spring

    2. 遍历此文件夹下所有文件,找到class文件

      1. image-20230709152921153

        urls中是下一层的所有文件/文件夹

      2. 由于url编码会将一些特殊字符(如/)转化成%加两个十六进制数,于是需要转回来

        image-20230709153145902

      3. 对于n个可进入的文件/文件夹url,为了避免bug,要进行许多判断

        • 是文件夹则再进入

        • 文件夹为空则回溯

        • 是文件还需判断是不是.class文件

        • 关键:对于符合条件的文件,需要转回.分割的路径,回Java逻辑下判断

          image-20230709154510127

        • 如果该文件是个接口,则跳过

        • 在.class中判断上面是否有注解@Bean

          暴力调用方法

        • 如果有就实例化,加入map

          还需进行一步:当前class对象是否是实现类,如果是,则加入IoC容器的形式应该是接口类做key,实现类做value

      • 学到的地方:写方法时分离出另一个方法的思考

        • 原本方法内要迭代,写出去迭代的具体方法便于修改迭代参数

        • 将相对独立的方法分离出去

          原本方法接收path,初步操作得到url的枚举类型,但这种不常见的类型尽量在原方法中就屏蔽掉不要传给分离的方法,是变成常见类型后分离出较为抽象的逻辑构成另一个方法。

@Di注解

  • 思路:

    在ApplicationContext的构造函数中,刚刚完成了填充map,map中存有各个bean对象,但这些对象的属性(引用属性)都没有被注入,于是在构造中要完成遍历+注入。

    ATTENTION: 不存在只需要被注入而本身不放入容器的类。

    因为IoC容器的bean管理就是管理内部的bean对象(配置方式的bean.xml)之间相互关系。

    即:需要被注入,自身一定要也在容器中

    过程:

    • 遍历map集合,得到其中每个value——bean对象

    • 遍历每个bean对象的每个属性,检测是否有@Di注解

    • 如果有,依赖注入——实际就是调用field的set方法(此时因为每个对象都被放松成Obj,所以没法调用自身的setter,Spring给出的方法是:

      1
      field.set(obj,map.get(field.getType()));

      感觉底层也应该是调的setter,可能用instanceof暴力匹配?

      逻辑:每个要注入的字段field,根据自身的类型在map中找到符合的对象,由此field调用注入方法。

  • 切记注意私有属性或方法要用declared


AOP

代理模式:

提出背景:为了解决多个功能混合在一个方法中,而并非每个功能都是核心功能。同时避免仅仅用分离出抽象方法但任然在一个类中导致的臃肿。

代理模式:

通过在上层调用与下层执行之间加一层代理,来使下层执行的逻辑更加清晰——分离出核心代码。

据说还可以实现解耦

image-20230709194318447

当初说创建新线程也用了代理模式:

是由于实现runnable接口的类本身没有start方法,需要用new Thread()作为代理调用Thread的start方法。

这里的代理更像是有能力的类包装没能力的类,一起带飞,

其实也就是外层代理重写内层核心类的方法,包装成需要的结构

通过调用外层代理类的方法,间接调用核心类方法。

静态代理:

代码写死

动态代理:

静态代理是在代码运行之前,代理的模式已经确定(写死)——外层代理能做的事情是有限的,动态代理在代码运行时才创建代理对象(即明确代理能做的事情),相较而言较为动态

  • 方法:

    实例化时返回代理对象

    newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHander h)

    通过代理对象使用原本方法即可自动调用包装后的方法

    invocationHander接口的实现类需要重写invoke()方法——即为包装的逻辑

    image-20230709202626943

    参数中:

    • proxy表示代理的对象

    • method表示代理可能调用的方法(在此重写为Method类型)便于调用

      image-20230709203400562

      反射思想————通过属性/方法改变类(倒反天罡!两极反转!)

      target即代理的对象

    • args表示method的参数

AOP概念》Aspect Oriented Programming

通过预编译和运行期间动态代理方式,在不修改源代码的情况下,给程序动态统一添加额外功能的技术。

利用AOP可以对业务逻辑的各个部分进行隔离,使得业务逻辑各部分间耦合度降低,提高程序可重用性。

  • 横切关注点:分散在各个模块中,解决同一问题的非核心业务的部分

    如:用户验证、日志管理、事务处理、数据缓存等

    这种思路可以使用多个横切关注点对相关方法进行不同层面的增强

  • 通知(增强):要添加的功能模块——安全/事务/日志……

    通知方法:实现此通知的方法

    分类:

    1. 前置通知

    2. 返回通知:代理目标的方法成功结束后执行

    3. 异常通知:代理目标的方法异常结束执行

    4. 后置通知:最终结束执行

    5. 环绕通知:方法之前之后都执行

  • 切面:

    封装前置通知、后置通知…的类

  • 连接点:

    在各个方法中一致的、能够添加增强的位置

  • 切入点:

    得到某个连接点,就是切入点

动态代理分类

  • 当代理目标有接口时——生成的代理类是和它实现了同样接口的好兄弟类

  • 当没有接口时——生成的代理类是继承了此类的好大儿

    为什么不能纯粹的另外一个类——通过调用代理目标类的方法前后切入实现增强?

    因为生成动态代理类需要一个模板,来确定代理类的结构和行为——比如在JDK实现中,method就间接映射到模板的方法......

  1. JDK动态代理

    当目标类有接口时用JDK和cglib

  2. CGLib动态代理

    目标类没有接口时只能用vglib


AspectJ

AOP框架

spring借用其中的注解进行便利开发

据说AspectJ本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件——导致效果是动态代理。

动态代理基本步骤:
  • 引入aop和aspect相关依赖

  • 配置xml文件或配置类实现组件扫描(依赖IoC容器)和aop的aspectJ的自动代理(识别加了@Aspect注解,为其自动生成代理类)

  • 创建目标资源

    • 接口

    • 实现类

  • 创建切面类

    • 明确切入点

    • 明确通知类型

      通知类型: 在切入方法上添加,明确切入点

      • 前置:@Before()

      • 返回:@AfterReturning() 在return语句后执行

      • 异常:@AfterThrowing()

      • 后置:@After() 类似try-catch中的finally

      • 环绕:@Around() 通过使用try-catch-finally,编写以上各个位置的切入

      通过设置参数value=“”,编写切入点表达式

      其作用就是:明确在哪一个/哪些方法附加切入(有点像sql)

    • 每个切面类的方法都可以有一个参数——JoinPoint类型,用于获取关于切入点的属性。

      ------

  • 返回通知AfterReturning——能够得到方法的返回值

    在注解@AfterReturning中添加参数:returning = ”sthName“

    之后便可在方法参数中使用Object类型的sthName

  • 异常通知AfterThrowing——能够得到异常类型

    形式和上面类似:有参数 throwing = ”sthName“

  • 环绕通知Around

    在try-catch-finally中适当位置可切入属于上面的方法

    • 在try中一开始写的是@Before

    • 需要模拟一个方法执行

      用更强的ProceedingJoinPoint类型的joinpoint调用proceed方法,获取一个Obj类型的返回值result

    • 返回之后可调用@AfterReturning

    • catch中可写异常处理@AfterThrowing

    • finally中写@After

    ------


    btw:切面类和代理类不一致:代理类是外包目标类实现拦截和增强,切面类是封装横切关注点的另外的类。切面类使用代理类来实现切面的功能,代理类调用切面类的方法来增强或拦截。

  • 重用切入点表达式:

    由于对于同一个方法或同一类方法可能需要写多个切入方法,同时就需要重复写多次切入点表达式,所以可以提取出来,进行复用和在核心代码中简单引用

    定义方法,给方法添加注释@Pointcut(value = “表达式”),然后调用方法即可

    如果定义的方法在不同切面类中:

    为什么不是抽象成属性直接引用呢?感觉也可以吧

  • 切面优先级:

    相同的目标方法上有多个切面时,切面通过优先级控制执行顺序

    外层的优先级较高,先执行

    使用@Order()注解, 参数越小,越先执行

基于配置文件的AOP切入

紧紧用配置文件完成所有事——

  • 找到切面类:

  • 配置切入点:

  • 配置五种切入方法:

全注解开发

需要将bean.xml配置文件转化成配置类,用注解完成相应配置

  • 开启全文扫描

  • 开启AspectJ自动代理

    • 据说EnableAspectJAutoProxy有个参数proxyTargetClass,是boolean类型

      true——Spring使用基于类的代理(CGlib)

      false——Spring使用基于接口的代理(JDK原生)

BTW:配置文件常见前缀配置

1
2
3
4
5
6
7
8
9
10
11
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://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">

JUnit单元测试

  • Spring整合JUnit,可以省略每次创建容器的过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //@SpringJUnitConfig(locations = "classpath:bean.xml")
    @SpringJUnitConfig(classes = bean.class)
    public class Test1 {

    @Autowired
    Car car;

    @Test
    public void say(){
    System.out.println(car.say());
    }
    }

通过注释明确加载哪个bean.xml配置文件,or某个配置类

JUnit还有巨多关于测试的功能

BTW:bean.xml注解总结:

另外:

<context>标签用于配置程序上下文信息(上下文可以理解为IoC容器)

  • <context:component-scan>:开启组件扫描

  • <context:annotation-config>:开启注解驱动功能

  • <context:poperty-placeholder>:用于加载属性文件中的属性值

事务

  • Spring框架对JDBC进行封装,封装结果是jdbcTemplate

JDBCTemplate

  • 引入的依赖:

    1. Spring-jdbc

    2. mysql

    3. 德鲁伊连接池druid

      • 连接池:

        解决普通JDBC获取连接,关闭连接的耗时繁琐操作——池化——将建立连接和关闭连接对用户屏蔽掉,转而对其面向调用连接和释放连接。

        上层操作简单看:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        package com.czxy;

        import com.alibaba.druid.pool.DruidDataSource;
        import com.alibaba.druid.pool.DruidPooledConnection;
        import org.junit.Test;

        import java.sql.SQLException;

        public class TestDruid {
        @Test
        public void testDemo() throws SQLException {
        //1 获得连接池
        //1.1 核心类
        DruidDataSource dataSource = new DruidDataSource();
        //1.2 基本4项
        // 1) 驱动
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        // 2) 连接
        dataSource.setUrl("jdbc:mysql://localhost:3306/ssm_db1");
        // 3) 用户
        dataSource.setUsername("root");
        // 4) 密码
        dataSource.setPassword("1234");
        //1.3 特殊项【可选】
        // 1) 初始化大小
        dataSource.setInitialSize(5);
        // 2) 最大值活动数
        dataSource.setMaxActive(10);
        // 3) 最小空闲数
        dataSource.setMinIdle(2);

        //2 从连接池中获得连接
        //对比两次获取到的连接是否一致
        DruidPooledConnection conn = dataSource.getConnection();
        System.out.println(conn);
        //用完了,返还到连接池中
        conn.close();

        DruidPooledConnection conn2 = dataSource.getConnection();
        System.out.println(conn2);
        conn2.close();
        }
        }
  • 经典操作:

    创建配置文件jdbc.properties,里面配置连接数据库的必要信息——用户,密码,数据库url,驱动

    在spring的配置文件(bean.xml)中引入此文件引入,引入后就可通过K-V形式访问到数据(properties是k=v,yml格式是k:v)

  • 配置JDBCTemplate——将其作为一个bean对象

    说明本身就是一个类

    JDBCTemplate有一个属性——dataSource——用于绑定连接池

  • sql语法复习:

    占位符?——表示写sql时并不确定,需要在真正传入DB时传入参数的位置

    mybatis的#{},${}转化成?然后用参数注入也是模拟此过程。

  • JDBCTemplate让程序员面向类的API,于是API设计的很人性化(通过可变参数完成注入)

  • mybatis并没有对象,而是通过配置和注解match到程序和目标sql,此时传参的操作就显得刻意了一点。

    1
    void updateSalary(Integer id, Integer salary, LocalDate fromDate,LocalDate toDate);
    1
    2
    3
    4
    5
    6
    7
    <update id="updateSalary">
    update salaries
    set salary=#{salary}
    where emp_no = #{id}
    and from_date = #{fromDate}
    and to_date = #{toDate}
    </update>
  • 查询的写法:

    写法一:手写中间参数的实现类(用resultSet和rowNum获取想要的结果)

    思想:查询的参数中有resultSet结果集,此类型内置了一个表,封装了一些API来访问这张结果表:

    写法二:

    使用中间参数的实现类

事务:

事务指一个操作序列,这些操作要么全部执行,要么全不执行。

  • ACID

    • 原子性(Atomicity):不可细分,绑定整体

    • 一致性(Consistency):事务中的数据正确,在财会中满足会计恒等式,在sql中满足主键不重复等等

    • 隔离性(Isolation):多个事务之间——在提交前相互隔离,一个事务不会访问到其他事务的中间状态。也要应对并发修改。

    • 持久性(Durability):提交的事务必须保存下来,既是系统崩溃,重启后也要有保留。

  • 编程式事务和声明式事务:

    编程式要求手动开启事务,提交事务,异常回滚…

    声明式仅仅通过配置即可实现——声明式是编程式的封装,将共同部分屏蔽掉。

  1. 开启事务

    1. 添加事务配置

    2. 在Service层添加注解:@Transactional

      • 加到方法上单独控制方法

      • 加到类上控制所有方法

  2. @Transactional的参数


SpringBasic
https://13038032626.github.io/2024/05/17/SpringBasic/
Author
Ha_Ha_Wu
Posted on
May 17, 2024
Licensed under