Spring 基础

Spring 基础

Spring Framework
-> Spring Boot
-> Spring Cloud


(一)核心模块

  • Spring Core:核心模块,主要提供IoC依赖注入功能
  • Spring Aspects:提供AspectJ的切面支持
  • Spring AOP:AOP支持
  • Spring JDBC:数据库连接
  • Spring JMS:Java消息支持
  • Spring ORM:ORM支持
  • Spring Test:单元测试支持
  • Spring Web:为创建Web应用程序提供支持

    Spring 5中Web模块取消了Portlet,引入了WebFlux

(二)常用Bean注解

  • @Component:允许@ComponentScan扫描到改类,并注入IoC容器

  • @Repository:将DAO层类注入IoC容器

  • @Service:将Service层类注入IoC容器

  • @Controller:将Controller层类注入IoC容器

    @RestController:REST风格的Controller

  • @ComponentScan:指定Bean扫描目录/类

  • @Bean:通用单个Bean配置

  • @Configuration:配置整个类中的Bean


(三)核心概念

Spring 5

【AOP:面向切面编程】

所谓的AOP,简单来说就是将系统中可以复用的某些业务逻辑和系统服务(比如DB的连接和断开、数据库事务、日志……)进行分离,抽出来作为单独的一个切面,当需要使用的时候进行织入


1.通知(Advice)

通知定义了要织入目标对象的逻辑,以及执行时机:

  1. 前置通知(@Before):在目标方法执行前通知

  2. 后置通知(@After):在目标方法执行后通知,此时不关心目标方法是执行成功还是抛出异常

    After = After-returning + After-throwing

  3. 返回通知(@AfterReturning):在目标方法执行成功后通知

  4. 异常通知(@AfterThrowing)::在目标方法抛出异常后通知

  5. 环绕通知(@Around):目标方法被通知包裹,在目标方法执行前和执行后都会通知

    Around
    = Before + After
    = Before + (After-returning + After-throwing)


2.切点(Pointcut)

通过匹配规则查找合适的连接点(Joinpoint),AOP在这些点上织入通知

通知定义了何时执行,切点则定义了在何处执行。


3.切面(Aspect)

通知和切点共同定义了切面是什么,以及在何时、何处执行切面逻辑。

Aspect = Advice + Pointcut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
@Aspect
public class MyAspect{
//..表示当前目录下所有类,(..)表示所有函数
@Around(“execution(public * com.db3.fxxk.controller..*.*(..))”)
public void doAroundFunc(ProceedingJoinPoint pjp) {
//doSomething1()
}

//拦截所有diyAnnotation的注解
@Around(“@annotation(diyAnnotation)”)
public void doAroundAnnotation(ProceedingJoinPoint pjp) {
//doSomething2()
}

}

4.引入

允许对现有的类添加新的方法或属性,对它进行增强。


5.织入

将切面加入到某个流程的前后。


织入的3种时期
  1. 编译期(AspectJ):切面在目标类编译时被织入,这种方式需要特殊的编译器
  2. 类加载期:切面在目标类加载到JVM被织入,这种方式需要特殊的类加载器,可以在目标类被引入应用之前增强目标类的字节码
  3. 运行期(Spring AOP):切面在应用运行的某个时期被织入,一般情况下,在织入切面时,AOP容器会对目标类进行动态代理

动态代理:Spring AOP原理

参考自https://www.cnblogs.com/puyangsky/p/6218925.html

静态代理:用一个类代理另一个类;
缺点:如果被代理类变更了,代理类也需要变更;同时一个代理类只能代理一个类。

AOP的原理是动态代理,Spring AOP同时支持:

(1)JDK动态代理(首选)

代理类需要实现java.lang.reflect.InvocationHandler接口

缺点:
1.JDK动态代理必须要实现InvocationHandler接口,如果没有实现则无法使用;
2.JDK动态代理的原理是反射,众所周知,由于在.invoke()时进行了大量装箱拆箱、调用Native方法时无法内联,也无法进行JIT优化,所以反射的速度非常慢

  • InvocationHandler接口:

    1
    2
    3
    4
    5
    6
    7
    8
    //java.lang.reflect.InvocationHandler   JDK动态代理接口
    public interface InvocationHandler{
    //反射调用被代理类的对应函数
    //@param: Object proxy 被代理类的实例
    //@param: Method method 调用被代理类的对应函数
    //@param: Object[] args 对应函数需要的全部参数
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
    }
  • Proxy.newProxyInstance()方法:

    1
    2
    3
    4
    5
    6
    7
    //java.lang.reflect.Proxy.newProxyInstance()    创建代理类的实例
    //@param: ClassLoader loader 被代理类的类加载器
    //@param: Class<?>[] interfaces 被代理类的接口数组
    //@param: InvocationHandler h 被代理类的反射调用
    public static Object newProxyInstance(ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler h) throws IllegalArgumnetException{}
【JDK动态代理Demo】
  • 父类接口Fruit
1
2
3
public interface Fruit{
public void show();
}
  • 接口实现类Apple

    虽然代理的是接口,但还是需要一个接口实现类来完成具体操作。

1
2
3
4
5
6
public class Apple implements Fruit{
@Override
public void show() {
System.out.println("show() is invoked");
}
}
  • 动态代理类DynamicAgent

    需要实现java.lang.reflect.InvocationHandler接口,通过Proxy.newProxyInstance()创建代理类实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class DynamicAgent{

static class MyHandler implements InvocationHandler{

private Object proxy; //被代理类

public MyHandler(Object proxy) {
this.proxy = proxy;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>>>before invoking");
//真正通过反射调用被代理类的对应方法
Object rst = method.invoke(this.proxy, args);
System.out.println(">>>>after invoking");
return rst;
}
}

//调用Proxy.newInstance生成代理类
public static Object agent(Class interfaceClass, Object proxy){
return Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class[]{interfaceClass},
new MyHandler(proxy));
}

}
  • 测试类DynamicInvokeTest
1
2
3
4
5
6
7
8
9
10
11
12
13
public class DynamicInvokeTest {
public static void main(String[] args) {
//.agent()内部实际是Pxocy.newProxyInstance(),返回代理类实例
Fruit fruit = (Fruit) DynamicAgent.agent(Fruit.class, new Apple());
//注意传入的参数需要是Fruit.class,因为只能代理接口
fruit.show(); //调用代理类的方法会直接跳到invoke
}
}

//输出:
//before invoke
//show() is invoked
//after invoke

(2)CGLIB动态代理

原理:通过实现MethodInterceptor接口,为代理类创建子类进行代理

缺点:由于CGLIB动态代理的原理是为被代理类创建子类,所以如果被代理类是final类型的,无法创建子类,就不能使用CGLIB动态代理,只能使用JDK动态代理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//...
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CGLibAgent implements MethodInterceptor {

private Object proxy;

public Object getInstance(Object proxy) {
this.proxy = proxy;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.proxy.getClass()); //设置父类
enhancer.setCallback(this); //设置回调函数
return enhancer.create(); //创建
}

//此即回调函数
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before invoking");
Object rst = methodProxy.invokeSuper(o, objects);
System.out.println("after invoking");
return rst;
}

public static void main(String[] args) {
CGLibAgent agent = new CGLibAgent();
Apple apple = (Apple) agent.getInstance(new Apple()); //CGLib动态代理可以直接操作类
apple.show();
}
}
//输出:
//before invoking
//show() is invoked
//after invoking

IoC:控制反转】

控制反转是指将原本手动创建对象的控制权,交给Spring容器进行


【实现原理】

IoC实现原理:工厂模式 + 反射(通俗来说就是根据给出的类的全限定名来动态生成对象


【主要步骤】

1.创建IoC容器

基础IoC容器是BeanFactory,在此基础上与网络相关的Bean容器是ApplicationContextApplicationContext继承自BeanFactory而包含BeanFactory


2.Bean创建、管理、初始化、依赖注入

流程:Constructor -> @Autowired -> @PostConstruct -> afterPropertiesSet() -> init-method

  1. Bean Constructor构造函数;

    此时只完成了对象的初始化,即new Object(),而实例化要在init-method中完成

  2. @Autowired依赖注入;

    要想将BeanA注入BeanB,那么就要先生成BeanABeanB,所以@Autowired依赖注入发生在构造函数之后。

  3. 构造器后置处理(初始化判断) —— 如:@PostConstruct修饰非静态void方法

    如果想在对象初始化后完成某些操作,而这些操作需要依赖于其他Bean,这时就不能在构造函数中执行了,而要在依赖注入后执行,所以@PostConstruct@Autowired依赖注入之后。

  4. 实现InitializingBean接口的afterPropertiesSet()方法,在初始化Bean时执行,用于对某个具体Bean进行配置

  5. init-method:类方法开始执行


【单例Bean的循环依赖】

1. 构造函数注入 循环依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class A{
private B b;

//通过构造函数注入
public A(B b){
this.b = b;
}
}

public class B{
private A a;

//通过构造函数注入
public B(A a){
this.a = a;
}
}
  • 通过构造函数注入构成的循环依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖
  • Spring容器将每一个正在创建的Bean标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保留在这个池中,如果在创建Bean过程中发现自己已经在“当前创建Bean池”中,将抛出BeanCurrentlyInCreationException表示循环依赖;创建完毕的Bean将从“当前创建Bean池”中删除
2. setter注入 循环依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class A{
private B b;

//通过setter注入
public void setB(B b){
this.b = b;
}
}

public class B{
private A a;

//通过setter注入
public void setB(A a){
this.a = a;
}
}
  • 通过setter注入是不会造成循环依赖的 —— Spring会递归地扫描一个Bean依赖的所有Bean,直到所有它依赖的Bean都在IoC容器中有实例
  1. 实例化Bean A时,发现依赖Bean B,此时会递归调用ApplicationContext.getBean(),将B扫描到IoC容器进行实例化,并缓存在ObjectFactory

    ObjectFactory会在Bean还没有实例化完成时就提前暴露
    【阿里-政务钉钉-一面】的时候被问到。

  2. 接下来实例化B,发现依赖A,但是递归调用ApplicationContext.getBean()时会首先查询ObjectFactory缓存中是否已经存在A实例,发现A已经在IoC容器中有实例了,此时会结束递归,直接使用该实例

  3. 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
protected <T> T doGetBean(final String name, 
final Class<T> requiredType,
final Object[] args,
boolean typeCheckonly) throws BeansException {

//name可能是别名/FactoryBean,需要提取对应的beanName
final String beanName = transformedBeanName(name);
Object bean;

//首先检查【缓存】或者【实例工厂】中是否有对应的Bean实例:
//因为创建单例Bean的时候会存在依赖注入的情况,
//而在创建依赖的时候为了避免【循环依赖】,
//Spring创建bean的原则是:
//不等bean创建完成就将创建bean的ObjectFactory工厂提早曝光,
//将ObjectFactory加入缓存,
//一旦下个bean创建时需要依赖上一个bean则直接使用缓存ObjectFactory
Object sharedInstance = getSingleton(beanName);
if(sharedInstance != null && args == null) {
if(logger.isDebugEnabled() ) {
if(isSingletonCurrentlyInCreation(beanName) ) {
//...
}else{
//...
}
}
//【缓存中已经存在对应Bean实例,直接返回】
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}else{
//【如果缓存中不存在,则创建对应Bean实例】
//其他逻辑,比如prototype不进行缓存,无法完成依赖注入……
}
}
3. prototype无法依赖注入
  • 对于作用域为prototype的Bean,Spring容器无法完成依赖注入,因为Spring容器不缓存作用域为prototype的Bean,因此无法提前暴露一个创建中的Bean

【Bean生命周期】

  1. 实例化Bean对象:通过Bean的id、name属性进行实例化

  2. 设置对象属性:根据Bean的property属性进行配置

    你可以把这两部理解成一个类加载的过程。

  3. 检查Aware相关接口并设置相关依赖:比如如果实现了BeanFactoryAware接口,实例化Bean时会将BeanFacotry注入Bean

  4. BeanPostProcessor前置处理

  5. 自定义init-method方法

  6. BeanPostProcessor后置处理:AOP在这一步将Advice织入Bean中

  7. 注册必要的Destruction相关回调接口

  8. 使用Bean

  9. 是否实现DisposableBean接口:ApplicationContext被销毁时,会调用注册的Destruction相关方法

  10. 自定义destroy方法


Bean作用域】

Spring 5中的Bean作用域

关键词中文翻译
singleton默认作用域,单个IoC容器内只有一个Bean,在BeanFactory中就可以用
prototype每次请求就创建一个新的Bean,在BeanFactory中就可以用
request在每个HTTP请求中只有一个Bean,在ApplicationContext中才能用
session在每个HTTP请求会话(session)中只有一个Bean,在ApplicationContext中才能用
global-session在所有的HTTP请求会话中都只有一个Bean,在ApplicationContext中才能用

单例模式线程安全吗?【不是,常用ThreadLocal解决】

Spring Bean的单例模式采用的是双重判断加锁的懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//@param: String beanName   get Bean by name
//@param: boolean allowEarlyReference 是否允许提前指向
// true为立即加载,false为延迟加载
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if(singletonObject == null && isSingletonCurrentlyInCreation(beanName)){
synchronized(this.singletonObjects) {
singletonObject = this.singletonObjects.get(beanName);
//双重判断加锁的单例模式:
//首先从缓存中获取bean,如果不存在则加锁,
//再从缓存中获取一次bean,如果不存在则创建。
//【这样双重判断,可以避免在加锁的瞬间bean被创建,导致重复】
if(singletonObject == null && allowEarlyReference){
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if(singletonFactory != null) {
singletonObject = singletonFacotry.getObject();
this.earlySinletonObjects.put(beanName, sinletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletionObject != NULL_OBJECT ? singletonObject : null);
}
  • 需要注意的是 —— Spring的单例模式并不是线程安全的!!!!
  • Spring的单例模式只是说IoC容器内只会有一个Bean实例,但是多个线程操作同一个对象时候,对这个对象的可变变量进行写操作会存在线程安全问题
  • 场景的线程安全解决办法有两个:
  1. 将Bean对象中的变量都定义为不可变的,比如全部定义为final(不太现实)
  2. 将Bean对象中所有的可变变量都保存在一个ThreadLocal成员变量中

(四)DB相关操作

1.JDBC

pom.xml中通过Maven引入JDBC相关的依赖spring-boot-starter-jdbc

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

实现原理——自动配置

  1. DataSourceAutoConfiguration:数据源自动配置
  2. DataSourceTransactionManagerAutoConfiguration:事务管理器自动配置
  3. JdbcTemplateAutoConfiguration:JDBC模板自动配置

(1)配置单数据源

application.yml文件中进行相关的配置:

1
2
3
4
5
6
7
# spring.datasource:数据源相关配置
spring:
datasource:
url: jdbc:mysql://localhost/test
username: user
password: password
driver-class-name: com.mysql.jdbc.Driver

(2)配置多数据源

多数据源的注意事项:

  1. 不同数据源的配置要分开
  2. 编程时(事务、ORM……)要注意使用的是哪个数据源

具体方法:

  1. 通过@Primary注解设置主数据源
  2. 如果多个数据源同等重要,那么就excludeSpring Boot的DataSourceAutoConfigurationDataSourceTransactionManagerAutoConfigurationJdbcTemplateAutoConfiguration自动配置,在代码中通过@Bean@ConfigurationProperties()自主实现不同数据源的配置和使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    //在启动类中exclude数据源、事务和JDBC的自动配置
    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
    DataSourceTransactionManagerAutoConfiguration.class,
    JdbcTemplateAutoConfiguration.class})
    public class MultiDataSourceApplication{...}

    //---------------------------------------

    //数据源1
    @Bean
    @ConfigurationProperties("foo.datasource")
    //foo.datasource是.properties文件中的前缀
    public DataSourceProperties fooDataSourceProperties() {
    return new DataSourceProperties();
    }

    @Bean
    //DataSource
    public DataSource fooDataSource() {
    DataSourceProperties dsp = fooDataSourceProperties();
    return dsp.initializeDataSourceBuilder().build(); //构造器模式
    }

    @Bean
    @Resource
    //DataSourceTransactionManager
    public PlatformTransactionManager fooTrxManager(DataSource fooDataSource) {
    return new DataSourceTransactionManager(fooDataSource);
    }

    //---------------------------------------

    //数据源2同理

(3)连接池

HikariCP:快

Spring Boot 2.x默认使用HikariCP

HikariCP为什么这么快?

  1. 进行了很多字节码级别的优化,很多方法都是通过JavaAssist在编译期动态生成
  2. 做了很多大量的小改进,比如使用无锁集合ConcurrentBag、使用FastStatementList替代ArrayList……

HikariCP常用配置有:

1
2
3
4
5
6
7
8
spring:
datasource:
hikari:
maximumPoolSize: 10
minimumIdel = 10 #最小工作线程数
idleTimeout = 6000000 #空闲时间
connectionTimeout = 30000
maxLifetime = 1800000
Alibaba Druid:监控

实用功能:

  1. 监控
  2. ExceptionSorter,针对主流DB的返回码都有支持
  3. SQL防注入
  4. 密码加密
  5. 众多扩展点,方便定制(比如如果我想知道系统中的每一条SQL,在连接池的层面进行拦截和预处理,是要比在DB或中间件的层面好的)
  6. 内置Logging可以诊断Hack应用行为

配置:

  1. pom.xml中排除HikariCP,引入druid-spring-boot-starter依赖
  2. 将配置写入application.yml,前缀为spring.datasource.druid
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    # Druid配置
    spring:
    datasource:
    password:..... #加密后的密码
    druid:
    initial-size=5 #初始大小
    max-active=5 #最大活跃数
    min-idel=5 #最小工作线程
    #filters
    #config:密码加解密
    #stat:统计功能
    #slf4j:日志
    filters=config,stat,slf4j
    #密码加密
    filter.config.enabled=true
    connection-properties=config.decrypt=true;config.decrypt.key=${public-key}

    test-on-borrow=true #获取连接时检测
    test-on-return=true #归还连接时检测
    test-while-idle=true #处理时检测
    #SQL防注入
    filter:
    wall:
    enabled: true
    config:
    delete-allow: false
    drop-table-allow: false

(4)声明式事务

原理:AOP

事务抽象的核心接口PlatformTransactionManager

1
2
3
4
5
6
7
8
//PlatformTransactionManager
//提交事务
void commit(TransactionStatus status) throws TransactionException;
//回滚事务
void rollback(TransactionStatus status) throws TransactionException;
//获取事务
TransactionStatus getTransaction(
@Nullable TranscationDefinition definition) throws TransactionException;

常见的TranscationDefinition
1.Propagation:传播特性
  1. PROPAGATION_REQUIRED(0):当前有事务就用当前的,没有就用新的,默认级别
  2. PROPAGATION_SUPPORTS(1):事务可有可无,不是必须的
  3. PROPAGATION_MANDATORY(2):一定要有事务,否则抛出异常
  4. PROPAGATION_REQUIRES_NEW(3):无论是否有事务,都新建事务,原事务挂起
  5. PROPAGATION_NOT_SUPPORTED(4):不支持事务,按无事务方式运行
  6. PROPAGATION_NEVER(5):不支持事务,有事务则抛异常
  7. PROPAGATION_NESTED(6):内嵌事务,当前有事务就在当前事务内再新建一个事务,子事务回滚不会影响父事务

2.Isolation:隔离特性

默认-1:完全取决于数据库

  1. ISOLATION_READ_UNCOMMITED(1):读未提交
  2. ISOLATION_READ_COMMITED(2):读已提交,可以防止脏读
  3. ISOLATION_REPEATABLE_READ(3):可重复读,可以防止脏读和不可重复读
  4. ISOLATION_SERIALIZABLE(4):序列化,可以防止脏读、不可重复读和幻读

    值得注意的是MySQLREPEATABLE_READ级别由于Gap锁的存在,就已经可以防止幻读了。


3.其他
  1. Timeout:超时
  2. Read-only status:只读

基于注解使用Spring事务
  1. 在启动类上添加@EnableTransactionManagement注解,开启事务支持
  2. 在需要开启事务的类/方法上添加@Transactional注解

@Transactional事务不生效的场景(自身调用、异常被吃、rollbackFor
  1. 数据库引擎不支持事务:比如MySQL使用的MyISAM引擎,而非InnoDB
  2. @Transactional依赖的Bean没有被Spring管理:
1
2
3
4
5
6
7
8
9
//@Service注解被注释,Spring不会管理OrderServiceImpl类对应的Bean,updateOrder()的事务也就失效了

//@Service
public class OrderServiceImpl implements OrderService{
@Transactional
public void updateOrder(Order order) {
//update order
}
}
  1. @Transactional只能用于public方法,否则事务不会生效;如果想用于非public方法,可以开启AspectJ代理模式。

    我的猜想是:Spring中事务是依靠AOP实现的,其中CGLIB动态代理的原理是创建被代理类的子类,但是非public方法无法被继承,所以无法被代理,导致事务不生效。

  2. 在无事务的方法中调用@Transactional方法,事务不生效:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class OrderServiceImpl implements OrderService{

public void update(Order order) {
updateOrder(order);
}

//事务不生效
@Transactional
public void updateOrder(Order order) {
//update order
}
}
  1. 自身调用:如果同一个类中外部事务调用内部事务,内部事务传播特性为PROPAGATION_REQUIRES_NEW,内部事务不生效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class OrderServiceImpl implements OrderService{

@Transactional
public void update(Order order) {
updateOrder(order);
}

//事务不生效
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateOrder(Order order) {
//update order
}
}

解决方法:将父子事务放在不同的类中
或者在配置中添加<aop:config expose-proxy="true">将代理类暴露,然后通过()(ServiceClassName) AopContext.currentProxy()).functionName()进行调用

  1. 数据源没有配置事务管理器PlatformTransactionManager
  2. @Transactional异常被catch住,没有抛出,导致事务无法回滚,同样会导致事务失效:
1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class OrderServiceImpl implements OrderService{

@Transactional
public void updateOrder(Order order) {
try{
//...
}catch{
//异常被catch导致事务无法回滚,从而导致了事务失效
}
}
}
  1. 在没有配置rollbackFor属性时,抛出的异常类型不是默认回滚的异常类型(即:RuntimeException):
1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class OrderServiceImpl implements OrderService{

@Transactional
public void updateOrder(Order order) {
try{
//...
}catch{
throw new Exception("默认不会回滚的异常类型"); //事务失效
}
}
}

通过@Transactional(rollbackFor = Throwable.class)可以配置回滚的异常类型,只能是Throwable及其异常子类


(5)JDBC异常

原理:AOP

Spring会通过SQLErrorSQLExceptionTranslator对错误码进行解析,然后将数据操作的异常转换为DataAccessException


2.ORM:对象关系映射

将POJO与数据库的表进行相互映射。
当操作比较简单时,选择JPA可以屏蔽SQL层;
如果SQL映射比较复杂,或者希望能够对原生SQL进行优化,或者希望定制SQL,可以选择MyBatis


(五)NoSQL相关操作

Spring Data Redis 之 Jedis

Spring Data Redis支持的客户端有JedisLettuce,提供了RedisTemplateRedis进行操作。

Jedis使用注意事项:

  1. Jedis不是线程安全的,不能在多个线程间共享同一个Jedis实例
  2. 一般会通过JedisPool获取Jedis实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--  spring-boot-starter-parent  -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>

<dependencies>
<!-- Jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>

(六)常见问题

1.@Autowired@Resource的区别

【1】@Resource:默认byName自动注入

  1. 同时指定了@Resource(name = "", type = xxx.class)ApplicationContext找到名称和类型都唯一匹配的Bean进行装配,找不到则抛出异常
  2. 指定了@Resource(name = "")ApplicationContext找名称匹配的Bean进行装配,找不到则抛出异常
  3. 指定了@Resource(type = xxx.class)ApplicationContext找类型匹配的Bean进行装配,找不到则抛出异常
  4. 既没有指定name,也没有指定type默认byName自动注入;如果没有匹配,则回退一个原始类型进行匹配,如果匹配则自动装配

【2】@Autowired:默认byType自动注入

  • @Autowired默认byType自动注入,但是如果ApplicationContext中不止一个同type不同name的Bean,会抛出BeanCreationException,需要配合@Qualifier解决
@Autowired + @Qualifier:解决同typename的情况

@Autowired默认byType,如果不通过@Qualifier进行byName查找,就会由于多个Bean都是同一个类的实例而报错

比如我现在定义了4个不同的Bean,它们都是同一个类型,但是名称不同:

1
2
3
4
5
6
7
<bean id="tempCartUidGenerator" class="com.uid.impl.DefaultUidGenerator" lazy-init="false"></bean>

<bean id="shoppingCartUidGenerator" class="com.uid.impl.DefaultUidGenerator" lazy-init="false"></bean>

<bean id="settleCartUidGenerator" class="com.uid.impl.DefaultUidGenerator" lazy-init="false"></bean>

<bean id="orderUidGenerator" class="com.uid.impl.DefaultUidGenerator" lazy-init="false"></bean>

那么我在通过@Autowired注入这些Bean的时候,就需要通过@Qualifier根据名称进行指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Autowired
@Qualifier(value = "tempCartUidGenerator")
private DefaultUidGenerator tempCartUidGenerator;

@Autowired
@Qualifier(value = "shoppingCartUidGenerator")
private DefaultUidGenerator shoppingCartUidGenerator;

@Autowired
@Qualifier(value = "settleCartUidGenerator")
private DefaultUidGenerator settleCartUidGenerator;

@Autowired
@Qualifier(value = "orderUidGenerator")
private DefaultUidGenerator orderUidGenerator;

2.@ControllerAdvice:全局异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ControllerAdvice
public class MyGlobalExceptionHandler extends BaseExceptionHandler{

@ResponseBody
@ExcetionHandler(AaaException.class)
public ModelAndView handleAaaException(AaaException e) {
ModelAndView mv = new ModelAndView();
mv.addObject(“message”, e.getMessage() );
return mv;
}

@ExcetionHandler(FffException.class)
public ModelAndView handleAaaException(FffException e) {
ModelAndView mv = new ModelAndView();
mv.addObject(“message”, "好大呀!");
return mv;
}

}

3.循环依赖的Bean如何控制初始化顺序?

BeanABeanB循环依赖,现要求BeanABeanB之前完成初始化,比如:先加载全局配置文件,然后再加载局部配置文件。

(1) 立Flag:不够优雅

采用一个boolean标志位记录BeanA是否被初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Service
public class A {

//采用一个标志位记录BeanA是否被初始化
private static volatile boolean initialized;

public boolean isInitialized() {
return initialized;
}

@Autowired
private B b;

public A() {
System.out.println("A Constructor");
}

@PostConstruct
public void initA() {
//判断BeanA是否被初始化,未初始化则先初始化
if (!isInitialized() ) {
System.out.println("A init");
}
initialized = true;
}
}

@Service
public class B {
@Autowired A a;

public B() {
System.out.println("B Constructor");
}

@PostConstruct
public void initB() {
//初始化BeanB前,先判断BeanA是否被初始化
if (!a.initialized) {
a.initA();
}
System.out.println("B init");
}
}

执行效果:

1
2
3
4
A Constructor
B Constructor
A init
B init

(2) Ordered接口:有序监听ApplicationContextEvent事件

  1. 实现ApplicationListener<ApplicationContextEvent>接口和Ordered接口
  2. 重写onApplicationEvent(ApplicationContextEvent e)方法,监听ApplicationContextEvent事件
  3. 重写getOrder()方法,设置事件的优先级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Component
public class AppListenerA implements ApplicationListener<ApplicationContextEvent>, Ordered {

//监听ApplicationContextEvent事件,初始化BeanA
@Override
public void onApplicationEvent(ApplicationContextEvent a) {
//initA();
}

//设置BeanA优先级,比BeanB高
@Override
public void getOrder() {
return Ordered.HIGHEST_PRECEDENCE = 100;
}
}

@Component
public class AppListenerB implements ApplicationListener<ApplicationContextEvent>, Ordered {

//监听ApplicationContextEvent事件,初始化BeanB
@Override
public void onApplicationEvent(ApplicationContextEvent b) {
//initB();
}

//设置BeanB优先级,低
@Override
public void getOrder() {
return Ordered.HIGHEST_PRECEDENCE = -1;
}
}
-------------本文结束感谢您的阅读-------------

本文标题:Spring 基础

文章作者:DragonBaby308

发布时间:2019年08月03日 - 20:22

最后更新:2020年04月17日 - 23:20

原始链接:http://www.dragonbaby308.com/spring/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

急事可以使用右下角的DaoVoice,我绑定了微信会立即回复,否则还是推荐Valine留言喔( ఠൠఠ )ノ
0%