「课件」原创 => Spring6【转载请标明出处】

第一章·配置篇

1.1 pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.1.3</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
    </dependency>
    <!-- AOP 面向切片编程 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>6.1.3</version>
    </dependency>
    <dependency>
        <groupId>jakarta.annotation</groupId>
        <artifactId>jakarta.annotation-api</artifactId>
        <version>2.1.1</version>
    </dependency>
    <!-- 下方【集成 MyBatis】 如用不到 请自行注释!-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.15</version>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.0.31</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>3.0.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>6.1.3</version>
    </dependency>
    <!--下方【导入其它数据源】如用不到 请自行注释! -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>5.0.1</version>
    </dependency>
    <!--由于 HikariCP 自动会使用 slf4j 日志打印,所以要导入 slf4j -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jdk14</artifactId>
        <version>1.7.25</version>
    </dependency>
    <!--下方【集成 Junit】如用不到 请自行注释! -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.9.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>6.0.10</version>
    </dependency>
</dependencies>

<build>
 <pluginManagement>
     <plugins>
         <plugin>
             <artifactId>maven-compiler-plugin</artifactId>
             <version>3.10.1</version>
             <configuration>
                 <compilerArgs>
                     <arg>-parameters</arg>
                 </compilerArgs>
             </configuration>
         </plugin>
     </plugins>
 </pluginManagement>
</build>

1.2 spring-config.xml

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
	<!--导入其它 spring*.xml 配置文件-->
    <import resource = "B.xml" />
    <!--注册 bean -->
    <bean id="A" name="A" class="top.muquanyu.bean.A">
        
    </bean>
</beans>
  • B.xml
<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--诶哟~ 小老弟, 过来了 ~-->
    <bean name="B" class="top.muquanyu.bean.B"/>
</beans>

1.2.1 干掉单例模式

<bean name="A" class="top.muquanyu.bean.A" scope = "prototype"/>

1.2.2 懒加载

Spring 容器会在第一次请求该 bean 时才进行实际的初始化操作,而不是在容器启动时就立即创建。

<bean id="dependentBean" class="com.example.DependentBean">
    <!-- configuration for dependentBean -->
</bean>

<bean id="exampleBean" class="com.example.ExampleBean" lazy-init="true" depends-on="dependentBean">
    <!-- configuration for exampleBean -->
</bean>

这对于大型应用程序来说可以提高启动性能,因为不是所有的 bean 都需要在启动时初始化,而是在需要时再初始化。这在一些场景下可以有效地减少启动时间和资源占用。

depends-on 是初始化这个 bean 之前有无其它的 bean 先初始化!

举例来说,假设你有两个 bean,其中一个被配置为懒加载,而另一个是依赖它的。你可以使用 depends-on 来确保在懒加载的 bean 被请求时,其依赖的 bean 已经在之前被初始化!

1.2.3 init、destroy

  • init:当容器创建时,默认情况下Bean都是单例的,那么都会在一开始就加载好,对象构造完成后,会执行 init-method 绑定的方法
  • destroy:当代码中 调用 context.close(); 关闭容器之后,此时容器内存放的Bean也会被一起销毁,然后会执行 destroy-method 绑定的方法 注意哟,如果是 ConfigurableApplicationContext 的 context,但是这玩意是注解的时候才会用到。
  • ApplicationContext,是 不需要手动关闭的,所以可能你看不到 destroy() 方法的执行。它一般都是注册关闭挂钩,然后 JVM 关掉它。当然你也可以采取 ((ClassPathXmlApplicationContext) applicationContext).close(); 然后关闭也行。
public void init(){
    System.out.println("我是对象初始化时要做的事情!");    
}

public void destroy(){
    System.out.println("我是对象销毁时要做的事情!");
}

我们可以通过init-methoddestroy-method来指定:

<bean name="A" class="top.muquanyu.bean.A" init-method="init" destroy-method="destroy"/>

1.2.4 依赖注入

  • set 方法注入

Spring 规定,如果要使用 set 方式依赖注入的话,那么就必须在 该类中,声明一个 对该属性的 set 方法

并且这个方法的命名必须是 : set + 属性名 (符合驼峰命名原则)否则是不会识别到这个 set 方法的!会报错的 ~

public class B {
    private A a;
    
    public void setA (A a) {
        this.a = a;
    }
    
    public void f(){
        a.say();
    }
}
<bean name="A" class="top.muquanyu.bean.a"/>
<bean name="B" class="com.test.bean.Student">
    <property name="a" ref="A"/>
        <!--  对于集合类型,我们可以直接使用标签编辑集合的默认值  -->
    <property name="list">
        <list>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </list>
    </property>
     <property name="map">
        <map>
            <entry key="语文" value="100.0"/>
            <entry key="数学" value="80.0"/>
            <entry key="英语" value="92.5"/>
        </map>
    </property>
</bean>
B b = context.getBean(B.class);
b.f();
  • 有参构造注入不太推荐
public class B {
    private final A a;  

    public B(A a){   
        this.a = a;
    }
    
    public void f(){
        a.say();
    }
}
<bean name="A" class="top.muquanyu.bean.a"/>
<bean name="B" class="com.test.bean.Student">
    <!--如果构造方法的参数是 引用类型的对象的话,就要提供 ref 属性-->
    <!--<constructor-arg name="a" ref="A"/>-->
    <!--如果构造方法的参数是 基本类型的话,可以写 type = "类型" 来判定 而 value = "这个参数传递过去的值" -->
    <!--<constructor-arg value="1" type="int"/>-->
        <!--如果构造方法的参数是 基本类型的话,可以写 name = "参数名" 来判定 而 value = "这个参数传递过去的值" -->
    <constructor-arg value=1 name="age"/>
    <constructor-arg value="小明"  name="name"/> <!--多个参数-->
</bean>

1.3 Bean 的属性继承【基本不用】

<bean id="parentTriangle" class="...">
  <property name="pointA" ref="pointA"/>
</bean>

<bean id="triangleOne" parent="parentTriangle">
  <property name="pointB" ref="pointB"/>
  <property name="pointC" ref="pointC"/>
</bean>

1.4 自动装配

autowire 属性:前提是找到的 ref 引用必须是 单例的,否则根本不知道你是哪个呀。。

  • byType:依照 Type 类型直接 去找 容易找错
<bean name="B" class="top.muquanyu.bean.B" autowire="byType"/> <!--通过 set 方法的 参数类型 直接在 这个 xml 文件当中 去找 符合 的bean-->
  • byName:依照 参数的名字与 注册bean id/name 一致,然后 去找
<bean name="B" class="top.muquanyu.bean.B" autowire="byName"/> <!--通过 set 方法的 参数名字一样的 bean 中 name,然后在 这个 xml 文件当中 去找 符合 的 bean-->
  • constructor:依照有参构造里面的 参数类型去寻找符合的 ref
<bean name="B" class="top.muquanyu.bean.B" autowire="constructor"/> 
  • 注意:autowire 是可以与 property 联用的!

  • candidate="false" :不会被自动装配遍历筛选

<bean name="B1" class="top.muquanyu.bean.B1"/>
<bean name="B2" class="top.muquanyu.bean.B2" autowire-candidate="false"/>
<bean name="A" class="top.muquanyu.bean.A" autowire="byType"/>
  • primary="true":如果符合自动装配,则会被优先装配
<bean name="B1" class="top.muquanyu.bean.B1"/>
<bean name="B2" class="top.muquanyu.bean.B2" primary="true"/>
<bean name="A" class="top.muquanyu.bean.A" autowire="byType"/>

第二章·使用篇【Java 代码】

2.1 基本使用

public static void main(String[] args) {
    // 使用 ClassPathXmlApplicationContext 去加载我们的 XML 配置文件
    // ApplicationContext 是专门 加载 XML 配置文件获取到的 IoC 容器的引用类型
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
    
    // 通过 id/name 来获取注册 Bean 对应的对象
    A a = (A) context.getBean("A"); 
    a.name = "Spring";
    A.say(); 
    // 通过唯一的 Class 对象 来获取注册 Bean 对应的对象
    A a2 = (A) context.getBean(A.class);
    a2.say(); // 若为单例模式, 则获取的是同一个对象
}

2.2 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--我之前注册的是这个-->
    <!--<bean name="AImpl" class="top.muquanyu.bean.AImpl1"/>-->
    <!--现在注册的是这个-->
    <bean name="AImpl" class="top.muquanyu.bean.AImpl2"/>
    <!--起别名用的 都是 alias 关键字-->
    <alias name="AImpl" alias="A"/>
    
</beans>
package top.muquanyu.bean;

public interface A {
    public void say();
}

package top.muquanyu.bean;

public class AImpl1 implements A {
    public void say(){
        System.out.println("版本1");
    }
}

package top.muquanyu.bean;

public class AImpl2 implements A {
    public void say(){
        System.out.println("版本2");
    }
}

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
    A a = (A) context.getBean("A");
    A.say();
    A a2 = (A) context.getBean("AImpl");
    A.say(); 
}

2.3 工厂模式(基本不用)

public class Cat {
    Cat() {
        System.out.println("我是喵");
    }
}
public class CatFactory {
    public static Cat getCat(){
        return new Cat();
    }
}

2.3.1 xml 配置方式

<bean class="top.muquanyu.bean.CatFactory" factory-method="getCat"/>

上述缺点:无法进行进一步的属性依赖注入!下面的写法可以解决这个问题!

<bean name="CatFactory" class="top.muquanyu.bean.CatFactory">
    <!--随便注入-->
</bean>
<bean factory-bean="CatFactory" factory-method="getCat"/>

2.3.2 实现接口方式

package top.muquanyu.bean;

import org.springframework.beans.factory.FactoryBean;

public class CatFactory implements FactoryBean<Cat> {

    @Override
    public Cat getObject() throws Exception {
        return new Cat();
    }

    @Override
    public Class<?> getObjectType() {
        return Cat.class;
    }
}
Cat cat = (Cat) context.getBean("CatFactory"); // 直接拿到 cat 目标对象
CatFactory cat = (CatFactory) context.getBean("&CatFactory"); // 直接拿到 CatFactory 工厂类对象

第三章·AOP 篇【Java 代码】

3.1 切入点表达式 execution

execution 表达式的格式:

// execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

其中 <返回类型模式><方法名模式> (<参数模式>) 是必须得写的

符号 含义
*
.. 当前xx及其子xx,如:当前包及其子包
(..) 任意参数列表
  • 由此我们可以举几个例子
execution(public *.*(..)) // 只要是 public 的方法都可以
execution(* top.muquanyu.service..*(..)) // top.muquanyu.service 当前包或子包下面的任意方法
execution(* faker(String,String)) // 匹配 faker 这个参数列表的方法
execution(* faker(String,*)) // * 任意类型的参数
execution(* faker(Object+)) // 匹配 Object 类或该类的子类都可以

3.2 before 与 after

// 切入点
public class A {
    public void say(){
       System.out.println("嘻嘻嘻!"); 
    }
}

// 切片(切片就得往 T喵 ~ 地 切入点里切)
public class AOPofA {
    public void beforeSay(){
       System.out.println("我想嘻嘻!"); 
    }
}
<bean class="top.muquanyu.bean.A"/>
<bean id="AOPofA" class="top.muquanyu.bean.AOPofA"/>
<aop:config>
    <!--声明一个切入点,比如切入点是一个方法-->
    <!--demo01 是切入点的名字-->
    <aop:pointcut id="demo01" expression="execution(* top.muquanyu.bean.A.say())"/>
    <!--声明一个类作为切片,这样才能作为切片,进行切入!-->
    <aop:aspect ref="AOPofA">
    <!--选择切入方式 绑定一个方法,然后指明一个切入点进行切入-->
        <aop:before method="beforeSay" pointcut-ref="demo01"/>
    </aop:aspect>
</aop:config>
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
    A a = context.getBean(A.class);
    a.say();
}

3.3 JoinPoint

JoinPoint 可以拿到 原对象方法的很多东西。

  • 比如传递过来的参数值
public class A {
    public void say(Integer num){  
        System.out.println(num);
    }
}

public class AOPofA {
    public void afterA(JoinPoint joinPoint) {
        System.out.println(joinPoint.getArgs()[0]); // 轻松拿到 代理对象的 参数值
    }
}
<bean class="top.muquanyu.bean.A"/>
<bean id="AOPofA" class="top.muquanyu.bean.AOPofA"/>
<aop:config>
    <aop:pointcut id="demo01" expression="execution(* top.muquanyu.bean.A.say(Integer))"/>
    <aop:aspect ref="AOPofA">
        <aop:after method="afterA" pointcut-ref="demo01"/>
    </aop:aspect>
</aop:config>

3.4 around 环绕方法

<bean class="top.muquanyu.bean.A"/>
<bean id="AOPofA" class="top.muquanyu.bean.AOPofA"/>
<aop:config>
    <aop:pointcut id="demo01" expression="execution(* top.muquanyu.bean.A.say())"/>
    <aop:aspect ref="AOPofA">
        <aop:around method="around" pointcut-ref="demo01"/>
    </aop:aspect>
</aop:config>
// around 环绕方法是否有返回值,取决于 代理的对象那个方法 是否有返回值,如果有!那么我们真的就得给人家把这个返回值 通过 around() 代理方法 给它进行返回。
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("before 部分");
    String arg = joinPoint.getArgs()[0]; // 可以直接拿 这个方法本来传递的参数
    Object value = joinPoint.proceed(new Object[]{arg}); // 代理对象的那个方法,这样就自由多了吧 ~ 我们可以把这个方法 插入到 此代码的任何位置, 如果方法有参数,那么你别忘了 传参
    // 这个参数,当然我们可以在这里 通过我们来提供,不一定非要 拿到 本来的参数 来传参
    System.out.println(value);
    return value;
}

3.5 接口方式实现 AOP

如果你使用了 接口来实现 AOP,那么在 xml 配置文件里面,就得使用 <aop:advisor>

<bean id="Temp" class="top.muquanyu.bean.Temp"/>
<bean id="TempAOP" class="top.muquanyu.bean.TempAOP"/>
<aop:config>
    <aop:pointcut id="demo01" expression="execution(* top.muquanyu.bean.temp())"/>
  	<!--  这里只需要添加我们一会儿写好的advisor就可以了 -->
    <aop:advisor advice-ref="TempAOP" pointcut-ref="demo01"/>
</aop:config>
public class TempAOP implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("Before");
    }
    
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(returnValue);
    }
}
public class Temp {
    public void temp(){
        System.out.println("我是原本的方法内容");
    }
}

3.5.1 实现环绕

public class TempAOP implements MethodInterceptor {   //实现MethodInterceptor接口
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {  //invoke 不就是代理方法嘛,跟 咱们之前设的 around() 没啥区别
        Object value = invocation.proceed();   // 懂得都懂,不解释
        return value;
    }
}

第四章·注解篇【Java 代码】

4.1 配置类

主要的思想是:以 Java 代码的形式代替 xml 臃肿的配置文件 但实际开发中,一般都是 注解与xml 混合搭配使用的 ~

@Configuration
public class NewConfiguration {
    @Bean("Cat", initMethod = "", destroyMethod = "", autowireCandidate = false) // 他娘的, 该有的都有!
    @Lazy(true)     //对应lazy-init属性
@Scope("prototype")    //对应scope属性
@DependsOn("Animal")    // 对应depends-on 属性
    public Cat cat(){
        // 呦西, 通过代码的方式 拿到 bean 对应的 实例化对象!
        return new Cat();
    }
}
// ApplicationContext 是专门接收 配置类获取到的 IoC 对象的
ApplicationContext  context = new AnnotationConfigApplicationContext(MyConfiguration.class,第二个配置类,第三个配置类 ...); // 把配置类加载
Cat cat = context.getBean("Cat");
System.out.println(cat);

4.2 Import 其它配置类

@Configuration
public class MyConfiguration {
    @Bean("Dog")
    public Dog dog(){
        return new Dog();
    }
}
@Import(MyConfiguration.class) // 把这个配置类 Import 进来
@Configuration
public class NewConfiguration {
    @Bean("Cat")
    public Cat cat(){
        return new Cat();
    }
}

4.3 多种其它方式 注册为 Bean

首先你必须得在 配置类的上方写上 @ComponentScan(待扫描的包) 这样才可以扫描到那些并非在 配置类中 @Bean 方式的 实例。

@Configuration
@ComponentScan("top.muquanyu.bean") 
public class NewConfiguration {
    
}

4.3.1 @Component

这个就是说单纯的 注册进去,作为一个组件

@Component("Dog") 
public class Dog {
    
}

4.3.2 @Repository

注册 Dao 层的 有关数据库表的操作

@Repository
public class FoodDaoImpl {
}

4.3.3 @Service

注册 Service 业务层 利用 Dao 层的方法去实现一些业务

@Service
public class FoodServiceImpl {
}

4.3.4 @Controller

这个 SpringMVC、SpringBoot 中 会有,是替代 麻烦的 Servlet 而研发的。

题外话:

其实我们哪怕是 不给这些 bean 起名字,它会自动地 生成默认的名字,按照小写驼峰原则。

例如 Cat 类,那么 默认的 名字就是 cat

Cat cat= (Cat ) context.getBean("cat"); // 你要是没自己去起名字,那默认就是这个 cat
System.out.println(cat);

大家可以自行尝试 ~

4.4 自动装配

public class Dog {
    @Autowired // 默认采用 byType 方式进行自动装配, 所以可能会遇到装配选择错误的问题
    private Animal animal;
    // emmm... 你可能在想,欸?咋没有 set 方法呢??这就是注解新增的一个功能,就是为了方便,然后 可以让你采用这种 注解在 属性上的方式直接拿到 这个ref 对象自动装配。
}
  • @Qualifier 指明匹配 bean 的名称,去避免 byType 匹配错误的问题
public class Dog {
    @Autowired
    @Qualifier("Animal") // 这就是开启了 byName 模式
    private Animal animal;
}
  • 旧版本的 @Resource 默认byName 如果找不到则 byType,可以添加到set方法、字段上。 其实说句实话,包括现在的新版本,也比价推

4.4.1 set 方法 【自动装配】

public class UserService {

    private UserRepository userRepository;

    // Other properties and methods...

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        // 在setter方法内部执行额外逻辑
        if (userRepository == null) {
            throw new IllegalArgumentException("UserRepository cannot be null.");
        }

        // 执行其他验证或处理逻辑
        // ...

        // 正常执行依赖注入
        this.userRepository = userRepository;

        // 触发事件
        this.onUserRepositorySet();
    }

    // 额外的验证、处理或触发事件的方法
    private void onUserRepositorySet() {
        // 可以在这里执行一些逻辑,例如触发事件、记录日志等
        System.out.println("UserRepository has been set.");
    }

    // Other methods that use userRepository...
}

4.4.2 有参构造【自动装配】

public class UserService {

    private final UserRepository userRepository;
    private final EmailService emailService;

    @Autowired
    public UserService(UserRepository userRepository, EmailService emailService) {
        // 在构造方法内部进行依赖注入
        if (userRepository == null || emailService == null) {
            throw new IllegalArgumentException("UserRepository and EmailService cannot be null.");
        }

        this.userRepository = userRepository;
        this.emailService = emailService;
    }

    // Other methods that use userRepository and emailService...
}

4.5 EL 表达式

注解中通常都是用 EL 表达式来进行 普通属性的注入

Spring的表达式语言(Expression Language,简称EL)是一种强大的表达式语言,它允许在运行时访问和操作对象的属性。Spring EL通常在Spring框架中的注解、XML配置、模板引擎等地方被广泛使用。以下是一些Spring EL的常见用法:我个人是比较建议 主要去学习 用注解去用 EL!然后 xml 配置的话,我认为这么简单的东西,你再用配置文件。。。说不过去吧 ~ 咳咳 ~

而且说句实话啊!!对于 Spring 来说,它的EL 表达式这东西很鸡肋的。。。你要是 SpringMVC 与前端页面数据交互,人家的那个 EL 表达式可就有大用了。

4.5.1 xml 方式【了解】

<bean id="A" class="top.muquanyu.bean.A">
    <property name="age" value="#{ T(java.lang.Math).random() * 100.0 }"/>
</bean>

4.5.2 读 properties 文件(字符串注入)

读 properties 文件(字符串注入)

  • 先创建一个 .properties 后缀的配置文件
name = 小明
age = 18
  • 使用 @PropertySource注解
@Configuration
@ComponentScan("top.muquanyu.bean")
@PropertySource("classpath:demo.properties")
public class MyConfiguration{
    
}
@Component
public class Student {
    @Value("${name}") 
    private String name; 
    private Integer age;

    public void getName(){
        System.out.println(this.name);
    }
    
    public Student(@Value("${name}") String name, @Value("${age}") String age){
        this.age = age;
        this.name = name;
    }
    
    public setAge(@Value(10) age){
        this.age = age;
    }
}

4.5.3 @Value 更多使用方式

  1. 获取系统属性然后进行注入
@Value("#{systemProperties['os.name']}")
private String osName;
  1. 调用类的方法
@Value("#{T(java.lang.Math).random() * 100}")
private double randomNumber;
  1. 获取类的属性
@Value("#{person.pName}")
private String pName;

person 是 把一个类 注册为 Bean 起的 name 名字,这玩意可不是随便写的哟!

  1. 链式调用方法(也就是支持这种方式)
@Value("#{person.getpName().toUpperCase()}")
private String pName;
  • 处理链式时突然获取到 null 对象(加个 ? 就完事了)
@Value("#{person.getpName()?.toUpperCase()}")
private String pName;

这其实叫做 elvis 运算符(如果有值则输出值,为null则输出默认值),删掉注解@Value

  1. 算术运算
@Value("#{(1+2)>3}")
public boolean count;
  1. 三目运算符
@Value("#{(1+2)>3 ? 'yes' : 'no'}")
public String count;
  1. 集合类型的运用
@Value("#{testBean.list[2]}")
private Person person;
Person{pName='wangwu', age=20}
@Value("#{testBean.map[key1]}")
private String value;
value1
@Value("#{testBean.list.?[age==19]}")
private List<Person> person;
[Person{pName='zhangsan', age=19}, Person{pName='lisi', age=19}]

SpEL取出了age为19的对象.注意要拿list去接收,不然会报错.其中.?[]是查询所有,而.[1]是查询第一个,满足条件的,.$[]是查询最后一个满足条件的. 也就是它们查询结束的 条件是不一样的 ~

@Value("#{testBean.list.![pName]}")
private List<String> pNames;
[zhangsan, lisi, wangwu]

这个意思是:把 list 中每个元素的 pName 属性都拿出来,然后组成一个新的集合,我们 Spring 又称之为 集合投影 ![元素的某个属性] 就可以实现这个效果!

4.5.4 Java 代码层面 使用 EL 表达式

我们其实也可以直接通过 Java 代码的方式 去玩这个 EL 表达式,但是但是但是,重要的事情说三遍,真的很鸡肋。。。

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello EL'"); 
parser.parseExpression("'Hello EL'.toUpperCase().bytes.length"); 
parser.parseExpression("50 < 45"); 
parser.parseExpression("10 + 20");
parser.parseExpression("T(java.lang.Math).random()"); // 直接可以拿到整个类的对象然后调用方法,返回的结果 可以用 getValue 获取!
// 表达式运算结果可以通过getValue()获取
System.out.println(exp.getValue());   
  • 目标对象的属性或方法
Student student = context.getBean(Student.class);
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
exp.setValue(student, "小明"); // 设置这个属性值
System.out.println(exp.getValue(student));    // 拿到 student 对象的 name 属性值

// 集合
Expression exp = parser.parseExpression("list[2]");
System.out.println(exp.getValue(student));
Expression exp = parser.parseExpression("{1,2,3}"); //使用{}来快速创建List集合
List value = (List) exp.getValue();
value.forEach(System.out::println);

Expression exp = parser.parseExpression("{name: '小明', age: 10}");
System.out.println(exp.getValue());

4.6 注解实现 AOP

  • 首先要让 配置类支持 AOP
@EnableAspectJAutoProxy
@ComponentScan("top.muquanyu.bean")
@Configuration
public class NewConfiguration {
}
@Component
public class Temp {
    public void temp(){
        System.out.println("fuck you man");
    }
}
  • 声明一个切片
@Aspect
@Component
public class TempAOP {
    @Before("execution(* top.muquanyu.bean.Temp.temp())")
    public void before(){
        System.out.println("Before");
    }
}
  • 使用
public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(NewConfiguration.class);
    Student temp = context.getBean(Temp.class);
    temp.temp();
}

4.6.1 得到原方法的参数

@Component
public class Temp {
    public void temp(String you){
        System.out.println("fuck" + you + "man");
    }
}
@Before(value = "execution(* top.muquanyu.bean.Temp.temp(..)) && args(you)", argNames = "str")
public void before(String str){
    System.out.println(str); 
}
  • 或者直接用 JoinPoint
@Before("execution(* top.muquanyu.bean.Temp.temp(..))")
public void before(JoinPoint point){
    System.out.println("参数列表:"+ Arrays.toString(point.getArgs()));
}

4.6.2 around 环绕

@Around("execution(* top.muquanyu.bean.Temp.temp(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
    System.out.println("before");
    Object ret = point.proceed();
    System.out.println("after");
    return ret;
}

4.6.3 @AfterReturning

这注解 厉害,是在返回之后,然后 给你 加一些方法。

public String doSomeThing(){
    System.out.println("我去办个儿事!");
    return "好好好";
}
@AfterReturning(value = "execution(* top.muquanyu.bean.Temp.doSomeThing())", returning = "haohaohao")   //使用 returning指定接收方法返回值的参数returnVal
public void afterReturn(Object haohaohao){
    System.out.println("返回值是:"+returnVal);
}

4.7 @EnableAsync 异步任务(基本不用)

转载或参考自:itbaima

@EnableAsync
@Configuration
@ComponentScan("top.muquanyu.bean")
public class NewConfiguration {
}
@Component
public class Student {
    public void syncTest() throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
    }

    @Async
    public void asyncTest() throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
    }
}

4.8 @EnableScheduling 定时任务(基本不用)

转载或参考自:itbaima

@EnableScheduling
@Configuration
@ComponentScan("top.muquanyu.bean")
public class NewConfiguration {
}
@Scheduled(fixedRate = 1000)   // 1000 毫秒 = 1 秒
public void task(){
    System.out.println(new Date()); // 一秒一秒的时间输出
}
  • fixedDelay:在上一次定时任务执行完之后,间隔多久继续执行。
  • fixedRate:无论上一次定时任务有没有执行完成,两次任务之间的时间间隔。
  • cron:如果嫌上面两个不够灵活,你还可以使用cron表达式来指定任务计划。

4.9 监听器(基本不用)

监听就是等待某个事件的触发,然后去执行一些代码操作。

转载或参考自:itbaima

实现 一些接口

@Component
public class TestListener implements ApplicationListener<ContextRefreshedEvent> { // ContextRefreshedEvent 是监听的事件
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println(event.getApplicationContext());   // 可以直接通过事件获取到事件相关的东西
    }
}
  • 自定义 事件
public class TestEvent extends ApplicationEvent {   //自定义事件需要继承ApplicationEvent
    public TestEvent(Object source) {
        super(source);
    }
}
@Component
public class TestListener implements ApplicationListener<TestEvent> {
    @Override
    public void onApplicationEvent(TestEvent event) {
        System.out.println("发生了一次自定义事件,成功监听到!");
    }
}

比如现在我们希望在定时任务中每秒钟发生一次这个事件:

@Component
public class TaskComponent implements ApplicationEventPublisherAware {  
  	// 要发布事件,需要拿到ApplicationEventPublisher,这里我们通过Aware在初始化的时候拿到
  	// 实际上我们的ApplicationContext就是ApplicationEventPublisher的实现类,这里拿到的就是
  	// 我们创建的ApplicationContext对象
    ApplicationEventPublisher publisher;

    @Scheduled(fixedRate = 1000)   //一秒一次
    public void task(){
      	// 直接通过ApplicationEventPublisher的publishEvent方法发布事件
      	// 这样,所有这个事件的监听器,都会监听到这个事件发生了
        publisher.publishEvent(new TestEvent(this));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }
}

第五章·整合篇【Java 代码】

5.1 使用 mybatis-spring 中的 SqlSessionTemplate 工具类

  • 实体类
@Data
public class Student {
    private int sid;
    private String name;
    private String gender;
}
  • Mapper
public interface StudentMapper {
    @Select("select * from student")
    Student getAllStudnets();
}
  • mybatis-config.xml
<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
                <property name="username" value="root"/>
                <property name="password" value="123123"/>
            </dataSource>
        </environment>
    </environments>
  	<mappers>
        <mapper class="top.muquanyu.mapper.StudentMapper"/>
    </mappers>
</configuration>
@Configuration
@ComponentScan("top.muquanyu.bean")
public class NewConfiguration {
  	// 注册为 Bean 会更加方便
    @Bean("SqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate() throws IOException {
        // SqlSessionTemplate 会包装 sqlSessionFactory 工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        return new SqlSessionTemplate(factory);
    }
}
public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(NewConfiguration.class);
    SqlSessionTemplate template = context.getBean("SqlSessionTemplate");
    StudentMapper studentMapper = template.getMapper(StudentMapper.class);
    System.out.println(studentMapper.getAllStudnets());
}

5.2 @MapperScan() 扫描 Mapper 自动注册

@MapperScan 注解是 mybatis-spring 中特有的。所以我们才说 我们导入的这些依赖都是有大用的!

它可以直接自动的 把扫描到的 所有 mapper 全部注册!然后我们就可以只关注 使用那一部分了。 @Bean 这玩意 或者 @Mapper 亦或者 @Compoent 不用写了 OK?

@Configuration
@ComponentScan("top.muquanyu.entity")
@MapperScan("top.muquanyu.mapper")
public class NewConfiguration {
public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(NewConfiguration.class);
    StudentMapper mapper = context.getBean(StudentMapper.class);
    System.out.println(mapper.getAllStudnets());
}

5.3 DataSource 方式进行数据库连接

@Configuration
@ComponentScan("top.muquanyu.entity")
@MapperScan("top.muquanyu.mapper")
public class NewConfiguration {
    @Bean   
    public DataSource dataSource(){
        return new PooledDataSource("com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://localhost:3306/demo", "root", "123456");
    }

    // 这里的参数 dataSource 会自动的装配 我们上面注册的 dataSource
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){  
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean;
    }
}

这种方式是可以不需要再去 SqlSessionTemplate 的,这种方式真的比较方便。但其实我不是特别推荐这种。。有的时候实际开发中,xml 与 注解搭配着用真的是最优解!

5.4 HikariCP 【JDBC 连接池】

HikariCP 优点太显眼:轻量级、速度快!你可别看这俩优点说着轻松,这不是随便说说的。。

5.4.1 xml 方式

  • hikari-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<hikari-config>
    <dataSourceClassName>com.mysql.cj.jdbc.MysqlDataSource</dataSourceClassName>
    <dataSource>
        <serverName>localhost</serverName>
        <port>3306</port>
        <databaseName>your_database</databaseName>
        <user>your_username</user>
        <password>your_password</password>
    </dataSource>
    <poolName>MyPool</poolName>
    <maximumPoolSize>10</maximumPoolSize>
</hikari-config>
  • mybatis-config.xml
<?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>
    <!-- 其他 MyBatis 配置 -->

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="com.zaxxer.hikari.HikariDataSource">
                <property name="configLocation" value="classpath:hikari-config.xml"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 其他 MyBatis 配置 -->
</configuration>

5.4.2 Java 代码的 DataSource 方式

@Bean
public DataSource dataSource() {
    HikariDataSource dataSource = new HikariDataSource(); // 只要这里的 池子换成 Hikari 就完事了
    dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/demo");
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setUsername("root");
    dataSource.setPassword("123456");
    return dataSource;
}
@Slf4j // Lombok 直接快速应用该 日志
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(NewConfiguration.class);
        TestMapper mapper = context.getBean(TestMapper.class);
        log.info(mapper.getStudent().toString());
    }
}

5.5 事务管理

隔离也是分等级的:

  • ISOLATION_READ_UNCOMMITTED(读未提交):其他事务会读取当前事务尚未更改的提交(相当于读取的是这个事务暂时缓存的内容,并不是数据库中的内容)也就是说这个操作还没提交呢,但是已经有了提交之后的样子。我们读的就是提交之后的那个。但本质上,我们要知道的是,目前真的没提交呢!

会导致脏读,因为可能 人家没提交的数据到最后也没提交,进行了回滚。。那么你这个数据对我们程序而言就是脏的。

  • ISOLATION_READ_COMMITTED(读已提交):其他事务会读取当前事务已经提交的数据(也就是直接读取数据库中已经发生更改的内容)

可以避免脏读,因为 它肯定每次读取都是成功提交之后的数据。但可能导致虚读,也就是在提交之前的数据与提交之后的数据不一样。这就是虚读。

  • ISOLATION_REPEATABLE_READ(可重复读):其他事务会读取当前事务已经提交的数据并且其他事务执行过程中不允许再进行数据修改(注意这里仅仅是不允许修改数据)就是在读的时候,不允许其它事务进行修改。

这个级别,也是 MySQL 的默认隔离级别!

这个级别虽然已经很叼了,可以解决我们的脏读和虚读。但是它还是会 导致 幻读,因为 可重复读级别,只是限制了update 操作,没有限制 新增和删除的操作。所以可能会出现幻觉一样,感觉怎么多了一条数据或者少了一条数据?

因此,要解决这种问题,只能使用最后一种隔离级别串行化来实现了,每个事务不能同时进行,直接避免所有并发问题,简单粗暴,但是效率爆减,并不推荐。

  • ISOLATION_SERIALIZABLE(串行化):它完全服从ACID原则,一个事务必须等待其他事务结束之后才能开始执行,相当于挨个执行,效率很低!

5.5.1 ManagedTransaction 实务操作交给 Spring

ManagedTransaction 的设计就是为了 交给一些其它框架区实现这些具体的 实务操作。

  • 而我们学习的 Spring 刚刚好支持。
@Configuration
@ComponentScan("org.example")
@MapperScan("org.example.mapper")
@EnableTransactionManagement // 开启事务管理
public class NewConfiguration {

    // 事务管理必须得 提供一个 待管理的数据源
    @Bean
    public TransactionManager transactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
  	...
@Mapper
public interface TestMapper {
    ...

    @Insert("insert into student(name, sex) values('测试', '男')")
    void insertStudent();
}
public interface TestService {
    void test();
}
@Component
public class TestServiceImpl implements TestService{

    @Resource
    TestMapper mapper;

    @Transactional   // 此注解表示事务,之后执行的所有方法都会在同一个事务中执行
    public void test() {
        mapper.insertStudent();
        if(true) throw new RuntimeException("我是测试异常!");
        // 比较智能的是,Spring 进行托管事务之后,可以利用 AOP 机制进行方法的拓展处理,如果一旦发现 异常,就会进行事务的回滚。
        mapper.insertStudent();
    }
}
@Slf4j
public class Main {
    public static void main(String[] args) {
        log.info("项目正在启动...");
        ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
        TestService service = context.getBean(TestService.class);
        service.test();
    }
}

5.5.2 @Transactional() 属性

  • transactionManager:指定事务管理器
  • propagation:事务传播规则,一个事务可以包括N个子事务
  • isolation:事务隔离级别,不多说了
  • timeout:事务超时时间
  • readOnly:是否为只读事务,不同的数据库会根据只读属性进行优化,比如MySQL一旦声明事务为只读,那么久不允许增删改操作了。
  • rollbackFor和noRollbackFor:发生指定异常时回滚或是不回滚,默认发生任何异常都回滚。

Spring默认的传播级别 PROPAGATION_REQUIRED

意思是如果是一个事务的方法中调用了其它事务的方法,那么就会一起进行 该方法的事务管理。

其它常用的传播级别:

  • propagation = Propagation.SUPPORTS 如果单独调用该方法,那么哪怕出现异常,也不会进行回滚。除非 调用了持有这个方法的事务方法才会去进行回滚。

  • propagation = Propagation.MANDATORY

    如果该方法不存在于任何事务方法之中,然后被调用的话,那么单独运行就会直接异常!不管你到底是不是真的异常。

  • propagation = Propagation.NESTED 这玩意就是对 Savepoint 的二次封装。当出现异常的时候,只会回滚到该方法,而不是影响到 调用这个方法的 父亲事务方法! 即回滚到子事务方法的保存点

  • propagation = Propagation.NEVER 断绝所有的父亲事务方法!它不应该被 其它的事务方法调用!如果被调用就会报红。

5.6 整合 Junit

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.9.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.10</version>
</dependency>
@ExtendWith(SpringExtension.class)
// 使用 Spring 扩展类 来运行 测试类
// 这样的话 Spring 的所有功能我们就都能直接用了!
@ContextConfiguration(classes = TestConfiguration.class) // 我们说这个测试类要加载 TestConfiguration 这个 Spring 配置类!
public class TestMain {

    @Autowired // 这个是自动装配进去的,由于我们通过 @ContextConfiguration 指定了配置类所以我们就没必要再去 获取 ApplicationContext 对象然后再去 getBean 拿到 service 了,而是可以直接 @Autowired 拿到它!
    TestService service;
    
    @Test
    public void test(){
        service.test();
    }
}

  1. ↩︎

posted @ 2024-03-16 15:55  小哞^同^学的技术博客  阅读(10)  评论(0编辑  收藏  举报