Spring框架笔记

38 阅读11分钟

Spring中的核心概念

控制反转(IoC)与依赖注入(DI)

java1.jpg

XML文件配置

  • id名字不能乱取,对应接口中的方法名
  • class表示是接口对应的类,这种更加不能乱取了,而且要讲模块名完整加进来

java3.jpg

引用类型的就用ref

java4.jpg

总结一下依赖注入的基本流程

java5.jpg

java6.jpg

要注意name中bookDao和ref中bookDao不是同一个东西

java7.jpg

  1. 背景介绍 - 在Spring框架中,<property>标签通常用于配置JavaBean的属性。

当你看到<property name = "bookDao"/>这样的配置,它是在为一个包含bookDao属性的JavaBean进行属性注入的配置。

  1. 详细解释
  • name属性内容
    • name里面配置的是JavaBean中的属性名称。在这个例子中,bookDao是一个属性名。
    • 这个属性通常是在对应的Java类(一般是一个Spring管理的Bean)中定义的。例如,假设有一个名为BookService的类,它可能有一个如下的属性:
private BookDao bookDao; 
public void setBookDao(BookDao bookDao) { 
        this.bookDao = bookDao; } 
  • Spring会通过反射机制,在创建BookService这个Bean的时候,根据<property>标签中的name属性(这里是bookDao)找到对应的setBookDao方法。
  • 然后,Spring会尝试将一个BookDao类型的对象注入到这个属性中。

3.配置目的:这种配置方式实现了依赖注入。

  • 通过将bookDao属性注入到包含这个属性的Bean(如BookService)中,使得这个Bean可以使用bookDao对象提供的方法来完成业务逻辑。例如,BookService可能会使用bookDaofindById
public Book getBookById(Long id) { 
      return bookDao.findById(id); } 

bean基础配置

bean1.jpg

bean的作用范围

bean2.jpg

bean实例化的三种方法

其实后面用SpringBoot做开发的时候,基本是用注解@Autowired注解在bean对象上面的了,下面的东西不用太在意

构造方法(常用)

bean1.jpg

静态工厂(了解)

bean2.jpg

实例工厂(了解)

bean3.png

alt+insert创建构造方法

查看接口的层级,选中接口->Navigator->Hierachy

bean的生命周期

bean6.jpg

setter注入

引用类型

bean7.jpg

简单类型

bean8.jpg

构造器注入

111.jpg

type属性和index属性

222.jpg

自动装配

把原来的<property name = "bookDao" ref = "bookDao" />注释掉

1111.jpg

注意,自动装配不能对简单类型操作

111.jpg

数据源对象管理

222.jpg

333.jpg 方式二中,相对路径不行的话,可以使用绝对路径

444.jpg

获取Bean

555.jpg

注解开发定义Bean

666.jpg

@Component中如果没有指定名称,需要通过指定类名来传递,如下面的BookService.class

888.jpg

再来看一个例子

@Component("fooFormatter")
    public class FooFormatter implements Formatter {
        public String format() {
            return "foo";
        }
    }

    @Component("barFormatter")
    public class BarFormatter implements Formatter {
        public String format() {
            return "bar";
        }
    }

    @Component
    public class FooService {
        @Autowired
        private Formatter formatter;
        
        //todo 
    }

上面的例子会抛出异常NoUniqueBeanDefinitionException,应该加上@Qualifier,如下

@Component
    public class FooService {
        @Autowired
        @Qualifier("fooFormatter")
        private Formatter formatter;
        //todo 
    }

或者也可以在Formatter实现类上使用@Qualifier注释,而不是在@Component 或者@Bean 中指定名称,也能达到相同的效果

@Component
     @Qualifier("fooFormatter")
     public class FooFormatter implements Formatter {
         public String format() {
             return "foo";
         }
     }
 
     @Component
     @Qualifier("barFormatter")
     public class BarFormatter implements Formatter {
         public String format() {
             return "bar";
         }
     }

一些常用的衍生注解

777.jpg

纯注解开发

999.jpg

1. @Service 注解

@Service 是Spring框架提供的一个注解,用于标记类为业务逻辑层的组件。当类上标注了@Service注解后,Spring容器会自动扫描并创建该类的一个实例(即Bean),这样我们就可以在其他地方通过自动装配(Autowired)的方式注入这个Bean。

示例代码: 假设我们有一个用户服务类UserService,它包含了一些与用户相关的业务逻辑方法。

import org.springframework.stereotype.Service;

@Service
public class UserService {

    // 假设有一个userRepository用于数据库操作
    private final UserRepository userRepository;

    // 通过构造器注入UserRepository
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User findUserById(Long id) {
        return userRepository.findById(id)
                             .orElseThrow(() -> new UserNotFoundException("User with id " + id + " not found"));
    }
    
    // 其他业务逻辑方法...
}

在上面的代码中,UserService类上标注了@Service注解,这意味着Spring容器会管理它的生命周期,并且我们可以在其他地方通过@Autowired来注入这个服务。

2. @Mapper 注解(通常与MyBatis一起使用)

@Mapper注解通常不是Spring框架的一部分,而是MyBatis框架提供的。在Spring Boot项目中,如果集成了MyBatis,那么@Mapper注解用于标记接口,使得接口可以被MyBatis扫描到并生成对应的代理实现类。这样我们就可以通过这个接口来调用数据库的操作。

示例代码: 假设我们有一个用户映射器接口UserMapper,它定义了与数据库交互的方法。

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Optional;

@Mapper
public interface UserMapper {
    
    @Select("SELECT * FROM users WHERE id = #{id}")
    Optional<User> findById(Long id);
    
    // 其他数据库操作方法...
}

在上面的代码中,UserMapper接口上标注了@Mapper注解,并且方法上使用了MyBatis提供的注解如@Select来定义SQL查询。这样,MyBatis会为这个接口生成一个实现类,我们可以在服务类中注入这个映射器接口来调用数据库操作。

注意:在某些配置中,如果你已经在启动类或配置类上使用了@MapperScan注解来指定扫描的包路径,那么映射器接口上的@Mapper注解可以省略。例如:

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.demo.mapper") // 指定扫描的包路径
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

在这个例子中,所有位于com.example.demo.mapper包下的接口都会被MyBatis扫描并处理,无需在每个接口上单独使用@Mapper注解

备注: @Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName自动注入罢了。@Resource有两个属性是比较重要的,分是name和type。

两种加载初始化容器

99.jpg

  • 跟上面的init-method和destroy-method有点像
  • 下面的@Scope("singleton")指的是bean单例
  • 改成@Scope("propertype")就是多例 11.jpg

注意看小字部分 111.jpg

222.jpg

值注入

333.jpg

注解开发管理第三方bean

使用外部文件properties加载,用PropertySource注入

不支持使用通配符,不可以写成classpath*:jdbc.properties,只能是classpath:jdbc.properties

444.jpg

555.jpg

注解开发实现第三方bean注入资源

11.jpg

22.jpg

33.jpg

一般会使用独立的配置类管理第三方bean 44.jpg

Import版本 55.jpg

扫描版本 66.jpg

如果注释掉注解,那将会检测不到需要注入的资源而报错

77.jpg

88.jpg

99.jpg

注解开发总结

1010.jpg

Spring整合MyBatis

  1. 第一步:导入相关坐标,版本问题可以看看网上怎么配置的,配置坐标的文件在pom.xml

27-1.jpg

  1. 第二步:在需要注入的方法上面标上Bean标签 27-2.jpg

  2. 第三步:黄色部分是包,蓝色部分是数据源,这部分在另外的配置文件xml里面 27-3.jpg

  3. 第四步:还有映射文件的部分,Mapper.XML里面有SQL语句 27-4.jpg

Spring整合JUnit

  • 下面划红线的部分必须是classes,跟以往不同,以往是value,这里如果不加classes = 会报错 11.jpg

  • 使用Spring整合Junit专用的类加载器

    • @RunWith
    • ContextConfiguration
  • @ContextConfiguration这个注解通常与@RunWith(SpringJUnit4ClassRunner.class)联合使用用来测试 22.jpg

AOP面向切面开发

核心概念

AOP1.jpg

基本流程

AOP2.jpg

  • 注解@Pointcut标注好切入点是哪个方法
  • 注解@Before标注好method方法在切入点之前执行 AOP3.jpg
  • 定义通知类有两个重要的标签 AOP4.jpg

AOP开发流程总结

  1. 第一步 入门1.jpg
  2. 第二步 入门2.jpg
  3. 第三步 入门3.jpg
  4. 第四步 入门4.jpg
  5. 第五步,注意红色字的部分 入门5.jpg
  6. 第六步:@Component定义通知类受Spring容器管理,@Aspect定义当前类为切面类 入门6.jpg
  7. 第七步:@Configuration定义该类为配置类,@EnableAspectJAutoProxy开启Spring对AOP注解驱动支持 入门7.jpg

AOP相关包的导入

如果导入了spring-context,aop默认自动导入,无需额外导入。 AOP导入.jpg

通过打印Bean对象的类,得到,AOP方式开发是用代理的方式 代理.jpg

切入点.jpg

AOP切入点表达式

11.jpg

22.jpg

33.jpg

44.jpg

@Around括号里面写类的方法,有两种形式,一种是只写方法,另外一种是类.方法名()55.jpg

下面说的原始方法指的时方法pt()里面的内容 66.jpg

  • 当打印时,不知道打哪种方法时,用Signature

111.jpg

AOP获取数据(参数、返回值、异常信息)

222.jpg

目前我们写AOP仅仅是在原始方法前后追加一些操作,接下来我们要说说AOP中数据相关的内容,我们将从获取参数、获取返回值和获取异常三个方面来研究切入点的相关信息。 前面我们介绍通知类型的时候总共讲了五种,那么对于这五种类型都会有参数,返回值和异常吗?

我们先来逐一分析下:

  • 获取切入点方法的参数,所有的通知类型都可以获取参数

    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
    • ProceedingJoinPoint:适用于环绕通知
  • 获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究

    • 返回后通知
    • 环绕通知
  • 获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究

    • 抛出异常后通知
    • 环绕通知

获取参数

非环绕通知获取方式

在方法上添加JoinPoint,通过JoinPoint来获取参数

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.yolo.dao.BookDao.findName(..))")
    public void pt(){}

    @Before("pt()")
    public void before(JoinPoint jp){
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("before advice ...");
    }
}

运行App类,可以获取如下内容,说明参数100已经被获取

image.png

思考:方法的参数只有一个,为什么获取的是一个数组?

  • 因为参数的个数是不固定的,所以使用数组更通配些。
  • 如果将参数改成两个会是什么效果呢?

修改BookDao和BookDaoImpl类

public interface BookDao {
    String findName(int id, String password);
}

@Repository
public class BookDaoImpl implements BookDao {
    @Override
    public String findName(int id, String password) {
        System.out.println("id:" + id);
        //返回一个固定值
        return "TestName";
    }
}

修改App类,调用方法传入多个参数

public class App {
    public static void main(String[] args) {
        ApplicationContext  ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        String name = bookDao.findName(100, "rick666");
        System.out.println(name);
    }
}

输出结果如下,两个参数都已经被获取到

image.png @After与@Before获取参数的方式相同,不再赘述

环绕通知获取方式

环绕通知使用的是ProceedingJoinPoint,因为ProceedingJoinPoint是JoinPoint类的子类,所以对于ProceedingJoinPoint类中应该也会有对应的getArgs()方法,我们去验证下

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.yolo.dao.BookDao.findName(..))")
    public void pt(){}
    
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        Object res = pjp.proceed();
        return res;
    }
}

运行App后查看运行结果,说明ProceedingJoinPoint也是可以通过getArgs()获取参数

注意:

pjp.proceed()方法是有两个构造方法,分别是: proceed()

proceed(Object[] object)

proceed相关源码

Object proceed() throws Throwable;

Object proceed(Object[] var1) throws Throwable;

  • 调用无参数的proceed,当原始方法有参数,会在调用的过程中自动传入参数
  • 所以调用这两个方法的任意一个都可以完成功能(且运行结果一致)
  • 但是当需要修改原始方法的参数时,就只能采用带有参数的方法,如下
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.yolo.dao.BookDao.findName(..))")
    public void pt(){}
    
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        //对参数值进行修改
        args[0] = 777;
        Object res = pjp.proceed(args);
        return res;
    }
}

运行程序,输出结果如下

image.png

获取返回值

对于返回值,只有返回后AfterReturing和环绕Around这两个通知类型可以获取,具体如何获取?

环绕通知获取返回值

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.yolo.dao.BookDao.findName(..))")
    public void pt(){}
    
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        //对参数值进行修改
        args[0] = 777;
        Object res = pjp.proceed(args);
        return res;
    }
}

上述代码中,res就是方法的返回值,我们是可以直接获取,不但可以获取,如果需要还可以进行修改。

返回后通知获取返回值

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.yolo.dao.BookDao.findName(..))")
    public void pt(){}
    
    @AfterReturning(value = "pt()", returning = "res")
    public void afterReturning(Object res){
        System.out.println("res = " + res);
        System.out.println("afterReturning advice ...");
    }
}

运行程序,输出如下,成功获取了返回值

几点注意:

参数名的问题

  • 赋给returning的值,必须与Object类型参数名一致,上面的代码中均为res
  • afterReturning方法参数类型的问题
  • 参数类型可以写成String,但是为了能匹配更多的参数类型,建议写成Object类型
  • afterReturning方法参数的顺序问题
  • 如果存在JoinPoint参数,则必须将其放在第一位,否则运行将报错

获取异常

对于获取抛出的异常,只有抛出异常后AfterThrowing和环绕Around这两个通知类型可以获取,具体如何获取?

环绕通知获取异常

这块比较简单,以前我们是抛出异常,现在只需要将异常捕获,就可以获取到原始方法的异常信息了

@Around("pt()")
    public Object around(ProceedingJoinPoint pjp) {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        //对参数值进行修改
        args[0] = 777;
        Object res = null;
        try {
            res = pjp.proceed(args);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return res;
    }

在catch方法中就可以获取到异常,至于获取到异常以后该如何处理,这个就和你的业务需求有关了。

抛出异常后通知获取异常

@AfterThrowing(value = "pt()", throwing = "t")
    public void afterThrowing(Throwable t){
        System.out.println("afterThrowing advice ..." + t);
    }

那现在我们只需要让原始方法抛一个异常来看看效果

@Repository
public class BookDaoImpl implements BookDao {
    @Override
    public String findName(int id, String password) {
        System.out.println("id:" + id);
        //模拟异常
        int i = 1/0;
        //返回一个固定值
        return "TestName";
    }
}

运行程序,输出如下,成功输出了异常

image.png

333.jpg

Spring事务管理

11.jpg

22.jpg

33.jpg

44.jpg

55.jpg

Spring事务角色

66.jpg

77.jpg