Spring核心总结

发布于:2024-04-22 ⋅ 阅读:(40) ⋅ 点赞:(0)

 要学什么? 

(1)核心层

* Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块

(2)AOP层

* AOP:面向切面编程,它依赖核心层容器,目的是==在不改变原有代码的前提下对其进行功能增强==
* Aspects:AOP是思想,Aspects是对AOP思想的具体实现

(3)数据层

* Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
* Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
* Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容

(4)Web层

* 这一层的内容将在SpringMVC框架具体学习

(5)Test层

* Spring主要整合了Junit来完成单元测试和集成测试

IOC&DI

图解

 Why:

以SpringMVC的web的Service与DAO层代码为例,依赖耦合程度过高。

How:

常见的解耦方式就是有个中间层,将信息交给中间层管。 

IOC(Inversion of Control)控制反转

使用对象时,由主动new产生对象转换为由IOC容器提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。

  • * 业务层要用数据层的类对象,以前是自己`new`的
  • * 现在自己不new了,交给`别人[外部]`来创建对象
  • * `别人[外部]`就反转控制了数据层对象的创建权
  • * 这种思想就是控制反转

【别人(中间层)】IOC容器的作用以及内部存放的是什么?

  • IOC容器负责对象的创建、初始化等一系列工作,其中包含了数据层和业务层的类对象

  • 被创建或被管理的对象在IOC容器中统称为==Bean==

  • IOC容器中放的就是一个个的Bean对象

依赖关系如何保持住? 

DI(Dependence Injection):依赖注入
  • 使用IOC容器管理bean(IOC)

  • 依赖注入,绑定对象与对象之间的依赖关系

  • 在IOC容器内将有依赖关系的bean进行关系绑定(DI)

  • 最终结果为:使用对象时不仅可以直接从IOC容器中获取,并且获取到的bean已经绑定了所有的依赖关系.

IOC核心容器总结
 容器相关
  • BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载

  • ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载

  • ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能

  • ApplicationContext接口常用初始化类

    • ==ClassPathXmlApplicationContext(常用)==

    • FileSystemXmlApplicationContext

 bean相关

其实整个配置中最常用的就两个属性==id==和==class==。

把scope、init-method、destroy-method框起来的原因是,后面注解在讲解的时候还会用到,所以大家对这三个属性关注下。

 依赖注入相关

注解开发总结

  1. @Component: 这是一个通用的注解,用于表明一个类是Spring容器管理的组件。被注解的类将被自动扫描并注册为Spring的bean。

  2. @Controller: 该注解用于标识一个类为Spring MVC控制器。它允许Spring自动检测和自动装配MVC组件。

  3. @Service: 用于标识一个类为业务逻辑层的服务组件。通常在service层中使用,用于表示服务层的bean。

  4. @Repository: 这是一个专用的注解,用于标识一个类为数据访问层的组件,如DAO(数据访问对象)。它对应于特定于数据访问的异常转换。

  5. @Autowired: 该注解用于自动装配Spring bean。Spring容器会自动查找合适的bean进行注入,无需显式地配置。

  6. @Qualifier: 当有多个相同类型的bean时,@Qualifier注解可与@Autowired一起使用,指定要注入的bean的名称。

  7. @Value: 该注解用于注入外部属性值到Spring bean的字段、构造函数或方法中。

  8. @RequestMapping: 在Spring MVC中,这个注解用于映射web请求到特定的处理方法或控制器类。

  9. @RestController: 这是一个组合注解,相当于@Controller和@ResponseBody的组合。它用于创建RESTful web服务的控制器。

  10. @PathVariable: 该注解用于从URL模板中获取参数值。

  11. @RequestBody: 用于将HTTP请求体映射到处理方法的参数上,适用于接收JSON、XML等格式的请求数据。

  12. @ResponseBody: 该注解用于将处理方法的返回值直接写入HTTP响应体。

  13. @PostMapping@GetMapping@PutMapping@DeleteMapping:用于指定处理HTTP POST、GET、PUT、DELETE请求的方法。

  14. @Transactional:用于声明事务管理,标识一个方法应该在事务中执行。

 AOP

AOP(Aspect Oriented Programming)面向切面编程,在不惊动原始设计的基础上为其进行功能增强,前面咱们有技术就可以实现这样的功能即代理模式。Java设计模式——代理模式-CSDN博客

 AOP是在不改原有代码的前提下对其进行增强

基础概念

  1. 连接点(Join Point): 连接点是在应用程序执行期间可以插入切面的点。在Spring中,连接点通常是方法调用,尽管它也可以是某些特定的程序执行点,比如异常处理或字段访问。

  2. 切入点(Pointcut): 切入点是一组连接点的集合,其中每个连接点都符合特定的条件。切入点定义了在应用程序中哪些连接点应该被拦截,并应用相应的通知。

  3. 通知(Advice): 通知是在切面的特定连接点上执行的代码。在Spring AOP中,有五种类型的通知:

    • 前置通知(Before Advice):在连接点之前执行。
    • 后置通知(After Advice):在连接点之后执行。
    • 返回通知(After Returning Advice):在连接点正常完成后执行。
    • 异常通知(After Throwing Advice):在连接点抛出异常后执行。
    • 环绕通知(Around Advice):在连接点前后都执行。
  4. 通知类(Advisor): 通知类是将通知和切入点组合在一起的对象。在Spring中,通知类通常是由一个切面定义的,它包含一个或多个通知和一个切入点。

  5. 切面(Aspect): 切面是通知和切入点的组合。它描述了在何处以及何时应用通知。在Spring中,切面通常是一个带有通知方法的普通类,并且使用注解或XML配置来定义切入点和通知。

  AOP实现步骤
步骤1:添加依赖:

aspectjweaver、springframework

步骤2:定义接口与实现类

步骤3:定义通知类和通知

public class MyAdvice {
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

步骤4:定义切入点

public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

步骤5:制作切面

public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    
    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

切面是用来描述通知和切入点之间的关系

步骤6:将通知类配给容器并标识其为切面类

@Aspect

public class MyAdvice {

}

步骤7:开启注解格式AOP功能

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

 AOP工作流程

由于AOP是基于Spring容器管理的bean做的增强,所以整个工作过程需要从Spring加载bean说起:

流程1:Spring容器启动
  • * 容器启动就需要去加载bean,哪些类需要被加载呢?
  • * 需要被增强的类,如:BookServiceImpl
  • * 通知类,如:MyAdvice
  • * 注意此时bean对象还没有创建成功

 流程2:读取所有切面配置中的切入点

流程3:初始化bean

判定bean对应的类中的方法是否匹配到任意切入点

  • * 注意第1步在容器启动的时候,bean对象还没有被创建成功。
  • * 要被实例化bean对象的类中的方法和切入点进行匹配

匹配失败,创建原始对象,如`UserDao`

  •      匹配失败说明不需要增强,直接调用原始对象的方法即可。

匹配成功,创建原始对象的代理对象,如:`BookDao`

  •   * 匹配成功说明需要对其进行增强
  •   * 对哪个类做增强,这个类对应的对象就叫做目标对象
  •   * 因为要对目标对象进行功能增强,而采用的技术是动态代理,会为其创建一个代理对象
  •   * 最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强
流程4:获取bean执行方法
  •  * 获取的bean是原始对象时,调用方法并执行,完成操作
  • * 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

 验证容器中是否为代理对象

为了验证IOC容器中创建的对象和我们刚才所说的结论是否一致,首先先把结论理出来:

  • * 如果目标对象中的方法会被增强,那么容器中将存入的是目标对象的代理对象
  • * 如果目标对象中的方法不被增强,那么容器中将存入的是目标对象本身。
  • 当前类的getClass()方法
 AOP配置管理
切入点表达式:

要进行增强的方法的描述方式
execution(public User com.itheima.service.UserService.findById(int))

  • * execution:动作关键字,描述切入点的行为动作,execution表示执行到指定切入点
  • * public:访问修饰符,还可以是public,private等,可以省略
  • * User:返回值,写返回值类型
  • * com.itheima.service:包名,多级包使用点连接
  • * UserService:类/接口名称
  • * findById:方法名
  • * int:参数,直接写参数的类型,多个类型用逗号隔开
  • * 异常名:方法定义中抛出指定异常,可以省略
 通配符

 `*`:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
  execution(public * com.itheima.*.UserService.find*(*))

  匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

 `..`多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
  execution(public User com..UserService.findById(..))

  匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

 `+`专用于匹配子类类型
  execution(* *..*Service+.*(..))

  这个使用率较低,描述子类的,咱们做JavaEE开发,继承机会就一次,使用都很慎重,所以很少用它。*Service+,表示所有以Service结尾的接口的子类。

AOP通知类型 
  • 前置通知

  • 后置通知

  • ==环绕通知(重点)==

  • 返回后通知(了解)

  • 抛出异常后通知(了解)

 环绕通知

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    
    @Around("pt()")
    public void around(){
        System.out.println("around before advice ...");
        System.out.println("around after advice ...");
    }
}

ProceedingJoinPoint pjp

 获取原方法、设置返回值类型

@Around("pt2()")
    public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        System.out.println("around after advice ...");
        return ret;
    }

 获取方法名、接口名和签名信息

@Component
@Aspect
public class ProjectAdvice {
    //配置业务层的所有方法
    @Pointcut("execution(* com.itheima.service.*Service.*(..))")
    private void servicePt(){}
    //@Around("ProjectAdvice.servicePt()") 可以简写为下面的方式
    @Around("servicePt()")
    public void runSpeed(ProceedingJoinPoint pjp){
        //获取执行签名信息
        Signature signature = pjp.getSignature();
        //通过签名获取执行操作名称(接口名)
        String className = signature.getDeclaringTypeName();
        //通过签名获取执行操作名称(方法名)
        String methodName = signature.getName();

        
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
           pjp.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
    } 
}

通知获取数据

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

  •   * JoinPoint:适用于前置、后置、返回后、抛出异常后通知
  •   * ProceedingJoinPoint:适用于环绕通知
    @Before("pt()")
    public void before(JoinPoint jp) 
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("before advice ..." );
    }
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp)throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        Object ret = pjp.proceed();
        return ret;
    }

* 获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究

  •   * 返回后通知
  •   * 环绕通知
    @AfterReturning(value = "pt()",returning = "ret")
    public void afterReturning(Object ret) {
        System.out.println("afterReturning advice ..."+ret);
    }

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;
        Object ret = pjp.proceed(args);
        return ret;
    }

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

  •   * 抛出异常后通知
  •   * 环绕通知
    @AfterThrowing(value = "pt()",throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("afterThrowing advice ..."+t);
    }
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;
        Object ret = null;
        try{
            ret = pjp.proceed(args);
        }catch(Throwable throwable){
            t.printStackTrace();
        }
        return ret;
    }
 环绕通知注意事项
  1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
  2.  通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
  3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为Object类型
  4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
  5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常  

 Spring事务

 基础概念 

- 事务作用:在数据层保障一系列的数据库操作同成功同失败
- Spring事务作用:在数据层或**==业务层==**保障一系列的数据库操作同成功同失败

事务管理实现步骤 
步骤1:在需要被事务管理的方法上添加注解
public interface AccountService {
    /**
     * 转账操作
     * @param out 传出方
     * @param in 转入方
     * @param money 金额
     */
    //配置当前接口方法具有事务
    public void transfer(String out,String in ,Double money) ;
}
​
@Service
public class AccountServiceImpl implements AccountService {
​
    @Autowired
    private AccountDao accountDao;
    @Transactional
    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        int i = 1/0;
        accountDao.inMoney(in,money);
    }
​
}

==注意:==

@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上

  • 写在接口类上,该接口的所有实现类的所有方法都会有事务

  • 写在接口方法上,该接口的所有实现类的该方法都会有事务

  • 写在实现类上,该类中的所有方法都会有事务

  • 写在实现类方法上,该方法上有事务

  • ==建议写在实现类或实现类的方法上==

 步骤2:在JdbcConfig类中配置事务管理器
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }

    //配置事务管理器,mybatis使用的是jdbc事务
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

 注意 :事务管理器要根据使用技术进行选择,Mybatis框架使用的是JDBC事务,可以直接使用`DataSourceTransactionManager`

步骤3:开启事务注解
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

 Spring事务角色

这节中我们重点要理解两个概念,分别是事务管理员事务协调员

- 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法

    目前的事务管理是基于DataSourceTransactionManagerSqlSessionFactoryBean使用的是同一个数据源

 * AccountDao的outMoney因为是修改操作,会开启一个事务T1
* AccountDao的inMoney因为是修改操作,会开启一个事务T2
* AccountService的transfer没有事务,

  •   * 运行过程中如果没有抛出异常,则T1和T2都正常提交,数据正确
  •   * 如果在两个方法中间抛出异常,T1因为执行成功提交事务,T2因为抛异常不会被执行
  •   * 就会导致数据出现错误

  • transfer上添加了@Transactional注解,在该方法上就会有一个事务T

  • AccountDao的outMoney方法的事务T1加入到transfer的事务T中

  • AccountDao的inMoney方法的事务T2加入到transfer的事务T中

  • 这样就保证他们在同一个事务中,当业务层中出现异常,整个事务就会回滚,保证数据的准确性。

 Spring事务属性

在这一节中,我们主要学习三部分内容`事务配置`、`转账业务追加日志`、`事务传播行为`

事务配置

  • * readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
  • * timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。
  • * rollbackFor:当出现指定异常进行事务回滚

Spring的事务只会对`Error异常`和`RuntimeException异常`及其子类进行事务回滚,其他的异常类型是不会回滚的,对应IOException不符合上述条件所以不回滚

 需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕

==无论转账操作是否成功,均进行转账操作的日志留痕==

  • 失败原因:日志的记录与转账操作隶属同一个事务,同成功同失败

事务传播行为

@Transactional(propagation = Propagation.REQUIRES_NEW)


网站公告

今日签到

点亮在社区的每一天
去签到