【Spring】从零开始学习Spring框架

专栏目录

文章目录

〇、启示录

0.1 OCP开闭原则

0.2 DIP依赖倒置

0.3 IoC控制反转

0.4 Spring特点

  1. 轻量
    • 大小与开销都是轻量级
    • 非侵入式,对象不依赖于具体类
  2. 控制反转
    • 不new对象
    • 交出对象关系
  3. 面向切面
    • 面向切面编程,允许通过分离应用的业务辑和系统级服务进行内聚性的开发
  4. 容器
    • 可以包含并管理对象的配置和生命周期
  5. 框架
    • 将简单的组件配置,组合成为复杂的应用

0.5 创建Spring项目

  1. 使用maven的pom.xml引入依赖
    <!--    配置仓库-->
    <project>
        <!--   使用提前版本需要设置仓库-->
        <repositories>
            <repository>
                <id>repository.spring.snapshot</id>
                <name>Spring Snapshot Repository</name>
                <url>https://repo.spring.io/snapshot</url>
            </repository>
    
        </repositories>
        <!--引入依赖   6.0-->
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>6.0.10</version>
            </dependency>
        </dependencies>
    </project>
    
  2. 创建对象—Bean包中创建对象类
  3. 创建配置文件<*.xml>
    <beans>
         <bean id="唯一标识符,不能重复" class="类的全路径"/>
    </beans>
    
  4. 写Spring程序
    class Demo{
    @Test
     public void test(){
    //      1.获取spring容器对象
    // ApplicationContext是一个接口       
    // 从根目录开始搜索spring配置文件
           ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring配置文件的目录");
    //      2.根据bean的id生成对象     
           Object name = applicationContext.getBean("Bean的id");
     }
    }
    
  • 通过无参构造方法,使用反射机制实现
  • 创建后存储到Map中
  • ClassPathXmlApplicationContext可以同时绑定多个xml文件
  • getBean方法传递id值错误会出现异常而报错
  • applicationContext.getBean(“Bean的id”,返回类型[Data.class]);
  • ApplicationContext父接口为BeanFactory(IoC顶级接口)
  • Spring底层IoC实现:XML解析+工厂模式+反射机制

0.6 Log4j日志框架

  1. maven引入Log4j2依赖
  2. log4j-core,log4j-slf4j2-impl
  3. 写log4j配置文件
    
    <configuration>
        <loggers>
            <!-- ALL < TRACE < DEBUG <INFO <WARN <ERROR <FATAL < OFF  -->
            <root level="DEBUG">
                <appender-ref ref="spring6log"/>
            </root>
        </loggers>
        <appenders>
            <console name="name" target="SYSTEM_OUT">
                <PatternLayout patten="日志格式"/>
            </console>
        </appenders>
    </configuration>
    
  4. 使用Log4j
    class Log{
        @Test
    public void test(){
            // 1.创建日志记录器对象
        Logger logger=LoggerFactory.getLogger();
        logger.level(str);
        }
    }
    

一、IoC控制反转

  • 控制反转

    • 控制反转是一种思想
    • 降低程序耦合度,提高程序扩展力,达到OCP原则,达到DIP原则
    • 控制反转反转的是什么
      • 将对象的创建权力交出去,交给第三方容器负责
      • 将对象和对象之间关系的维护交出去,交给第三方容器负责
    • 如何实现
      • 依赖注入–>DI
  • 依赖注入

    • 实现了控制反转的思想
      • Spring沟通过依赖注入的方式完成Bean的管理
      • Bean管理–>Bean对象的创建,Bean对象中属性的赋值(对象间关系的维护)
    • 依赖
      • 对象和对象之间的关联关系
    • 注入
      • 一种数据传递行为,通过注入行为来让对象和对象产生关系
    • 实现方式
      • set注入
      • 构造注入

1.1 依赖注入

1.1.1 Set方法注入 – 重点

先构造对象,再注入,必须存在set方法


<bean>
    <property name="spring调用对应的set方法" ref="对应的传参"/>
</bean>
  1. 注入外部Bean

    <beans>
        <bean id="bean1" class="#"/>
        <bean id="bean2" class="#">
        <property name="bean1" ref="#"/>
        </bean>
    </beans>
    
  2. 注入内部Bean—不常用

    <beans>
        <bean id="bean2" class="#">
            <property name="bean1">
                <bean class="#"/>
            </property>
        </bean>
    </beans>
    
  3. 简单类型

    • String,int,enum,CharSequence,Number,Date,Temperal(Java8提供的时间类型),URI,URL,Locale,Class等等(基本类型,包装类)
    • 一般实际开发不会将Data是为简单类型,而用ref
    <!--简单类型用Value进行赋值-->
    <bean id="#" class="#">
        <property name="#" value="#"/>
        <!--    若要Data简单类型,对格式有特殊要求-->
        <property name="#" value="日期 月 ..."/>
    </bean>
    
  4. 级联注入

    • property的配置顺序不能颠倒
    • 级联对象必须提供get方法
    <beans>
        <bean id="class1" class="#"/>
        <bean id="class2" class="">
            <property name="student" value="#"/>
            <property name="class1" ref="#"/>  
            <property name="class1.name" value="#"/>
        </bean>
    </beans>
    
  5. 注入数组

    <beans>
        <bean id="class1" class="#"/>
        <bean id="class2" class="">
            <property name="class1">
                <array>
                    <ref name="#"/>
                    <ref name="#"/>
                    <ref name="#"/>
                </array>
            </property>
        </bean>
    </beans>
    
  6. 注入List集合,Set集合,Map类,Properties

    <beans>
        <bean id="class1" class="#"/>
        <bean id="class2" class="">
            <property name="class1">
                <list><!--有序可重复-->
                    <value>#</value>
                    <value>#</value>
                    <value>#</value>
                </list>
                <set><!--无序不可重复-->
                    <ref>#</ref>
                    <ref>#</ref>
                    <ref>#</ref>
                </set>
                <map>
                    <entry key="#" value="#"/>
                    <entry key="#" value="#"/>
                    <entry key="#" value="#"/>
                </map>
                <props><!--只能是String类型-->
                    <prop key="#" >value</prop>
                    <prop key="#" >value</prop>
                    <prop key="#" >value</prop>
                </props>
            </property>
        </bean>
    </beans>
    
  7. 注入null和空串

    <elem>
        <!--    不注入默认null-->
        <!--手动注入null-->
        <property name="#">
            <null/>
        </property>
        <!--注入空串-->
        <property name="#" value=""/>
        <property name="#">
            <value/>
        </property>
    </elem>
    
  8. 注入特殊符号

    <properties>
        <!--1. 使用实体符号代替特殊符号-->
        <property name="#" value="a > b"/>
        <!--使用<![CDATA[]]>标签-->
        <property>
            <value><![CDATA["#"]]></value>
        </property>
    </properties>
    

    实体符号

    特殊符号转义字符
    [>]&gt
    [<]&lt
    [']&apos
    ["]&quot
    [&]&amp
构造注入

创建对象同时赋值


<bean id="#" class="#">
    <!--    构造方法的两个传参-->
    <!--    index指定参数下标-->'
    <!--    ref指定参数对应id-->
    <constructor-arg index="0" ref="#"/>
    <constructor-arg index="1" ref="#"/>
    <!--    通过参数名字指定传参-->
    <constructor-arg name="#" ref="#"/>
    <!--    不指定下标,不指定名字-->
    <!--    根据类型进行注入-->
    <constructor-arg ref="#"/>
</bean>
1.1.2p命名空间注入 — 底层还是set注入
  1. 在spring配置文件头添加p命名空间 xmlns:p="http://www.springframework.org/scema/p"

    <beans xmlns="#"
           xmlns:xsi="#"
           xmlns:p="http://www.springframework.org/scema/p"
           xsi:schemaLocation="#">
    </beans>
    
  2. 使用p命名空间

    <beans>
        <bean id="#" class="#" p:name="#" p:age="#" p:type-ref="#"/>
    </beans>
    
1.1.3 c命名空间注入 — 底层基于构造注入
  1. 在spring配置文件头添加c命名空间 xmlns:c="http://www.springframework.org/scema/c"

    <beans xmlns="#"
           xmlns:xsi="#"
           xmlns:c="http://www.springframework.org/scema/c"
           xsi:schemaLocation="#">
    </beans>
    
  2. 使用

    <beans>
        <bean id="#" class="#" c:name="#" c:下标="#"/>
    </beans>
    
1.1.4 util命名空间 — 让配置复用,针对集合

在spring配置文件头添加util命名空间 xmlns:util="http://www.springframework.org/scema/util , xsi:schemaLocaton="新增将beans-->util"


<beans xmlns="#"
       xmlns:xsi="#"
       xmlns:util="http://www.springframework.org/scema/util"
       xsi:schemaLocation="#">
</beans>
  1. 使用

    <beans>
        <util:properties id="prop">
            <prop key="#">#</prop>
            <prop key="#">#</prop>
            <prop key="#">#</prop>
        </util:properties>
        <bean id="#" class="#">
            <property name="#" ref="prop"/>
        </bean>
    </beans>
    
1.1.5 基于XML的自动装配 — 基于set注入
  1. 根据名字进行装配

    <beans>
        <bean id="#" class="#" autowire="byName"/>
        <!--根据名字装配的目标bean的id需要是对应的set方法名-->
        <bean id="名称(方法名)" class="#"/>
    </beans>
    
  2. 根据类型进行装配

    <beans>
        <bean id="#" class="#" autowire="byType"/>
        <!--根据名字装配的目标bean的id需要是对应的set方法名-->
        <bean class="#"/>
    </beans>
    
1.1.6 引入外部配置文件

使用context时spring默认首先搜索windows的命名,建议外部配置文件内容命名添加前缀


<beans>
    <!--    引入context命名空间-->
    <!--    1. xmlns:context="#"-->
    <!--    赋值将xsi:schemaLocation将beans改为context-->
    <context:property-placeholder location="#"/>
    <bean id="#" class="#">
        <!--        取值-->
        <property name="#" value="${name}"/>
        <property name="#" value="${name}"/>
        <property name="#" value="${name}"/>
    </bean>
</beans>

1.2 Bean的作域

1.2.1 singleton

Spring默认情况下
Bean时单例的,在Spring上下文初始化时实例化
每次调用getBean都返回同一个单例

1.2.2 prototype

singleton–>单例
prototype–>多例的


<bean id="#" class="#" scope="#"/>
1.2.3 其他scope

request --> 一次请求 仅限web应用

session --> 一次会话 web应用

global session --> 一次会话 portlet应用专用

application --> 一个应用对应一个Bean 仅限web应用

websocket --> 一个websocket生命周期一个Bean 仅限web应用

自定义scope --> 很少使用

1.2.4 自定义scope

例子:作用域:线程 --> 一个线程一个Bean

  1. 自定义Scope,实现scope接口

    • spring内置了线程范围的类:org.springframework.context.support.SimpleThreadScope,可直接使用
  2. 将自定义的Scope注册到Spring容器中

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="自定义scope名字">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry> 
            </map>
        </property>
    </bean>
    
  3. 使用Scope

    <bean id="#" class="#" scope="自定义scope"/>
    

二、Gof—四人组 --> 设计模式

  • 设计模式:一种可以被重复利用的解决方案
  • GoF(Gang of Four) --> 四个作者合著的《Design Patterns : Elements of Reusable Object-Oriented Software》《设计模式》
  • 描述了23中设计模式
  • 创建型:解决对象创建问题
    • 单例模式
    • 工厂方法模式
    • 抽象工厂模式
    • 建造者模式
    • 原型模式
  • 结构型:一些类或对象组合在一起的经典结构
    • 代理模式
    • 装饰模式
    • 适配器模式
    • 组合模式
    • 享元模式
    • 外观模式
    • 桥接模式
  • 行为型:解决类或对象之间的交互问题
    • 策略模式
    • 模板方法模式
    • 责任链模式
    • 观察者模式
    • 迭代子模式
    • 命令模式
    • 备忘录模式
    • 状态模式
    • 访问者模式
    • 中介者模式
    • 解释器模式

2.1 GoF工厂模式

2.1.1 三种状态

  • 简单工厂模式(Simple Factory)
    • 静态工厂方法模式
  • 工厂方法模式(Factory Method)
    • 23种设计模式之一
  • 抽象工厂模式(Abstract Factory)
    • 23种设计模式之一

2.1.2 简单工程模式

包括三个角色

  • 抽象产品角色 —> 抽象类
  • 具体产品角色 —> 实现抽象类
  • 工厂类角色 —> 静态方法
//* 抽象产品角色 ---> 抽象类
public abstract class Weapon {
    public abstract void attack();
}

//* 具体产品角色 ---> 实现抽象类
public class Tank extends create.abstractFactory.Weapon.Weapon {
    @Override
    public void attack() {
        System.out.println("Tank");
//        ...
    }
}

//* 工厂类角色 ---> 静态方法
public class WeaponFactory {
    public static Weapon get(String weaponType) {
//        ...
        if (weaponType.equals("Tank")) {
            return new Tank();
        } else if (equals()) {
            System.out.println("elseIF");
        } else {
            System.out.println("else");
        }
    }
}

解决问题

  • 客户端只需要对工厂索要

  • 简单工厂模式实现了职责分离,客户端不需要关系生产细节

  • 客户端只负责消费,工厂类负责生产.生产者和消费者分离

  • 缺点

    • 扩展时,需要修改工厂类,违背OCP
    • 工厂类责任比重大,不能出问题,一旦出问题,全部瘫痪–>全能类,上帝类

2.1.3 工厂方法模式

解决了简单工厂模式的OCP问题
一个工厂对应生产一种产品
解决了工厂是全能类的问题

包括三个角色

  • 抽象产品角色 —> 抽象类
  • 具体产品角色 —> 实现抽象类
  • 抽象工厂角色 —> 抽象静态方法
  • 具体工厂角色 —> 实现抽象工厂类
//* 抽象产品角色 ---> 抽象类
public abstract class Weapon {
    public abstract void attack();
}

//* 具体产品角色 ---> 实现抽象类
public class Tank extends create.abstractFactory.Weapon.Weapon {
    @Override
    public void attack() {
        System.out.println("Tank");
//        ...
    }
}

//* 抽象工厂角色 ---> 抽象静态方法
public abstract class WeaponFactory {
    public abstract Weapon get();
}

//* 具体工厂角色 ---> 实现抽象工厂类
public class TankFactory extends create.abstractFactory.Factory.WeaponFactory {
    @Override
    public static create.abstractFactory.Weapon.Weapon get() {
        return new Tank();
    }
}

优点

  • 扩展产品时,符合OCP原则,只需要增加产品角色类和对应的工厂角色类

缺点

  • 每次增减产品都要增加类,是的系统中类的个数增加,增加系统复杂度和依赖度

2.1.4 抽象工厂模式

优点

  • 当⼀个产品族中的多个对象被设计成⼀起⼯作时,它能保证客户端始终只使⽤同⼀个产品族
    中的对象。

缺点

  • 产品族扩展⾮常困难,要增加⼀个系列的某⼀产品,既要在AbstractFactory⾥加代码,⼜要
    在具体的⾥⾯加代码****

2.2 Bean的实例化方式

Spring为Bean提供实例化方式

  • 构造方法实例化
  • 简单工厂模式实例化
  • 通过factory-bean实例化
  • 通过FactoryBean接口实例化

2.2.1 构造方法

通过绑定类的全路径,自动调用无参构造方法实例化对象


<bean id="#" class="#"/>

2.2.2 简单工厂模式

静态方法

在配置文件中指定某个类中的某个方法

factory-method指定工厂类的某个静态方法


<bean id="#" class="#" factory-method="get"/>

2.2.3 factory-bean实例

工厂模式,实例方法

告诉Spring要调用哪个对象的哪个方法

通过factory-bean + factory-method 调用


<beans>
    <bean id="" class=""/>
    <bean id="" class="" factory-bean="" factory-method=""/>
</beans>

2.2.4 FactoryBean接口

实现FactoryBean接口,不需要描述factory-bean, factory-method

可简化factory-bean实例化方法

这个方法可以对普通Bean进行加工处理

public class PersonFactory extends FactoryBean<Person> {
    @Override
//    重写方法
    public Person getObject() {
        return new Person();
    }
}

<bean id="person" class="PersonFactory"/>

2.2.5 BeanFactory 和 FactoryBean 的区别

IoC的顶级容磁

BeanFactory --> Bean工厂 是工厂,负责创建Bean对象

FactoryBean是一个Bean

辅助Spring实例化其他bean对象

Spring有两类Bean

  • 普通Bean
  • 工厂Bean

2.3 Bean的生命周期

生命周期 --> 对象从创建到最终销毁的过程

2.3.1 Bean生命周期 --> 5步

  1. 实例化Bean
  2. Bean属性赋值
    • 调用set方法
  3. 初始化Bean
    • 使用init方法,需要指定init-method
  4. 使用Bean
  5. 销毁Bean
    • 使用destroy方法,需要制定destroy-method

2.3.2 Bean生命周期 --> 7步

  1. 实例化Bean
  2. Bean属性赋值
    • 调用set方法
  3. 执行"Bean后处理器"的before方法
  4. 初始化Bean
    • 使用init方法,需要指定init-method
  5. 执行"Bean后处理器"的after方法
  6. 使用Bean
  7. 销毁Bean
    • 使用destroy方法,需要制定destroy-method

2.3.3 Bean生命周期 --> 10步

  1. 实例化Bean
  2. Bean属性赋值
    • 调用set方法
  3. 检查Bean是否实现了Aware相关接口,并设置相关依赖
  4. 执行"Bean后处理器"的before方法
  5. 检查Bean是否实现了InitializingBean接口,并实现方法
  6. 初始化Bean
    • 使用init方法,需要指定init-method
  7. 执行"Bean后处理器"的after方法
  8. 使用Bean
  9. 检查Bean是否实现了DisposableBean接口,并实现了方法
  10. 销毁Bean
  • 使用destroy方法,需要制定destroy-method

2.3.4 Bean的作用域不同,管理方式不同

Spring容器只对Singleton的Bean进行完整的生命周期管理

prototype的Bean,只负责初始化完毕 --> 前八步

2.3.5 自己new的对象如何让Spring管理

new的对象之后通过DefaultListableBeanFactory方法加入Spring管理

public class Test {
    public static void main(String[] args) {
        Object object = new Object();
        DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
        defaultListableBeanFactory.registerSingleton("id", object);
    }
}

2.4 Bean的循环依赖

循环依赖 --> A对象中有B对象,B对象中有A对象 (例如丈夫对象和妻子对象)

2.4.1 singleton + setter

没有问题 单例的,每个对象只有唯一的一个

Spring管理Bean两个阶段

  1. Spring容器加载时,实例化Bean,任意一个Bean实例化后,马上曝光[属性赋值之前]

  2. 曝光之后,在进行属性的赋值

核心解决方案–>实例化对象和对象属性的赋值分为两个阶段

2.4.2 prototype + setter

BeanCurrentlyInCreationException异常,Bean处于创建中异常

都是prototype时出现

2.4.3 基于构造注入

不可以,在构造过程中要赋值,赋值就要构造,循环–>创建中异常

2.4.4 底层代码

2.5 反射机制

2.5.1 分析方法四要素

  1. 调用哪个对象
  2. 调用哪个方法
  3. 传递什么参数
  4. 返回什么结果

Class<?> clazz=CLass.forName(“”);

Method doSome = clazz.getDeclaredMethod(“methodName”,Class…);

doSome.invoke(对象,参数…);

//无参对象

Object obj=clazz.Construct();

//根据属性名获取其类型

clazz.getDeclaredField(Str)–>getType();

三、手写Spring框架

3.1 准备阶段

  1. maven项目,配置pom.xml
    • dom4j --> 解析xml
    • junit --> 单元测试
  2. 创建Bean类,写入spring.xml [使用者角度]

3.2 开发阶段

  1. 创建ApplicationContext接口–>解析xml
  2. classPathXmlApplicationContext实现ApplicationContext接口–>实例化Bean–>Map
    • SAXReader[dom4j解析xml核心对象]
    • 获取指向文件的输入流 InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation)
    • 读取文件 Document document = reader.read(in)
    • 使用xpath获取所有bean标签 List<Node> beans = document.selectNodes("//bean")
    • 遍历列表,曝光实例化Bean
      • 向下转型Element,为了获取更多方法 Element beanElt = (Element) node
      • 获取bean的id,class属性 String id = beanElt.attributeValue("id")
        *
        获取无参构造方法 Class<?> aClass = Class.forName(className) ; Constructor<?> defaultCon = aClass.getDeclaredConstructor()
      • 调用无参构造方法实例化 Object bean = defaultCon.newInstance()
      • 曝光Bean并存入Map singletonObjects.put(id,bean)
    • 遍历列表,给Bean赋值
      • 向下转型Element,为了获取更多方法 Element beanElt = (Element) node
      • 获取bean的id,class属性 String id = beanElt.attributeValue("id")
      • 获取无参构造方法 Class<?> aClass = Class.forName(className)
      • 获取bean下的property List<Element> properties = ele.elements("property")
      • 遍历property
        • 获取name属性,value属性,ref属性 String propertyName = property.attributeValue("name")
        • 获取属性类型 Field declaredField = aClass.getDeclaredField(propertyName)
        • 获取set方法名 String setMethodName="set"+propertyName.toUpperCase().charAt(0)+propertyName.substring(1)
        • 获取set方法 Method setMethod = aClass.getDeclaredMethod(setMethodName, declaredField.getType())
        • 调用set方法
          • value为空 String ref = property.attributeValue("ref")
          • ref为空
            • 获取simpleName String propertyType = declaredField.getType().getTypeName()
            • switch进行类型转换
            • 对属性赋值
    • 实现getBean方法

四、IoC注解式开发

简化sml配置

Spring6倡导全注解式开发

4.1 回顾注解

4.1.1 自定义注解
  • public @interface name{}
  • 标注注解的注解,元注解 @Target(value = { ElementType})
  • 标注注解最终保存在class文件中,并且可以被反射机制读取 Retention(RetentionPolicy)

@Target(ElementType.TYPE)
public @interface Component {
    String value();
}
4.1.2 反射注解

@Component(属性名 = 属性值)
public class User {
}

public class ReflectAnnotation {
    public static void main(String[] args) {
        Class<?> aClass = Class.forName("User");
        //判断类上是否有注解
        if (aClass.isAnnotationPresent(Component.class)) {
            //获取类上的注解
            Component annotation = aClass.getAnnotation(Component.class);
            System.out.println(annotation.value());
        }
    }
}
4.1.3 组件扫描

只知道包的名字,扫描包下的所有类,当类上具有特定注解的时候,实例化该对象,存放到Map中


class ComponentScan {
    public static void main(String[] args) {
        Map<String, Object> beanMap = new HashMap<>();

        String packageName = "web";
        String packagePath = packageName.replaceAll("\\.", "/");
        //系统加载器
        URL url = ClassLoader.getSystemClassLoader().getResource(packagePath);
        String path = url.getPath();
        File file = new File(path);
        File[] files = file.listFiles();
        Arrays.stream(files).forEach(f -> {
            String className = packageName + f.getName().split("\\.")[0];
            //通过反射机制解析注解
            Class<?> aClass = Class.forName(className);
            if (aClass.isAnnotationPresent(Component.class)) {
                Component annotation = aClass.getAnnotation(Component.class);
                String id = annotation.value();
                Object object = aClass.newInstance();
                beanMap.put(id, object);
            }
        });
    }
}

4.2 声明Bean的注解

步骤:

  1. 依赖aop --> spring context
  2. 添加context命名空间
  3. 给Spring开个那就指定要扫描的包中的类
    <context:component-scan base-package="#"/>
    
  4. 给Bean类上使用注解

负责声明Bean的注解,常见四个

  1. @Component --> 组件

    public @interface Component{
        String value() default "";
    }
    
  2. @Controller --> 控制器 --> 表示层

    public @interface Controller{
        @AliasFor{ //别名
            annotation = Component.class;
        }
    }
    
  3. @Service --> 业务 --> 用在Service层

    public @interface Service{
        @AliasFor{ //别名
            annotation = Component.class;
        }
    }
    
  4. @Repository --> dao --> 持久层

    public @interface Repository{
        @AliasFor{ //别名
            annotation = Component.class;
        }
    }
    

4.3 多包扫描

  1. 用逗号隔开

    <context:conmponent-scan base-package="#,#"/>
    
  2. 直接指定多个包的共同父包,但会牺牲效率

4.4 选择性实例化Bean

区别实现Controller,Service,Repository选择性实例化注解的bean

  1. 添加use-default-filters=“false” --> 所有注解Bean实例化失效

    <context:component-scan base-package="#" use-default-filters="false">
    <!--    添加生效的-->
        <context:include-filter type="annotation" expressioni="Type"/>
    </context:component-scan>
    
  2. 添加use-default-filters=“true” --> 所有注解Bean实例化生效

    <context:component-scan base-package="#" use-default-filters="true">
    <!--    添加失效的-->
        <context:exclude-filter type="annotation" expressioni="Type"/>
    </context:component-scan>
    

4.5 负责注入的注解

Bean属性赋值的注解:

  1. @Value --> 注入简单类型 不需要提供setter

    public class Value{
        @Value("String")
        private String driver;
        @Value("name")
        private String name;
        @Value("password")
        private String password;
        @Value("30")
        private int age;
    //    也可以使用在方法上
        public Value(@Value("") String name){}
    }
    
  2. @Autowired --> 注入非简单类型,根据类型

    public class Autowired{
        @Autowired
        private User user;
    }
    
  3. @Qualifier --> 与@Autowired一起使用,负责注入名字

    public class Qualifier{
        @Autowired
        @Qualifier("user")
        private User user;
    }
    
  4. @Resource

    • 可以进行非简单类型注入,是JDK扩展包中,属于标准注解
    • 默认根据名称装备byName,为指定名字会将属性名作为name,否则根据类型装配
    • 只能用在setter上
    • 依赖–>javax.annotation
    • spring6支持JakartaEE9规范
    • spring5支持JavaEE规范
    public class Resource{
        @Resource("nameBean")
        private Name name;
    }
    

4.6 全注解开发


@Configuration
@ComponentScan({"#", "#"})
class Spring6 {
}

class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spring6.clss);
        Student student = context.getBean("student");
    }
}

五、JdbcTemplate

Spring提供的JDBC模板类,对JDBC的封装,简化JDBC

同样也可以让Spring继承ORM框架:Mybatis,Hibernate等

5.1 环境准备

依赖:

spring-context

mysql

spring-jdbc

junit


<beans>
    <bean id="#" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="#" ref="#"/>   <!--自己的数据源-->
    </bean>
</beans>

5.2 增删改

class Test {
    public static void main(String[] args) {
        String sql;//SQL语句
        int count = jdbcTemplate.update(sql, "", "参数");
    }
}

5.3 查找

class Test {
    public static void main(String[] args) {
        String sql;
//        单个查找
        User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), "参数");
//        多个查找
        List<User> user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class));
//        查一个值
        int count = jdbcTemplate.queryForObject(sql, int.class);
    }
}

5.4 批量添加

class Test {
    public static void main(String[] args) {
        String sql;
        //准备数据
        Object[] objects1 = {"", ""};
        Object[] objects2 = {"", ""};
        Object[] objects3 = {"", ""};
//        添加list集合
        List<Object[]> list = new List<>();
        list.add(objects1);
        list.add(objects2);
        list.add(objects3);
//        执行
        int tf = jdbcTemplate.batchUpdate(sql, list.class);
    }
}

5.5 批量增删改

class Test {
    public static void main(String[] args) {
        String sql;
        //准备数据
        Object[] objects1 = {"", ""};
        Object[] objects2 = {"", ""};
        Object[] objects3 = {"", ""};
//        添加list集合
        List<Object[]> list = new List<>();
        list.add(objects1);
        list.add(objects2);
        list.add(objects3);
//        执行
        int tf = jdbcTemplate.batchUpdate(sql, list.class);
    }
}

5.6 回调函数

import java.sql.PreparedStatement;
import java.sql.ResultSet;

class Test {
    public static void main(String[] args) {
        String sql;
        jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
            @Override
            public User doInPreparedStatement(PreparedStatement ps) throws SQLExceptiion, DataAccessException {
                ps.setInt(1, 2);//给第几个问好赋什么值
                ResultSet rs = ps.executeQuery();
                if (rs.next()) {
                    int id = rs.getInt("id");
                    String realName = rs.getString("real_name");
                    int age = rs.getInt("age");
                    user = new User(id, realName, age);
                }
                return user;
            }
        });
    }
}

5.7 使用Druid连接池

依赖 --> druid


<beans>
    <bean id="#" class="DruidDataSource">
        <property name="#" ref="#"/>   <!--自己的数据源-->
    </bean>
</beans>

六、GoF代理模式

6.1 理论

代理模式是GoF设计模式之一,属于结构型设计模式

作用:

  • 当一个对象需要受到保护的时候,可以考虑使用代理对象去完成某个行为
  • 需要给某个对象的功能进行功能增强的时候,可以考虑找一个代理进行增强
  • A和B无法直接交互的时候,可以考虑使用代理

角色:

  • 代理类 --> 代理主题
  • 目标类 --> 目标对象
  • 代理类和目标类的公共接口(抽象主题)

特点:

6.2 静态代理

当提出新的需求,实现新的功能时:

  • 硬编码,在每个业务接口中每个方法直接添加新功能
    • 违背了OCP原则
    • 代码没有得到复用
  • 编写业务类的子类,子类继承弗雷,对每个方法进行重写
    • 导致耦合度高
    • 代码没有得到复用
  • 代理模式
    • 创建代理对象继承接口
    • 代理类中private目标类
// 公共接口
public interface OrderService {
    // 生成订单
    void generate();
}

// 目标类
public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        System.out.println("generate");
    }
}

// 代理类
public class OrderServiceProxy implements OrderService {
    private OrderServiceImpl orderService;

    public OrderServiceProxy(OrderServiceImpl orderService) {
        //可添加增强内容
        this.orderService = orderService;
    }
}

优点:

  • 解决了OCP原则
  • 降低耦合度

缺点:

  • 类爆炸,需要创建的代理类太多,不好维护

6.3 动态代理

  • 可以解决静态代理的类爆炸问题
  • 添加了字节码生成技术,在内存中为我们动态生成一个class字节码,该字节码就是代理类
  • 可以解决代码复用问题

常见动态生成类的技术:

  • JDK动态代理技术: 只能代理接口
  • CGLIB动态代理技术: 高性能高质量的代码生成类库,通过继承方法实现(借助了字节码处理框架ASM)
  • Javassist动态代理技术
6.3.1 JDK代理
// 公共接口
public interface OrderService {
    // 生成订单
    void generate();
}

// 目标类
public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        System.out.println("generate");
    }
}

//使用代理
public class client {
    public static void main(String[] args) {
        //创建目标对象
        OrderService target = new OrderServiceImpl();
        /*
        新建代理对象,在内存中动态生成了代理类的字节码,new对象,通过字节码是实例化了对象
        newProxyInstance
        1. 类加载器ClassLoader 必须和目标类的加载器是同一个 class.getCLass().getClassLoader()
        2. 代理类实现接口Class<?>[]  代理类和目标类实现同一个接口 class.getCLass().getInterface()
        3. 调用处理器InvocationHandler 传需要增强的程序/增强代码,是个接口,就要实现类
            该实现类实现InvocationHandler接口,实现invoke方法
        向下转型
         */
        OrderService proxyObj = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new OrderInvocationHandler(target));
        //调用代理对象
        proxyObj.generate();
    }
}

//代理增强类
public class OrderInvocationHandler implements InvocationHandler {
    /*当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke方法被调用
    三个参数
    1. Object proxy 代理对象引用
    2. Method method 目标对象的目标方法
    3. Object[] args 目标方法上的实参
     */
    private Object object;

    public OrderInvocationHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("InvocationHandler");
        //目标对象,目标方法,目标参数,返回值
        method.invoke(object, args);
        return null;
    }
}

可以对newProxyInstance()方法进行proxyUtils进行封装

6.3.2 CGLIB代理

既可以代理接口,也可以代理类

需要引入依赖 --> cglib

public class client {
    public static void main(String[] args) {
//        创建字节码增强器
//        CGLIB库当中的核心对象,依靠他生成代理类
        Enhancer enhancer = new Enhancer();
        //告诉CGLIB父类是谁,告诉CGLIB目标类是谁
        enhancer.setSuperclass(Object.class);
        //设置回调(等同于JDK代理的调用处理器)
        //实现接口MethodInterceptor
//        methodProxy.invokeSuper(target,objects);
        enhancer.setCallback("???");
        //创建代理对象
        //在内存中生成UserService类的子类,代理类字节码
        //创建代理对象
        Object object = enhancer.create();
//        调用方法
        object.method();
    }
}

底层实际是生成了一个继承目标类的类

jdk高于8汇报错

  • 添加参数 -> Modify options -> Add VM
  • –add-opens java.base/java.lang=ALL-UNNAMED
  • –add-opens java.base/sun.net.util=ALL-UNNAMED

七、AOP面向切面编程

7.1 AOP介绍

  • AOP是一种编程技术

  • AOP是对OOP(面向对象)的补充延申

  • 底层通过动态代理实现

  • Spring的AOP动态代理:JDK动态代理+CGLIB动态代理

    • 代理接口,默认使用JDK
    • 代理某个类,且这个类没有实现接口,会默认使用CGLIB
  • 每个系统中都会有一些系统服务,例如日志,事务管理等,这些服务统称为交叉业务

  • 因为交叉业务是通用的,如果每个业务都掺杂交叉业务,会存在问题

    • 交叉业务代码没有得到复用,若修改则需要修改多处
    • 程序员无法专注于核心业务代码的编写,要额外处理交叉业务

AOP便解决上述问题,将与核心业务无关的代码提取出来,形成独立的组件,以横向交叉的方式应用于业务流程中的过程

优点

  1. 代码复用性强
  2. 代码易于维护
  3. 是开发者更关注业务逻辑

7.2 AOP七大术语

  1. 连接点 Join point --> 位置
    • 整个程序执行过程中,可以织入切面的位置(方法执行前后,异常抛出之后等)
  2. 切点 Pointcut --> 方法
    • 程序执行流程中,真正织入切面的方法(一个切点对应多个连接点)
  3. 通知 Advice --> 增强{具体代码)
    • 前置通知 --> 方法前
    • 后置通知 --> 方法后
    • 环绕通知 --> 前后都有
    • 异常通知 --> 在catch中
    • 最终通知 --> 在finally中
  4. 切面 Aspect
    • 切点 + 通知
  5. 织入 Weaving
    • 把通知应用到目标对象上的过程
  6. 代理对象 Proxy
    • 一个目标对象被织入通知后产生的新对象
  7. 目标对象 Target
    • 被织入的对象

7.3 切点表达式

  • 格式

execution([访问权限修饰符]返回类型[全限定类名]方法名(形参)[异常])

  • 访问控制权限修饰符
    • 可选项
    • 没写就是四个权限都包括
    • public只表示是公开
  • 返回值类型
    • 必填项
  • 全限定类名
    • 可选项
    • "…"表示当前包以及子包的所有类
    • 省略表示所有类
  • 方法名
    • 必填
    • "*"表示所有方法
    • "set*"表示所有set方法
  • 形参
  • 必填项目
    • ()表示无参
    • (*)表示只有一个参数的方法
    • (…)表示参数类型和个数随意的方法
    • (*,String)第一个参数类型随意,第二个是String类型
  • 异常
    • 可选项
    • 省略是表示任意异常类型

八、Spring的AOP

Spring对AOP的实现包括以下三种:

  • Spring框架结合AspectJ框架实现的AOP,基于注解方式
  • Spring框架结合AspectJ框架实现的AOP,基于XML方式
  • Spring框架自己实现的AOP,基于XML方式

8.1 准备工作

使用Spring+Aspect的AOP,需要引入依赖

  • spring-context
  • spring-aop
  • spring-aspect

在Spring的配置文件中添加context命名空间和aop命名空间

8.2 实现

创建目标类,纳入IoC管理


@Service("userService")
public class UserService {
    public void getName() {
        System.out.println("name");
    }
}

切面(通知+切点),纳入IoC管理


@Component("logAspect")
@Aspect //切面类需要使用该注解进行标注
public class LogAspect {
    @Before("切点表达式")
    public void addUp() {
        System.out.println("我是通知");
    }
}

Spring配置文件开启自动代理

<!--proxy-target-class="true" 强制使用CGLIB代理-->
<aop:aspectj-autoproxy proxy-target-class="false"/>

8.3 所有通知类型

  • 前置通知 --> @Before
  • 后置通知 --> @AfterReturning
  • 环绕通知 --> @Around
    class Test{
        @Around("#")
        public void aroundAdvice(ProceedingJoinPoint joinPoint){
            //前环绕
            //执行目标
            jointPoint.proceed();
            //后环绕
        }
    }
    
  • 异常通知 --> @AfterThrowing
  • 最终通知 --> @After

前环绕–>前置通知–>目标方法–>异常通知–>(后置通知)–>最终通知–>(后环绕)

8.4 切面顺序

可以通过@Order注解控制,Order数字越小优先级越高

8.5 通用切点

定义方法,添加注解Pointcut定义通用切点表达式

class Test {
    //定义通用的切点表达式
    @Pointcut("#")
    public void Name() {
    }

    @Before("Name()")
    public void get() {
    }
}

8.6 连接点

除了环绕通知,其实所有的通知都可以添加Join Point参数,可以直接使用这个连接点的方法

8.7 全注解开发

新建一个类即可,代替spring配置文件


@Configuration //代替XMl文件
@ComponentScan("#") //组件扫描
@EnableAspectJAutoProxy(proxyTargeetClass = true) //启用aspectj的自动代理
public class Test {

}

8.8 基于XML方式实现

<!--添加命名空间-->
<beans>
    <bean id="#" class="#"/>
    <bean id="#" class="#"/>
    <!--    aop配置-->
    <aop:config>
        <!--        切点表达式-->
        <aop:pointcut id="myPointcut" expression="表达式"/>
        <!--        切面:通知+切点-->
        <aop:aspect ref="#">
            <aop:around method="#" piontcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

8.9 AOP编程式事务

通过aop的环绕通知编写切面,将事务管理加入

class Test {
    @Around("#")
    public void Advice(ProceedingJoinPoint joinPoint) {
        try {
            //前环绕-->开启事务
            joinPoint.proceed();
            //后环绕-->提交事务
        } catch (Throwable e) {
            //出现异常-->回滚事务
            e.printStackTrace();
        }
    }
}

8.10 AOP安全日志

使用aop的通知实现安全日志操作(把用户的所有增删改操作进行日志记录)

九、事务

9.1 概念

  • 什么是事务
    • 多条DML(增删改)要么同时成功,要么同时失败
  • 处理过程
    • 开启事务-start transaction
    • 执行核心业务代码
    • 提交事务(核心业务处理过程中没有异常)-commit transaction
    • 回滚事务(核心业务处理过程中出现异常)-rollback transaction
  • 四个特性-ACID
    • 原子性:事务是最小单位,不可再分
    • 一致性:要么同时成功,要么同时失败,事务前后总量不变
    • 隔离性:事务和事务之间有隔离性,保证互不干扰
    • 持久性:事务结束的标志

9.2 Spring对事务的支持

Spring实现事务的两种方式:

  • 编程式事务 --> 编写代码的方式实现事务管理
  • 声明式事务 --> 基于注解 // 基于XML

Spring事务管理API

PlatformTransactionManager接口:spring事务管理器的核心接口

  • DataSourceTransactionManager:支持jdbcTemplate,MyBatis,Hibernate等事务管理
  • JtaTransactionManager:支持分布式事务管理
9.2.1 基于注解

配置事务管理器

<!--命名空间-->
<beans xmlns:tx="#">
    <!--    事务管理器-->
    <bean id="#" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--            数据源-->
        <property name="#" ref="#"/>
    </bean>
    <!--事务启动器  告诉spring框架采用注解方式管理事务-->
    <tx:annotation-driven transaction-manager="事务管理器的id"/>
</beans>

在方法或者类上添加注解->@Transactional

9.2.2 基于XML
未完待续...

9.3 事务属性

9.3.1 事务属性有哪些
9.3.2 事务传播行为
  • REQUIRED:支持当前事务,若没有事务,则新建一个
  • SUPPORTS:支持当前事务,若没有事务,则按非事务方式执行
  • MANDATORY:必须在一个事务中,没有事务则抛出异常
  • REQUIRES_NEW:开启新的事物,如果事务已经存在,则将存在的事务挂起
  • NOT_SUPPORTED:以非事务的方式运行,如果事务已经存在,如果事务已经存在
  • NEVER:如果事务已经存在,如果事务已经存在,则抛出异常
  • NESTED:如果当前有事务进行,则将该方法应当运行在一个嵌套式事务中,被嵌套的事务可以独立于外层事务进行提交回滚.若外层事务不存在,则如REQUIRED一样
9.3.3 事务隔离级别
@Transactional(isolation = Isolation)

防止多事务并发

数据库读取数据三大问题:

  1. 脏数据:读到但是没有提交 --> 读缓存
  2. 不可重复读:同一个事务中,两次读取的数据不一样 --> 数据不一样
  3. 幻读:读到的数据是假的 --> 数据不存在

隔离的四个级别:

  • 读未提交:READ_UNCOMMITTED
    • 存在脏读问题,能够读取到其他事务未提交的数据
  • 读提交:READ_COMMITTED
    • 解决脏读问题,其他事务提交后才能读取到,但存在不可重复读的问题
  • 可重复读:REPEATABLE_READ
    • 解决了不可重复读,只要当前事务不结束,读到的数据一直都是一样的,但存在幻读
  • 序列化:SERIALIZABLE
    • 解决了幻读问题,事务排队执行,不支持并发
9.3.4 事务超时
@Transactional(timeout = 10)

超过设定时间,如果该事务中所有的DML语句还没有执⾏完毕的话,最终结果会选择回滚

默认值-1,表示没有时间限制。

在当前事务当中,最后⼀条DML语句执⾏之前的时间。如果最后⼀条DML语句后⾯很有很多业务逻辑,这些业务代码执⾏的时间不被计⼊超时时间。

9.3.5 只读事务
@Transactional(readOnly = true)

当前事务设为只读,在该事务中只允许select语句执行,DIU均不可执行

该特性启动spring的优化策略,提高select语句执行效率

9.3.6 设置异常回滚
@Transactional(rollbackFor=NumberFormatException.class)

只有发生特定异常的时候,才执行回滚

9.3.7 设置异常不回滚
@Transactional(noRollbackFor=NumberFormatException.class)

只有不发生特定异常的时候,才执行回滚

9.3.8 全注解开发

编写一个类来代替XML配置文件

import javax.management.MXBean;

@Configration //代替xml文件
@ComponentScan("#") //组件扫描
@EnableTransactionManagement  //开启事务注解
public class TransactionManageConfig {
    @Bean(name = "id") //德鲁伊数据池
    public DataSource getDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    @Bean(name = "jdbcTemplate") //jdbc
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean("txManage") //事务管理器
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值