Spring基本配置和特性

291 阅读9分钟

spring最早是由Rod Johnson创建的,它的根本使命就是解决企业级应用开发的复杂性,即简化Java开发的复杂性。

一. 装配bean的三种方式

1.1 通过注解【推荐使用,自定义的类直接使用Component注解】

具体实现:在每个javabean上添加一个注解@Component,即可将其交给IOC容器进行管理。在需要使用的地方,我们使用@Autowired注解即可注入进来。

@Data
@Component   //关键注解
public class User implements Serializable {
private int id;
private String name;
private int age;
private String description;


}

注意:使用注解方式这种方式,必须要先开启注解的支持,只需要在spring中创建一个配置类,并在配置类上添加一个@ComponentScan注解扫描有注解的所在包即可。 如下1.2所示

1.2 通过配置类【推荐使用,使用别人写好的类,就通过@Bean方式注入】

@Configuration //表明是一个配置类,可以代替spring的xml配置文件,推荐使用!
@ComponentScan(basePackages = {"com.ljy"})  //开启注解扫描
public class MyConfigg {
 @Bean
 public User getUser() throws InterruptedException {
   System.out.println("开始创建user对象.............");
   TimeUnit.SECONDS.sleep(3);
   User user = new User();
   user.setAge(10);
   user.setName("吕建友");
   System.out.println("创建对象成功..........");
   return user;
 }
}

1.3 通过xml文件装配【不推荐使用】

<bean id="user" class="com.ljy.pojo.User">

二. SpringAOP的配置方式【以纯注解举例】

2.1 开启AOP

在主配置类上加上如下注解,表示开启AOP

@Configuration        //表明这是一个配置类
@EnableAspectJAutoProxy    //表明开启AOP的支持,必须!!!
@ComponentScan(basePackages = {"com.ljy"})   //开启注解,并配置注解扫描的包
public class MyConfig {

//注册Bean
 @Bean(name = "user")
 public User getUser() throws InterruptedException {
   return new User;
 }
}

2.2 创建一个切面

@Aspect   //表明是一个切面
@Component  //表明是spring的一个组件,必须!
public class TestAOP {

 //定制切入点,切面需要切入的地方
 @Pointcut(value = "execution(* com.ljy.service.*.*(..))")
 private void anyMethod(){}

 @Before("anyMethod()")   
 public void beforeAdvice(){
   System.out.println("============前置通知===================");
 }

 @After("anyMethod()")
 public void afterAdvice(){
   System.out.println("===========后置通知===============");
 }


@AfterReturning("anyMethod()")
public void afterReturning(){
 System.out.println("=========后置返回通知===========");
}

@AfterThrowing("anyMethod()")
public void afterThrowing(){
 System.out.println("=========异常返回通知=============");
}
// 环绕通知
@Around("anyMethod()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
 Object[] args = pjp.getArgs();
 for (Object arg : args) {
   System.out.println(arg);
 }
 String name = pjp.getSignature().getName();
 Object proceed=null;
 try {
   //@Before
   System.out.println("【环绕前置通知】【"+name+"方法开始】");
   /*这句相当于method.invoke(obj,args),通过反射来执行接口中的方法;因此在反射执行完方法后会有一个返回值proceed*/
   proceed = pjp.proceed();
   //@AfterReturning
   System.out.println("【环绕返回通知】【"+name+"方法返回,返回值:"+proceed+"】");
 } catch (Exception e) {
   //@AfterThrowing
   System.out.println("【环绕异常通知】【"+name+"方法异常,异常信息:"+e+"】");
 }finally{
   //@After
   System.out.println("【环绕后置通知】【"+name+"方法结束】");
 }
 return proceed;
}
}

切面一共有五种通知类型,分别再什么情况下执行?我们应该看注解名字就可以猜出来了吧!方法执行前,方法执行后,方法正常结束后,方法抛出异常后...还有一种是最为强大的环绕通知。

@Around环绕通知:
它集成了@Before、@AfterReturing、@AfterThrowing、@After四大通知。需要注意的是,他和其他四大通知注解最大的不同是需要手动进行接口内方法的反射后才能执行接口中的方法,换言之,@Around其实就是一个动态代理。

在环绕通知中,大家注意到有一个参数ProceedingJoinPoint pjp,在pjp中包含了接口中方法的信息,可以通过pjp来获取到接口中的方法信息(如方法名,方法的参数,方法返回值等信息。)

切入点表达式

关键字:execution(表达式)
表达式: 访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)

标准的表达式写法:
       public void com.ljy.service.impl.AccountServiceImpl.saveAccount()
       
访问修饰符可以省略
       void com.ljy.service.impl.AccountServiceImpl.saveAccount()
       
返回值可以使用通配符,表示任意返回值
       * com.ljy.service.impl.AccountServiceImpl.saveAccount()
       
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
       * *.*.*.*.AccountServiceImpl.saveAccount()
       
包名可以使用..表示当前包及其子包
        * *..AccountServiceImpl.saveAccount()
        
类名和方法名都可以使用*来实现通配
                    * *..*.*()
                参数列表:
                    可以直接写数据类型:
                        基本类型直接写名称           int
                        引用类型写包名.类名的方式   java.lang.String
                    可以使用通配符表示任意类型,但是必须有参数
                    可以使用..表示有无参数均可,有参数可以是任意类型
                全通配写法:
                    * *..*.*(..)
                实际开发中切入点表达式的通常写法:
                    切到业务层实现类下的所有方法
                        * com.itheima.service.impl.*.*(..)**

三. spring事务

spring的事务还是依赖于数据库的事务来存在的。可以说,没有数据库的事务操作,就没有spring的事务存在。

什么是事务? 事务就是一组操作,一个具体的业务,这组操作要么同时成功,要么同时失败,如果在中间出错了话那就需要对已经执行了的操作进行rollback回滚。从数据库层面来看的话就是一组组的CRUD语句,只能同时成功,或者同时失败。

我们来回顾一下关于数据库的一些基本知识。

对数据库中的数据进行操作时可能会引发那些问题?

1. 脏读

事务A读取到事务B已经修改,但是还没有提交的数据,就叫读到了脏数据。

2. 幻读

事务A在执行批量修改或者查询的过程中,事务B新增加了一条数据,这条数据刚好满足事务A执行修改或者查询的条件;这时候在事务A执行完修改或查询以后,重新查看,发现竟然还有一条数据没有修改!这就是“幻读”的现象。

3. 不可重复读

事务A连续执行了两次读的操作,在两次操作的中间事务B对其中的数据进行了一个修改,导致了事务A两次读取出来的数据不一致,这就是不可重复读!

有的同学可能会搞混幻读和不可重复读,这两个比较相似,但是注意:幻读重点在另一个事务进行了insert或者delete,但是不可重复读主要是另一个事务进行了update。

数据库的隔离机制有哪些?

1. 读未提交

写数据的时候添加一个X锁(排他锁),也就是在写数据的时候不允许其他事务进行写操作,但是读不受限制,读不加锁。

可以解决数据丢失的问题,但是不能解决“脏数据”。

2. 读已提交

写数据的时候加上X锁(排他锁),读数据的时候添加S锁(共享锁),而且有约定:如果一个数据加了X锁就没法加S锁;同理如果加了S锁就没法加X锁,但是一个数据可以同时存在多个S锁(因为只是读数据),并且规定S锁读取数据,一旦读取完成就立刻释放S锁(不管后续是否还有很多其他的操作,只要是读取了S锁的数据后,就立刻释放S锁)。

解决了脏读,但是不能解决“不可重复读”。

3. 可重复读

对S锁进行修改,之前的S锁是:读取了数据之后就立刻释放S锁,现在修改是:在读取数据的时候加上S锁,但是要直到事务准备提交了才释放该S锁,X锁还是一致。

解决了不可重复读,但是不能解决“幻读”。

4. 串行化

事务只能一件一件的执行,不能并发执行。

可以解决脏读,幻读,不可重复读的问题。

3.1 spring使用事务

分别编程式事务和声明式事务。说得通俗易懂也就是自己手写事务,和直接使用spring定义好的注解的区别。

3.1.1 编程式事务
3.1.2 声明式事务

很简单,直接在方法上我们添加一个注解即可。

@Transactional
public void methodA(){
 System.out.println("这是一组事务操作....");
 System.out.println("插入数据...");
 System.out.println("修改数据....");
 System.out.println("删除数据....");
}

这个时候,这个方法中的业务就是具有“原子性”了!如果中间出现了异常,所有操作将会进行回滚。

3.2 Spring事务的传播机制(七种)

事务的传播机制是什么意思?传播传播,说明起码需要两个事务之间才能互相传播吧!试想这样一种条件:

@Transactional    //开启事务
public void methodA(){
 methodA();
}

@Transactional
public void methodB(){
 methodA();
}

所谓事务传播机制,也就是在事务在多个方法的调用中是如何传递的,是重新创建事务还是使用父方法的事务?父方法的回滚对子方法的事务是否有影响?这些都是可以通过事务传播机制来决定的。

  1. PROPAGATION_REQUIRED 【传播——必须的】 看名字就知道,这种情况下,代码必须要在事务中才能运行。
    它的机制是:若外层方法存在事务,则加入该事务,若不存在事务,则新建一个事务。

  2. PAOPAGATION_REQUIRE_NEW【传播-需要新的】 不管外层方法是否存在事务,都创建一个新的事务,原来的方法先挂起,新的方法执行完毕后,继续执行老的事务

  3. PAOPAGATION_SUPPORTS【传播-支持事务】 如果外层方法中存在事务,那么就使用外层方法的事务,如果外层方法没有事务,那么就不使用事务!

  4. PAOPAGATION_NOT_SUPPORTED【传播-不支持事务】 不支持事务,那就是不能用事务嘛!spring不为当前的方法开启事务,也就是没有事务。

  5. PROPAGATION_MANDATORY【传播-强制性的】 必须要有事务执行,如果外层方法没有事务的话,直接报错。

  6. PROPAGATION_NEVER【传播-绝不】 绝不允许以事务的方式执行,如果当前存在事务的话,那么就直接报错。

  7. PROPAGATION_NESTED【传播-嵌套的】 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作,新建一个事务来执行。

我们发现了一个规律:1.2 ===3.4 === 5.6都是互相对应的,因此也比较方便我们记忆。

Spring常见面试题

1. Spring Bean的生命周期是什么?

  1. 实例化
  2. 填充属性
  3. 调用BeanNameAware的setBeanName()方法。
  4. 调用BeanFactoryAware的setBeanFactory方法,
  5. 调用ApplicationContextAware的setApplicationContext方法
  6. 调用BeanPostProcessor的预初始化方法
  7. 调用InitialzingBean的afterPropertiesSet方法
  8. 调用自定义的初始化方法
  9. 调用BeanPostProcessor的初始化后方法
  10. Bean可以使用了,存在于应用上下文中
  11. 调用DisableBean的destory方法
  12. 调用自定义的销毁方法。

2. spring中Bean的作用域有哪些?

Spring中的bean的作用域有哪些?

1.singleton:唯一bean实例,Spring中的bean默认都是单例的。

2.prototype:每次请求都会创建一个新的bean实例。

3.request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。

4.session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。

5.global-session:全局session作用域。