Spring6基础学习

Spring启示录

OCP

软件七大开发原则中最基本, 最核心的原则, 开闭原则:  对扩展开放, 对修改关闭.

条件: 在扩展系统功能的时候, 没有修改以前写好的代码, 就符合OCP原则. 反之不符合.

依赖倒置原则(DIP)

凡是上依赖下的, 都违背了依赖倒置原则.

条件: 上 不再依赖 下, 符合依赖倒置原则.

核心: 倡导面向接口编程, 面向抽象编程, 不要面向具体编程.

目的: 降低程序的耦合度, 提高扩展力.

控制反转 IoC

反转的是两件事: 1.不在程序中采用硬编码来new对象了.

                           2.不在程序中采用硬编码的方式来维护对象的关系了.

控制反转是一种编程思想(新型的设计模式).

spring框架

功能: 实现了IoC思想, 可以自动new对象和维护对象之间的关系, 是实现了IoC的容器.

依赖注入

含义: 控制反转的实现方式有多种, 其中比较重要的叫做: 依赖注入(Dependency Injection, DI).

方式: 1.set注入(执行set方法给属性赋值).  2.构造方式注入(执行构造方法给属性赋值).

依赖: 对象之间的关系.  注入: 让对象之间产生关系的手段.

spring的jar包

注: 如果你只是想⽤Spring的IoC功能,仅需要引⼊:spring-context即可。将这个jar包添加到classpath当中。如果采⽤maven只需要引⼊context的依赖即可。

<!--Spring6的正式版发布之前,这个仓库地址是需要的-->
<repositories>
    <repository>
        <id>repository.spring.milestone</id>
        <name>Spring Milestone Repository</name>
        <url>https://repo.spring.io/milestone</url>
    </repository>
</repositories>
<dependencies>
    <!--spring context依赖:使⽤的是6.0.0-M2⾥程碑版-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.0-M2</version>
    </dependency>
</dependencies>

Spring入门程序

依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.10</version>
</dependency>
spring配置文件(名字不限, 放在类路径中, 即resources目录下)

模板

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

bean标签属性: id: bean的唯一标识, 不能重复.   class:类的全限定类名.

<bean id="userBean" class="com.powernode.spring6.bean.User"/>
获取bean实例化对象
ApplicationContext: 接口, 有很多实现类.
ClassPathXmlApplicationContext: 专门从类路径中加载spring配置文件的spring上下文对象.
原理: 默认spring会通过反射机制, 通过类的无参数构造方法来实例化对象.

反射代码

Class clazz = Class.forName("com.powernode.spring6.bean.User");
Object user = clazz.newInstance();

 spring实例化bean

//1.获取spring容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//2.根据bean的id从spring容器中获取这个对象
Object userBean = applicationContext.getBean("userBean");

容器可加载多个spring配置文件

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring6.xml", "beans.xml", "xml/beans.xml");

可配置JDK的类

<bean id="nowTime" class="java.util.Date"/>

指定bean类型

Date nowTime = applicationContext.getBean("nowTime", Date.class);

绝对路径加载配置文件

ApplicationContext applicationContext = new FileSystemXmlApplicationContext("F:/AA-studyAgain/A-Spring6/spring6.xml");
注: ApplicationContext的顶级父接口是BeanFactory, BeanFactory也是IoC容器的顶级接口, Spring的IoC用的是工厂模式.       
Spring的IoC的实现方式: XML解析 + 工厂模式 + 反射机制

注: spring不是在调用getBean时才创建bean, 执行以下代码的时候, 就会实例化对象.
BeanFactory applicationContext = new ClassPathXmlApplicationContext("spring6.xml");
spring6启用Log4j2日志框架
依赖
<!--log4j2的依赖-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.19.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j2-impl</artifactId>
    <version>2.19.0</version>
</dependency>
配置文件

日志级别: ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <loggers>
        <!--
        level指定⽇志级别,从低到⾼的优先级:
        ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
        -->
        <root level="DEBUG">
            <appender-ref ref="spring6log"/>
        </root>
    </loggers>
    <appenders>
        <!--输出⽇志信息到控制台-->
        <console name="spring6log" target="SYSTEM_OUT">
            <!--控制⽇志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
        </console>
    </appenders>
</configuration>
使用
Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
logger.info("111");
logger.debug("debug11");
logger.error("error22");

Spring对IoC的实现: 依赖注入

set注入

ref: 要注入的bean的id.     name: setXxx方法名去掉set切首字母小写的字符串.

<bean id="userDaoBean" class="com.powernode.spring6.dao.UserDao"/>
<bean id="userServiceBean" class="com.powernode.spring6.service.UserService">
    <property name="mySQLUserDao" ref="userDaoBean"/>
</bean>
构造注入

index: 构造方法第i个参数(从0开始).   ref: 要注入的bean的id.

<bean id="csBean" class="com.powernode.spring6.service.CustomerService">
    <constructor-arg index="0" ref="userDaoBean"/>
    <constructor-arg index="1" ref="vipDaoBean"/>
</bean>

name: 构造方法参数名.

<bean id="csBean2" class="com.powernode.spring6.service.CustomerService">
    <constructor-arg name="userDao" ref="userDaoBean"/>
    <constructor-arg name="vipDao" ref="vipDaoBean"/>
</bean>

spring自动根据类型注入

<bean id="csBean3" class="com.powernode.spring6.service.CustomerService">
    <constructor-arg ref="vipDaoBean"/>
    <constructor-arg ref="userDaoBean"/>
</bean>

set注入专题

注入外部bean
<bean id="orderDaoBean" class="com.powernode.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService">
    <property name="orderDao" ref="orderDaoBean"/>
</bean>
注入内部bean
<bean id="orderServiceBean2" class="com.powernode.spring6.service.OrderService">
    <property name="orderDao">
        <bean class="com.powernode.spring6.dao.OrderDao"/>
    </property>
</bean>
注入简单类型
private int age;
private Integer age2;
private boolean flag;
private Boolean flag2;
private char c;
private Character c2;
private Season season;
private String username;
private Class clazz;
<bean id="svt" class="com.powernode.spring6.bean.SimpleValueType">
    <property name="age" value="1"/>
    <property name="age2" value="1"/>
    <property name="username" value="zhansgan"/>
    <property name="season" value="SPRING"/>
    <property name="flag" value="false"/>
    <property name="flag2" value="true"/>
    <property name="c2" value="男"/>
    <property name="c" value="男"/>
    <property name="clazz" value="java.lang.String"/>
</bean>
Date类型注入

虽然Date是简单类型, 但在实际开发中, 一般不会把它当简单类型. 一般会采用ref给Date类型赋值.

<property name="birth" value="Wed Aug 30 15:29:08 CST 2023"/>
级联属性赋值

赋值属性需提供get方法.

注: 赋值顺序不能颠倒.

public Clazz getClazz() {
    return clazz;
}
<bean id="studentBean" class="com.powernode.spring6.bean.Student">
    <property name="name" value="张三"/>
    <property name="clazz" ref="clazzBean"/>
    <property name="clazz.name" value="高三二班"/>
</bean>
<bean id="clazzBean" class="com.powernode.spring6.bean.Clazz"/>
注入数组
<bean id="yuQian" class="com.powernode.spring6.bean.QianDaYe">
    <property name="aiHaos">
        <array>
            <value>抽烟</value>
            <value>喝酒</value>
            <value>烫头</value>
        </array>
    </property>
    <property name="women">
        <array>
            <ref bean="w1"/>
            <ref bean="w2"/>
            <ref bean="w3"/>
        </array>
    </property>
</bean>
List和Set集合注入
<bean id="personBean" class="com.powernode.spring6.bean.Person">
    <property name="names">
        <list>
            <value>张三</value>
            <value>李四</value>
            <value>张三</value>
        </list>
    </property>
    <property name="addrs">
        <set>
            <value>北京大兴区</value>
            <value>北京海鼎区</value>
            <value>北京大兴区</value>
        </set>
    </property>
</bean>
Map和Properties注入

如果key和value不是简单类型: <entry key-ref="" value-ref=""/>

<property name="phones">
    <map>
        <entry key="1" value="112"/>
        <entry key="2" value="112"/>
        <entry key="3" value="111"/>
        <entry key="4" value="123"/>
    </map>
</property>

Properties注入(key和value只能是String)

<property name="properties">
    <props>
        <prop key="driber">com.mysql.cj.jdbc.Driver</prop>
        <prop key="url">3306</prop>
        <prop key="username">111</prop>
    </props>
</property>
注入null和空字符串

null

不给属性注入, 属性的默认值就是null.

手动注入null.

<property name="name">
    <null/>
</property>

空字符串

<property name="name" value=""/>
<property name="name">
    <value/>
</property>
注入的值含有特殊符号

1.使用实体符号代替特殊符号

<property name="result" value="2 &lt; 3"/>

2.使用<![CDATA[]]>

<value><![CDATA[2 < 3]]></value>
p命名空间注入

目的: 简化配置.     底层: set注入. 

使用条件: 1. 在XML头部信息中添加p命名空间的配置信息:
xmlns:p="http://www.springframework.org/schema/p"

xmlns:p="http://www.springframework.org/schema/p"


2. p命名空间注⼊是基于setter⽅法的,所以需要对应的属性提供setter⽅法.

<bean id="birthBean" class="java.util.Date"/>
<bean id="dogBean" class="com.powernode.spring6.bean.Dog" p:name="小花" p:age="3" p:birth-ref="birthBean"/>
c命名空间注入

目的: 简化构造方法注入.

使用条件: 1.需要在xml配置⽂件头部添加信息:

xmlns:c="http://www.springframework.org/schema/c"

xmlns:c="http://www.springframework.org/schema/c"

2.需要提供构造⽅法。

使用:

c:_0 : 0是第0个参数

<bean id="peopleBean" class="com.powernode.spring6.bean.People" c:_0="zhangsan" c:_1="30" c:_2="true"/>
util命名空间

作用: 配置复用.

1.引入util命名空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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/util/spring-util.xsd">

2.使用

<util:properties id="prop">
    <prop key="driver">com.mysql.cj.jdbc.Driver</prop>
    <prop key="username">root</prop>
    <prop key="password">1234</prop>
    <prop key="url">jdbc:mysql://localhost:3306/spring6</prop>
</util:properties>

<bean id="ds1" class="com.powernode.spring6.jdbc.MyDataSource1">
    <property name="properties" ref="prop"/>
</bean>

<bean id="ds2" class="com.powernode.spring6.jdbc.MyDataSource1">
    <property name="properties" ref="prop"/>
</bean>
基于XML的自动装配
按名称自动装配(基于set注入)
<bean id="orderService" class="com.powernode.spring6.service.OrderService" autowire="byName"/>
<bean id="orderDao" class="com.powernode.spring6.dao.OrderDao"/>
按类型自动装配(基于set方法)

注: 一种类型只能有一个bean实例

<bean class="com.powernode.spring6.dao.VipDao"/>
<bean class="com.powernode.spring6.dao.UserDao"/>
<bean id="cs" class="com.powernode.spring6.service.CustomerService" autowire="byType"/>
spring引入外部属性配置文件

1.引入context命名空间

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

2.使用

<context:property-placeholder location="jdbc.properties"/>
<bean id="ds" class="com.powernode.spring6.jdbc.MyDataSource">
    <property name="driver" value="${driverClass}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</bean>

注: ${key}优先取系统变量

Bean的作用域

spring默认单例(启动容器就创建单例对象)

设置为多例模式(每调用一次getBean创建一次对象)   scope="prototype"

<bean id="sb" class="com.powernode.spring6.bean.SpringBean" scope="prototype"/>

注: scope属性其他值: request: 一个请求一个对象.      session: 一个会话一个对象.

自定义scope

一个线程一个bean

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="threadScope">
                <!--spring内置的, 也可自定义-->
                <bean class="org.springframework.context.support.SimpleThreadScope"/>
            </entry>
        </map>
    </property>
</bean>
<bean id="sb" class="com.powernode.spring6.bean.SpringBean" scope="threadScope"/>

GoF之工厂模式

工厂模式三种形态:

1.简单工厂模式(不属于23种模式, 又称静态方法工厂模式, 是工厂方法模式的一种特殊实现)

2.工厂方法模式(属于23种模式).       3.抽象工厂模式(属于23种模式).

简单工厂模式

抽象产品角色

public abstract class Weapon {
    public abstract void attack();
}

具体产品角色

public class Tank extends Weapon{
    @Override
    public void attack() {
        System.out.println("坦克开炮!!!");
    }
}

工厂类角色

public class WeaponFactory {
    public static Weapon get(String weaponType){
        return switch (weaponType) {
            case "TANK" -> new Tank();
            case "DAGGER" -> new Dagger();
            case "FIGHTER" -> new Fighter();
            default -> throw new RuntimeException("不支持该武器的生产");
        };
    }
}

优点: 生产者和消费者分离.

缺点: 1.需要扩展产品需要改写工厂类代码, 违背OCP(开闭原则).

         2.工厂类负责所有产品的生产, 不能出现任何问题,一旦出问题, 整个系统全部瘫痪.

工厂方法模式

解决简单工厂模式的违背OCP问题: 一个工厂只生产一种产品.

抽象产品角色

public abstract class Weapon {
    public abstract void attack();
}

具体产品角色

public class Tank extends Weapon{
    @Override
    public void attack() {
        System.out.println("坦克开炮!!!");
    }
}

抽象工厂角色

public abstract class WeaponFactory {
    public abstract Weapon get();
}

具体工厂角色

public class TankFactory extends WeaponFactory{
    @Override
    public Weapon get() {
        return new Tank();
    }
}

优点: 符合OCP.

缺点:增加系统复杂性.

Bean的实例化方式

1.构造方法实例化
<bean id="sb" class="com.powernode.spring6.bean.SpringBean"/>
2.简单工厂模式实例化

自定义工厂类

public class StarFactory {
    public static Star get(){
        return new Star();
    }
}

 配置

<bean id="starBean" class="com.powernode.spring6.bean.StarFactory" factory-method="get"/>
3.factory-bean实例化

具体工厂

public class GunFactory {
    public Gun get(){
        return new Gun();
    }
}

配置

<bean id="gunFactory" class="com.powernode.spring6.bean.GunFactory"/>
<bean id="gun" factory-bean="gunFactory" factory-method="get"/>
4.FactoryBean接口实例化

工厂bean实现FactoryBean<T>接口

public class PersonFactoryBean implements FactoryBean<Person> {
    @Override
    public Person getObject() throws Exception {
        return new Person();
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }
}

配置

<bean id="person" class="com.powernode.spring6.bean.PersonFactoryBean"/>
5.BeanFactory和FactoryBean的区别

BeanFactory
Spring IoC容器的顶级对象,BeanFactory被翻译为“Bean⼯⼚”,在Spring的IoC容器中,“Bean⼯
⼚”负责创建Bean对象。
BeanFactory是⼯⼚。

FactoryBean

FactoryBean:它是⼀个Bean,是⼀个能够辅助Spring实例化其它Bean对象的⼀个Bean。
在Spring中,Bean可以分为两类:

第⼀类:普通Bean.
第⼆类:⼯⼚Bean(记住:⼯⼚Bean也是⼀种Bean,只不过这种Bean⽐较特殊,它可以辅助
Spring实例化其它Bean对象。

6.注入自定义Date
public class DateFactoryBean implements FactoryBean<Date> {
    private String strDate;

    public DateFactoryBean(String strDate) {
        this.strDate = strDate;
    }

    @Override
    public Date getObject() throws Exception {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return simpleDateFormat.parse(strDate);
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}
<bean id="date" class="com.powernode.spring6.bean.DateFactoryBean">
    <constructor-arg index="0" value="2008-10-11"/>
</bean>
<bean id="stu" class="com.powernode.spring6.bean.Student">
    <property name="birth" ref="date"/>
</bean>

Bean的生命周期

五步

1.实例化Bean

public User() {
    System.out.println("1.无参构造方法执行");
}

2.Bean属性赋值

public void setName(String name) {
    System.out.println("2.属性赋值");
    this.name = name;
}

3.初始化Bean

public void initBean(){
    System.out.println("3.初始化bean");
}

4.使用Bean

5.销毁Bean (必须手动关闭spring容器)

public void destroyBean(){
    System.out.println("5.销毁bean");
}
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();

注: 3和5的方法需要配置

<bean id="user" class="com.powernode.spring6.bean.User" init-method="initBean" destroy-method="destroyBean">
    <property name="name" value="zhangsan"/>
</bean>
七步

基于五步, 在 第三步 之前执行"Bean后处理器的before方法", 之后执行"Bean后处理器的after方法".

 bean后处理器类

public class LogBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行before");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("执行after");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

配置 (作用于当前配置文件中的所有bean)

<bean class="com.powernode.spring6.bean.LogBeanPostProcessor"/>
十步

基于七步, 在 bean后处理器的before前和后 分别加一步, 和在 使用bean和销毁bean之间 加一步.

before之前: 检查Bean是否实现了Aware相关的接口, 如果实现了接口, 调用接口中方法,

                   目的: 传递数据, 方便实用.

before之后: 检查Bean是否实现了InitializingBean接口, 如果实现了则调用接口方法.

使用和销毁bean之间: 检查Bean是否实现了DisposableBean接口, 如果实现了则调用接口方法.

作用域不同, 生命周期不同

spring只对singleton的Bean进行完整的生命周期管理.

如果是prototype作用域的Bean, spring容器只负责将该bean初始化完毕, 一旦客户端获取到bean, spring容器将不在管理该对象的生命周期.

将自己new的对象纳入spring容器
Student student = new Student();
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerSingleton("studentBean", student);
Student studentBean = factory.getBean("studentBean", Student.class);

Bean的循环依赖问题

singleton+set注入模式 没问题

原因: 在这种模式下, spring对bean的管理主要分为两个清晰的阶段:

        1.spring容器加载, 实例化bean并立即进行曝光.

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

<bean id="husbandBean" class="com.powernode.spring6.bean.Husband">
    <property name="name" value="张三"/>
    <property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife">
    <property name="name" value="小花"/>
    <property name="husband" ref="husbandBean"/>
</bean>
prototype + set注入模式 会出异常

BeanCurrentlyInCreationException

singleton + 构造注入 会出异常

BeanCurrentlyInCreationException

Spring源码分析

DefaultSingletonBeanRegistry类下属性:

key: bean的name

一级缓存: private final Map<String, Object> singletonObjects

                 存储完整的单例bean对象, 属性都已赋值.

二级缓存: private final Map<String, ObjectFactory<?>> earlySingletonObjects   

                存储早期单例bean对象, 属性没有赋值.

三级缓存: private final Map<String, Object> earlySingletonObjects

                存储单例工厂对象, 创建单例bean的单例工厂对象.

回顾反射机制

//获取类
Class<?> clazz = Class.forName("com.powernode.reflect.SomeService");
//获取方法
Method doSomeMethod = clazz.getDeclaredMethod("doSome", String.class, int.class);
//调用方法
Object obj = clazz.newInstance();
Object res = doSomeMethod.invoke(obj, "李四", 250);
System.out.println(res);
//已知
String cn = "com.powernode.reflect.User", pn= "age";
//设置属性
Class<?> clazz = Class.forName(cn);
Field field = clazz.getDeclaredField(pn);
Class<?> type = field.getType();
Method setAge = clazz.getDeclaredMethod("set" + pn.toUpperCase().charAt(0) + pn.substring(1), type);
Object obj = clazz.newInstance();
setAge.invoke(obj, 15);

手写Spring框架

依赖
<dependencies>
    <dependency>
        <groupId>org.dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>2.1.3</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.2.0</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>
public class ClassPathXmlApplicationContext implements ApplicationContext{

    private static final Logger logger = LoggerFactory.getLogger(ClassPathXmlApplicationContext.class);

    private Map<String, Object> singletonObjects = new HashMap<>();

    public ClassPathXmlApplicationContext(String configLocation) {
        try {
            SAXReader reader = new SAXReader();
            InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);
            Document document = reader.read(in);
            //获取所有bean标签
            List<Node> nodes = document.selectNodes("//bean");
            nodes.forEach(node -> {
                try {
                    Element beanElt = (Element) node;
                    String id = beanElt.attributeValue("id");
                    String classname = beanElt.attributeValue("class");
                    logger.info("beanName=" + id);
                    logger.info("beanClassName=" + classname);
                    Class<?> aClass = Class.forName(classname);
                    Constructor<?> defaultCon = aClass.getDeclaredConstructor();
                    Object bean = defaultCon.newInstance();
                    //将bean曝光
                    singletonObjects.put(id, bean);
                    logger.info(singletonObjects.toString());
                }catch (Exception e){
                    e.printStackTrace();
                }
            });
            nodes.forEach(node -> {
                try {
                    Element beanElt = (Element) node;
                    String id = beanElt.attributeValue("id");
                    String className = beanElt.attributeValue("class");
                    Class<?> aClass = Class.forName(className);
                    List<Element> properties = beanElt.elements("property");
                    properties.forEach(property -> {
                        try {
                            String propertyName = property.attributeValue("name");
                            Field field = aClass.getDeclaredField(propertyName);
                            logger.info("属性名: " + propertyName);
                            String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
                            Method setMethod = aClass.getDeclaredMethod(setMethodName, field.getType());
                            String value = property.attributeValue("value");
                            Object actualValue = null;
                            String ref = property.attributeValue("ref");
                            if (value != null){
                                String propertySimpleType = field.getType().getSimpleName();
                                switch (propertySimpleType){
                                    case "byte":
                                        actualValue = Byte.parseByte(value);
                                        break;
                                    case "short":
                                        actualValue = Short.parseShort(value);
                                        break;
                                    case "int":
                                        actualValue = Integer.parseInt(value);
                                        break;
                                    case "long":
                                        actualValue = Long.parseLong(value);
                                        break;
                                    case "float":
                                        actualValue = Float.parseFloat(value);
                                        break;
                                    case "double":
                                        actualValue = Double.parseDouble(value);
                                        break;
                                    case "boolean":
                                        actualValue = Boolean.parseBoolean(value);
                                        break;
                                    case "char":
                                        actualValue = value.charAt(0);
                                        break;
                                    case "Byte":
                                        actualValue = Byte.valueOf(value);
                                        break;
                                    case "Short":
                                        actualValue = Short.parseShort(value);
                                        break;
                                    case "Integer":
                                        actualValue = Integer.valueOf(value);
                                        break;
                                    case "Long":
                                        actualValue = Long.valueOf(value);
                                        break;
                                    case "Float":
                                        actualValue = Float.valueOf(value);
                                        break;
                                    case "Double":
                                        actualValue = Double.valueOf(value);
                                        break;
                                    case "Boolean":
                                        actualValue = Boolean.valueOf(value);
                                        break;
                                    case "Character":
                                        actualValue = value.charAt(0);
                                        break;
                                    case "String":
                                        actualValue = value;
                                        break;
                                }
                                setMethod.invoke(singletonObjects.get(id), actualValue);
                            }
                            if (ref != null){
                                setMethod.invoke(singletonObjects.get(id), singletonObjects.get(ref));
                            }
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    });
                }catch (Exception e){
                    e.printStackTrace();
                }
            });
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public Object getBean(String beanName) {
        return singletonObjects.get(beanName);
    }
}

Spring IoC注解式开发

回顾注解

目的:简化配置, spring6倡导全注解开发.

定义 (属性名为value可省略, 数组元素长度为一大括号可省略)

@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value();
    String name();
    String[] names();
    int[] ages();
    int age();
}
使用

Controller, Service, Repository都是Component的别名, 为了增强可读性

(mvc: 表示层, 业务层, 持久层).

1.加入aop的依赖 (context依赖自动关联aop)

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.10</version>
</dependency>

2.配置文件中添加context命名空间

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

3.指定包扫描

<context:component-scan base-package="com.powernode.spring6.bean"/>

4.在bean上使用注解

@Component(value = "userBean")
public class User {
}

注: Component注解如果value没赋值, 默认为类名首字母变小写.

多个包扫描
<context:component-scan base-package="com.powernode.spring6.bean, com.powernode.spring6.dao"/>
选择性实例化bean

方案1

use-default-filters="false" : 让包下所有带有声明bean的注解全部失效.

<context:component-scan base-package="com.powernode.spring6.bean2" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

方案2

use-default-filters="true" : 让包下所有带有声明bean的注解全部生效. (默认值为true).

<context:component-scan base-package="com.powernode.spring6.bean2" use-default-filters="true">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
负责注入的注解
@Value只能注入简单类型

不需要写set方法

@Value("com.mysql.cj.jdbc.Driver")
private String driver;
@Value("jdbc:mysql//localhost:3306/spring6")
private String url;
@Value("root")
private String username;
@Value("1234")
private String password;

@Value用在set方法上

private String name;
private int age;
@Value("隔壁老王")
public void setName(String name) {
    this.name = name;
}
@Value("30")
public void setAge(int age) {
    this.age = age;
}

@Value用在构造方法上

private String name;
private int age;
public Product(@Value("隔壁老王") String name,@Value("40") int age) {
    this.name = name;
    this.age = age;
}
注入非简单类型

根据类型装配: @Autowired(默认根据类型装配)

参数上

@Autowired
private OrderDao orderDao;

set方法上

@Autowired
public void setOrderDao(OrderDao orderDao) {
    this.orderDao = orderDao;
}

构造方法上

@Autowired
public OrderService(OrderDao orderDao) {
    this.orderDao = orderDao;
}

构造方法参数上

public OrderService(@Autowired OrderDao orderDao) {
    this.orderDao = orderDao;
}

根据名字装配: @Autowired与@Qualifier一起用

@Autowired
@Qualifier("orderDaoImplForOracle")
private OrderDao orderDao;
public void generate(){
    orderDao.insert();
}
@Resource注解
与@Autowired区别

1.@Resource注解是JDK扩展包中的,也就是说属于JDK的⼀部分。所以该注解是标准注解,更加具有通⽤性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。)
2.@Autowired注解是Spring框架⾃⼰的。
3.@Resource注解默认根据名称装配byName,未指定name时,使⽤属性名作为name。通过name找不到的话会⾃动启动通过类型byType装配。
4.@Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解⼀起⽤。

5.@Resource注解⽤在属性上、setter⽅法上。
6.@Autowired注解⽤在属性上、setter⽅法上、构造⽅法上、构造⽅法参数上。

依赖:

spring6

<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
</dependency>

spring5

<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

使用

@Resource(name = "studentDaoImplForMySQL")
private StudentDao studentDao;
public void deleteStudent(){
    studentDao.deleteById();
}
全注解开发

配置类

@Configuration
@ComponentScan({"cn.powernode.dao", "cn.powernode.service"})
public class Spring6Config {
}

使用

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class);

JdbcTemplate

依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.10</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.10</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>
配置
<bean id="ds" class="com.powernode.spring6.bean.MyDataSource">
    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
    <property name="username" value="root"/>
    <property name="password" value="1234"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="ds"/>
</bean>
使用

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into t_user(real_name, age) values(?,?)";
int count = jdbcTemplate.update(sql, "王五", 20);

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "update t_user set real_name = ?, age = ? where id = ?";
int count = jdbcTemplate.update(sql, "张三丰", 55, 1);

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "delete from t_user where id = ?";
int count = jdbcTemplate.update(sql, 1);

查一个

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select id, real_name, age from t_user where id = ?";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 2);

查所有

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select id, real_name, age from t_user";
List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));

查一个值

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select count(1) from t_user";
Integer total = jdbcTemplate.queryForObject(sql, int.class);

批量添加

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into t_user(real_name, age) values(?, ?)";
Object[] objs1 = {"小花1", 30};
Object[] objs2 = {"小花2", 31};
Object[] objs3 = {"小花3", 32};
Object[] objs4 = {"小花4", 33};
List<Object[]> list = new ArrayList<>();
list.add(objs1);
list.add(objs2);
list.add(objs3);
list.add(objs4);
int[] count = jdbcTemplate.batchUpdate(sql, list);

批量修改

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "update t_user set real_name = ?, age = ? where id = ?";
Object[] objs1 = {"小明1", 30, 8};
Object[] objs2 = {"小明2", 31, 9};
Object[] objs3 = {"小明3", 32, 10};
Object[] objs4 = {"小明4", 33, 11};
List<Object[]> list = new ArrayList<>();
list.add(objs1);
list.add(objs2);
list.add(objs3);
list.add(objs4);
int[] count = jdbcTemplate.batchUpdate(sql, list);

批量删除

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "delete from t_user where id = ?";
Object[] objs1 = {8};
Object[] objs2 = {9};
Object[] objs3 = {10};
Object[] objs4 = {11};
List<Object[]> list = new ArrayList<>();
list.add(objs1);
list.add(objs2);
list.add(objs3);
list.add(objs4);
int[] count = jdbcTemplate.batchUpdate(sql, list);

回调函数

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "select id, real_name,age from t_user where id = ?";
User user = jdbcTemplate.execute(sql, new PreparedStatementCallback<User>() {
    @Override
    public User doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
        User user = null;
        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;
    }
});
使用德鲁伊连接池

依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.23</version>
</dependency>

配置

<bean id="ds" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
    <property name="username" value="root"/>
    <property name="password" value="1234"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="ds"/>
</bean>

GoF之代理模式

理解

代理模式中有⼀个⾮常重要的特点:对于客户端程序来说,使⽤代理对象时就像在使⽤⽬标对象⼀
样。

角色:
1.代理类(代理主题)
2.⽬标类(真实主题)
3.代理类和⽬标类的公共接⼝(抽象主题):客户端在使⽤代理类时就像在使⽤⽬标类,不被客户端所察觉,所以代理类和⽬标类要有共同的⾏为,也就是实现共同的接⼝。

分类: 静态代理和动态代理.

静态代理

缺点: 类爆炸(太多, 每个接口都需要一个代理类).

public class OrderServiceProxy implements OrderService{
    private OrderService target;

    public OrderServiceProxy(OrderService target) {
        this.target = target;
    }

    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        target.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时: " + (end - begin) + "ms");
    }

    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        target.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗时: " + (end - begin) + "ms");
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        target.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时: " + (end - begin) + "ms");
    }
}
JDK动态代理

在内存当中动态⽣成类的技术常⻅的包括:
1.JDK动态代理技术:只能代理接⼝。
2.CGLIB动态代理技术:CGLIB(Code Generation Library)是⼀个开源项⽬。是⼀个强⼤的,⾼性
能,⾼质量的Code⽣成类库,它可以在运⾏期扩展Java类与实现Java接⼝。它既可以代理接⼝,⼜可以代理类,底层是通过继承的⽅式实现的。性能⽐JDK动态代理要好。(底层有⼀个⼩⽽快的字节码处理框架ASM。)
3.Javassist动态代理技术:Javassist是⼀个开源的分析、编辑和创建Java字节码的类库。是由东京⼯业⼤学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加⼊了开放源代码JBoss 应⽤服务器项⽬,通过使⽤Javassist对字节码操作为JBoss实现动态"AOP"框架。

使用

定义处理器

public class TimerInvocationHandler implements InvocationHandler {
    private Object target;
    public TimerInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long begin = System.currentTimeMillis();
        Object retValue = method.invoke(target, args);
        long end = System.currentTimeMillis();
        System.out.println("耗时: " + (end - begin) + "ms");
        return retValue;
    }
}

使用

OrderService target = new OrderServiceImpl();
OrderService proxyObj = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), 
        target.getClass().getInterfaces(), new TimerInvocationHandler(target));
proxyObj.generate();
proxyObj.detail();
proxyObj.modify();
String name = proxyObj.getName();
CGLIB动态代理

定义回调函数

public class TimerMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long begin = System.currentTimeMillis();
        Object res = methodProxy.invokeSuper(target, objects);
        long end = System.currentTimeMillis();
        System.out.println("耗时: " + (end - begin) + "ms");
        return res;
    }
}

使用

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new TimerMethodInterceptor());
UserService userServiceProxy = (UserService) enhancer.create();
boolean res = userServiceProxy.login("admin", "123");
System.out.println(res);
userServiceProxy.logout();

注:对于⾼版本的JDK,如果使⽤CGLIB,需要在启动项中添加两个启动参数:

--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED

面向切面编程AOP

理解

⼀般⼀个系统当中都会有⼀些系统服务,例如:⽇志、事务管理、安全等。这些系统服务被称为:交叉业务

⽤⼀句话总结AOP:将与核⼼业务⽆关的代码独⽴的抽取出来,形成⼀个独⽴的组件,然后以横向交叉的⽅式应⽤到业务流程当中的过程被称为AOP。
AOP的优点:
1.代码复⽤性增强。
2.代码易维护。
3.使开发者更关注业务逻辑。

Spring的AOP使⽤的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接⼝,会默认使⽤JDK动态代理,如果要代理某个类,这个类没有实现接⼝,就会切换使⽤CGLIB。当然,你也可以强制通过⼀些配置让Spring只使⽤CGLIB。

七大术语

连接点 Joinpoint
在程序的整个执⾏流程中,可以织⼊切⾯的位置。⽅法的执⾏前后,异常抛出之后等位置。
切点 Pointcut
在程序执⾏流程中,真正织⼊切⾯的⽅法。(⼀个切点对应多个连接点)
通知 Advice
通知⼜叫增强,就是具体你要织⼊的代码。
通知包括:前置通知, 后置通知, 环绕通知, 异常通知, 最终通知.
切⾯ Aspect
切点 + 通知就是切⾯。
织⼊ Weaving
把通知应⽤到⽬标对象上的过程。
代理对象 Proxy
⼀个⽬标对象被织⼊通知后产⽣的新对象。
⽬标对象 Target
被织⼊通知的对象。

切点表达式

切点表达式⽤来定义通知(Advice)往哪些⽅法上切⼊。
切⼊点表达式语法格式:

execution([访问控制权限修饰符] 返回值类型 [全限定类名]⽅法名(形式参数列表) [异常])

访问控制权限修饰符:
可选项。
没写,就是4个权限都包括。
写public就表示只包括公开的⽅法。
返回值类型:
必填项。
* 表示返回值类型任意。
全限定类名:
可选项。
两个点“..”代表当前包以及⼦包下的所有类。
省略时表示所有的类。
⽅法名:
必填项。
*表示所有⽅法。
set*表示所有的set⽅法。
形式参数列表:
必填项
() 表示没有参数的⽅法
(..) 参数类型和个数随意的⽅法
(*) 只有⼀个参数的⽅法
(*, String) 第⼀个参数类型随意,第⼆个参数是String的。
异常:
可选项。
省略时表示任意异常类型。

使用Spring的AOP

Spring对AOP的实现包括以下3种⽅式:
第⼀种⽅式:Spring框架结合AspectJ框架实现的AOP,基于注解⽅式。
第⼆种⽅式:Spring框架结合AspectJ框架实现的AOP,基于XML⽅式。

第三种⽅式:Spring框架⾃⼰实现的AOP,基于XML配置⽅式。

依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.10</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.10</version>
</dependency>

 命名空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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 http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

配置

proxy-target-class属性:

true:强制使用CGLIB动态代理.

false:默认值, 接口使用JDK动态代理, 类使用CGLIB动态代理.

<!--开启aspectj的自动代理-->
<aop:aspectj-autoproxy proxy-target-class="true"/>

切面类

切面类必须加@Aspect注解

@Component
@Aspect
public class LogAspect {
    @Before("execution(* com.powernode.spring6.service.UserService.*(..))")
    public void 增强(){
        System.out.println("我是增强代码...");
    }
}
通知类型

前置通知

@Before("execution(* com.powernode.spring6.service..*(..))")
public void beforeAdvice(){
    System.out.println("前置通知...");
}

后置通知

@AfterReturning("execution(* com.powernode.spring6.service..*(..))")
public void afterReturningAdvice(){
    System.out.println("后置通知...");
}

环绕通知(前置之前, 后置之后)

@Around("execution(* com.powernode.spring6.service..*(..))")
public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("前环绕...");
    joinPoint.proceed();
    System.out.println("后环绕...");
}

最终通知

@After("execution(* com.powernode.spring6.service..*(..))")
public void afterAdvice(){
    System.out.println("最终通知...");
}

异常通知(发生异常才会通知)

@AfterThrowing("execution(* com.powernode.spring6.service..*(..))")
public void afterThrowingAdvice(){
    System.out.println("异常通知...");
}
不同切面排序

类上加 @Order(整数) 注解, 数字越小, 优先级越高.

切点表达式复用
@Pointcut("execution(* com.powernode.spring6.service..*(..))")
public void commonPoint(){
    
}
@Before("commonPoint()")
public void beforeAdvice(){
    System.out.println("前置通知...");
}
@AfterReturning("commonPoint()")
public void afterReturningAdvice(){
    System.out.println("后置通知...");
}
全注解式开发
@Configuration
@ComponentScan("com.powernode.spring6.service")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Spring6Config {
}
配置实现AOP
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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 http://www.springframework.org/schema/context/spring-context.xsd>
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="userService" class="com.powernode.spring6.service.UserService"/>
    <bean id="timerAspect" class="com.powernode.spring6.service.TimerAspect"/>
    <aop:config>
        <aop:pointcut id="mypointcut" expression="execution(* com.powernode.spring6.service..*(..))"/>
        <aop:aspect ref="timerAspect">
            <aop:around method="aroundAdvice" pointcut-ref="mypointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
一个通知有多个切点
@Pointcut("execution(* com.powernode.spring6.biz..save*(..))")
public void savePointcut(){}
@Pointcut("execution(* com.powernode.spring6.biz..modify*(..))")
public void modifyPointcut(){}
@Pointcut("execution(* com.powernode.spring6.biz..delete*(..))")
public void deletePointcut(){}
@Before("savePointcut() || deletePointcut() || modifyPointcut()")
public void beforeAdvice(JoinPoint joinPoint){
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
    String nowTime = sdf.format(new Date());
    System.out.println(nowTime + "张三 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
}

Spring对事务的支持

原理: spring对aop的封装.

 声明式事务
基于注解

配置

命名空间

<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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

配置事务管理器bean并开启

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>

使用

@Transactional (可加在类或方法上)

propagation属性(事务的传播行为)

七种传播行为:

REQUIRED: 没有就新建,有就加⼊.

SUPPORTS:有就加⼊,没有就不管了.

MANDATORY:有就加⼊,没有就抛异常.

REQUIRES_NEW: 不管有没有,直接开启⼀个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起.

NOT_SUPPORTED:不⽀持事务,存在就挂起.

NEVER:不⽀持事务,存在就抛异常.

NESTED:有事务的话,就在这个事务⾥再嵌套⼀个完全独⽴的事务,嵌套的事务可以独⽴的提交
和回滚。没有事务就和REQUIRED⼀样.

事务的四种隔离级别

事务超时
@Transactional(timeout = 5)

注:在当前事务当中,最后⼀条DML语句执⾏之前的时间。如果最后⼀条DML语句后⾯很有很多业务逻辑,这些业务代码执⾏的时间不被计⼊超时时间。
当然,如果想让整个⽅法的所有代码都计⼊超时时间的话,可以在⽅法最后⼀⾏添加⼀⾏⽆关紧要的DML语句。

只读事务
@Transactional(readOnly = true)

将当前事务设置为只读事务,在该事务执⾏过程中只允许select语句执⾏,delete insert update均不可执⾏。
作⽤:启动spring的优化策略。提⾼select语句执⾏效率

设置哪些异常回滚事务
@Transactional(rollbackFor = RuntimeException.class)
设置哪些异常不回滚事务
@Transactional(noRollbackFor = RuntimeException.class)
事务全注解开发

配置类

@Configuration
@ComponentScan("com.powernode.bank")
@EnableTransactionManagement
public class Spring6Config {

    @Bean(name = "dataSource")
    public DruidDataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
        dataSource.setUsername("root");
        dataSource.setPassword("1234");
        return dataSource;
    }

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

    @Bean(name = "txManager")
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager tx = new DataSourceTransactionManager();
        tx.setDataSource(dataSource);
        return tx;
    }
}
声明式事务xm实现方式

命名空间

<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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

配置切面等

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="transfer" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
        <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
    </tx:attributes>
</tx:advice>
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(* com.powernode.bank.service..*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

Spring6整合JUnit

junit4

依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.10</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

使用

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class SpringJunit4Test {
    @Autowired
    private User user;
    @Test
    public void testUser(){
//        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//        User user = applicationContext.getBean("user", User.class);
        System.out.println(user.getName());
    }
}
junit5

配置

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.10</version>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.9.0</version>
    <scope>test</scope>
</dependency>

使用

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:spring.xml")
public class SpringJunit5Test {
    @Autowired
    private User user;
    @Test
    public void testUser(){
        System.out.println(user.getName());
    }
}

Spring6集成MyBatis5

依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>6.0.4</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>6.0.4</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.10</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>3.0.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.13</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

mybatis配置日志 (核心配置文件)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
</configuration>

spring核心配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--组件扫描-->
    <!--<context:component-scan base-package="com.powernode.bank"/>-->
    <!--在spring的核心配置文件中引入其他的子spring配置文件-->
    <import resource="common.xml"/>

    <!--引入外部的属性配置文件-->
    <context:property-placeholder location="jdbc.properties"/>

    <!--数据源-->
    <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}"/>
    </bean>

    <!--配置SqlSessionFactoryBean-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--指定mybatis核心配置文件-->
        <property name="configLocation" value="mybatis-config.xml"/>
        <!--指定别名-->
        <property name="typeAliasesPackage" value="com.powernode.bank.pojo"/>
    </bean>

    <!--Mapper扫描配置器,主要扫描Mapper接口,生成代理类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.powernode.bank.mapper"/>
    </bean>

    <!--事务管理器-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--启用事务注解-->
    <tx:annotation-driven transaction-manager="txManager"/>

</beans>
Spring八大模式

1.简单工厂模式

BeanFactory的getBean()⽅法,通过唯⼀标识来获取Bean对象。是典型的简单⼯⼚模式(静态⼯⼚模式.

2.工厂方法模式

FactoryBean是典型的⼯⼚⽅法模式。在配置⽂件中通过factory-method属性来指定⼯⼚⽅法,该⽅法是⼀个实例⽅法。

3.单例模式

Spring⽤的是双重判断加锁的单例模式.

4.代理模式

Spring的AOP就是使⽤了动态代理实现的。

5.装饰器模式

Spring中类名中带有:Decorator和Wrapper单词的类,都是装饰器模式。

6.观察者模式

定义对象间的⼀对多的关系,当⼀个对象的状态发⽣改变时,所有依赖于它的对象都得到通知并⾃动更新。Spring中观察者模式⼀般⽤在listener的实现.

7.策略模式 (面向接口编程)

策略模式是⾏为性模式,调⽤不同的⽅法,适应⾏为的变化 ,强调⽗类的调⽤⼦类的特性 。

⽐如我们⾃⼰写了AccountDao接⼝,然后这个接⼝下有不同的实现类:AccountDaoForMySQL,
AccountDaoForOracle。对于service来说不需要关⼼底层具体的实现,只需要⾯向AccountDao接⼝调⽤,底层可以灵活切换实现,这就是策略模式。

8.模板方法模式
Spring中的JdbcTemplate类就是⼀个模板类。它就是⼀个模板⽅法设计模式的体现。在模板类的模板⽅法execute中编写核⼼算法,具体的实现步骤在⼦类中完成。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值