Spring介绍
狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。
Spring 框架是一个分层的、面向切面的 Java 应用程序的一站式轻量级解决方案,它是 Spring 技术栈的核心和基础,是为了解决企业级应用开发的复杂性而创建的。
Spring 有两个核心部分: IoC 和 AOP。
组成:Spring AOP、Spring ORM、Spring Web、Spring Web MVC、Spring DAO
Spring IoC
示例
创建Bean
Spring创建Bean有多种方式,代码编写形式上早期以*.xml文件的形式编写,现在使用注解的形式。
无参构造方法创建Bean(xml形式)
项目中大多数Bean是使用这种方式创建的
<bean id=“orderService" class="cn.itcast.OrderServiceBean"/>
静态工厂方法创建Bean(xml形式)
<bean id="personService" class="cn.itcast.service.OrderFactory" factory-method="createOrder"/>
Java代码:
public class OrderFactory {
public static OrderServiceBean createOrder(){
return new OrderServiceBean();
}
}
实例工厂方法创建Bean(xml形式)
首先要实例化工厂
<bean id="personServiceFactory" class="cn.itcast.service.OrderFactory"/>
<bean id="personService" factory-bean="personServiceFactory" factory-method="createOrder"/>
Java代码:
public class OrderFactory {
public OrderServiceBean createOrder(){
return new OrderServiceBean();
}
}
很明显,这三种方式最根本的区别还是创建方式的不同。
第一种,通过默认的无参构造方式创建,其本质就是把类交给Spring自带的工厂(BeanFactory)管理、由Spring自带的工厂模式帮我们维护和创建这个类。如果是有参的构造方法,也可以通过XML配置传入相应的初始化参数,这种也是开发中用的最多的。
第二种,通过静态工厂创建,其本质就是把类交给我们自己的静态工厂管理,Spring只是帮我们调用了静态工厂创建实例的方法,而创建实例的这个过程是由我们自己的静态工厂实现的,在实际开发的过程中,很多时候我们需要使用到第三方jar包提供给我们的类,而这个类没有无参构造方法,而是通过第三方包提供的静态工厂创建的,这是时候,如果我们想把第三方jar里面的这个类交由spring来管理的话,就可以使用Spring提供的静态工厂创建实例的配置。
第三种,通过实例工厂创建,其本质就是把创建实例的工厂类交由Spring管理,同时把调用工厂类的方法创建实例的这个过程也交由Spring管理,看创建实例的这个过程也是有我们自己配置的实例工厂内部实现的。在实际开发的过程中,如Spring整合Hibernate就是通过这种方式实现的。但对于没有与Spring整合过的工厂类,我们一般都是自己用代码来管理的。
配置文件+注解形式
xml文件指定扫描包,注解标记要生成Bean的类
项目内创建Bean直接使用无参构造@Controller+@Service+@Repository+@Component
引入第三方工具,想要创建Bean:类上@Configuration,方法上使用@Bean,其中@Configuration本质是@Component
以上注解使用的时候要在配置文件内配置扫描包context:component-scan
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.gugugutime.spring.service"/>
</beans>
实体类:
import org.springframework.stereotype.Component;
@Component
public class User {
private String name;
private Integer age;
private String email;
}
main方法:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User obj = context.getBean("user", User.class);
System.out.println(obj);//com.gugugutime.spring.entity.User@41488b16
}
纯注解形式
将上述扫描包的xml配置文件删除,使用@Component
ClassPathXmlApplicationContext修改为
main
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ScanConfig.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println("bean名称:"+beanDefinitionName);
}
}
扫描包
@ComponentScan({"com.gugugutime.spring.entity"})
public class ScanConfig {
}
注解实现工厂方式
初识化实现FactoryBean接口,实现对Bean加载到容器之前的批处理操作.
public class UserFactory implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
// 可以初始化
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
注意:使用@Bean不一定就创建方法返回的对象
@Configuration
public class CommonConfiguration {
@Bean
public UserFactory userFactory(){
return new UserFactory();
}
}
导入原有xml配置bean
@ImportResource
import加载Bean
依赖注入
为属性赋值
Bean注入
Bean注入的方式有两种,一种是在XML中配置,此时分别有属性注入、构造函数注入和工厂方法注入;另一种则是使用注解的方式注入 @Autowired,@Resource,@Required
装配
bean之间的关系
Spring AOP
代码包括业务代码和系统代码两部分,AOP可以将业务代码与系统代码分离。
公共的行为,像日志记录,权限验证等如果都使用面向对象来做,会在每个业务方法中都写上重复的代码,造成代码的冗余。而AOP指的是面向切面编程,定义一个切面,用切面去切相应的方法,就可以织入相关的逻辑。
面向切面编程使用代理模式。
切入点定义切入的位置,通知定义切入的时间。
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义切面,指定切入点和切入时间
@Component
@Aspect
public class MyAspect {
// 切点,即在哪些业务方法上进行增强
@Pointcut("execution(* com.gugugutime.spring.service.*.*(..))")
public void advice(){}
// 切入时间:目标切点执行前的方法
@Before("advice()")
public void before(){
System.out.println("=====切面在业务方法前执行======");
}
//目标切点执行后的方法
@After("advice()")
public void after(){
System.out.println("=====切面在业务方法后执行======");
}
}
业务方法:
@Service
public class TestService {
public String add(String id){
System.out.println("增加:"+id);
return "ok!";
}
通知
spring aop的5种通知类型都有
Before前置通知
AfterReturning后置通知
AfterThrowing异常通知
在目标方法抛出异常时执行的通知
可以配置传入JoinPoint获取目标对象和目标方法相关信息,但必须处在参数列表第一位
另外,还可以配置参数,让异常通知可以接收到目标方法抛出的异常对象。
After最终通知
Around环绕通知
使用@Around注释,功能最强,却不常用,环绕通知需要携带ProceedingJoinPoint类型的参数。
在环绕通知中必须显式的调用目标方法,目标方法才会执行,这个显式调用时通过ProceedingJoinPoint来实现的,可以在环绕通知中接收一个此类型的形参,spring容器会自动将该对象传入,注意这个参数必须处在环绕通知的第一个形参位置。
环绕通知类似于动态代理全过程,ProceedingJoinPoint类型参数可以决定是否执行目标方法,且环绕通知必须要有返回值,返回值即为目标方法的返回值。
如果环绕通知随便返回一个值,那么切入点方法的返回值就会被环绕通知的返回值覆盖。
环绕通知可以控制返回对象,即你可以返回一个与目标对象完全不同的返回值,虽然这很危险,但是你却可以办到。而后置方法是无法办到的,因为他是在目标方法返回值后调用。
事务
事物隔离
事务是基于AOP代理技术的,所以一定要先明白AOP。
事务传播
事务的传播特性:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
如下代码,事务a方法里面调用了事务b方法,如果b方法发生了异常,a方法内执行的SQL语句是否会滚?
class A{
@Transactional
public void a(){
b();
}
@Transactional
public void b(){
}
}
或者
class A{
@Transactional
public void a(){
b.b();
}
}
class B{
@Transactional
public void b(){
}
}
注意这里的主体是两个事务方法。
我们修改传播级别的对象是被调用的方法,即上面示例的b方法。
如
class A{
@Transactional
public void a(){
b();
}
@Transactional(propagation= Propagation.SUPPORTS)
public void b(){
}
}
这两个事务方法可以在一个类内,也可以分别在各自的类里面。
当在方法上使用@Transactional注解,就表示该方法开启了事务。
事务默认传播级别
事务默认在发生运行时异常(RuntimeException)和错误(Error)时回滚,如果需要所有的Exception都要回滚的话,直接@Transactional(rollbackFor = Exception.class) 就可以了。
事务默认传播级别是Propagation.REQUIRED。
Spring支持7种事务传播级别
(1)PROPAGATION_REQUIRED:默认事务类型。如果没有,就新建一个事务;如果有,就加入当前事务。适合绝大多数情况。保证有事务。
解释:b方法使用该级别,如果a方法没有事务,b方法就会新建一个事务;如果a方法有事务,那么b方法就会加入到a方法的事务内。
(2)PROPAGATION_REQUIRES_NEW:如果没有,就新建一个事务;如果有,就将当前事务挂起。与调用方法相反。
解释:b方法使用该级别,如果a方法没有事务,b方法就会新建一个事务;如果a方法有事务,那么b方法就会加入到a方法的事务内。
(3)PROPAGATION_NESTED:如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。
(4)PROPAGATION_SUPPORTS:如果没有,就以非事务方式执行;如果有,就使用当前事务。
解释:
(5)PROPAGATION_NOT_SUPPORTED:如果没有,就以非事务方式执行;如果有,就将当前事务挂起。即无论如何不支持事务。
(6)PROPAGATION_NEVER:如果没有,就以非事务方式执行;如果有,就抛出异常。
(7)PROPAGATION_MANDATORY:如果没有,就抛出异常;如果有,就使用当前事务。
普通方法调用事务方法
在一个类里面,一个普通方法调用事务方法是无效的,此时不遵守事务传播级别规则。因为事务基于AOP动态代理,此时要想执行事务,设置@EnableAspectJAutoProxy(exposeProxy = true)。
在多个类里面,一个类内的普通方法调用另一个类的事务方法是遵守事务传播级别规则的。
测试数据
测试代码
UserService1的transfer1()方法调用了UserService2的transfer2()方法
public void transfer1(){
jdbcTemplate.update("update user set age = ? where id = ?",10,1);
userService2.transfer2();
}
@Transactional(propagation= Propagation.REQUIRED)
public void transfer2(){
jdbcTemplate.update("update user set age = ? where id = ?",10,2);
int num = 1/0;
}
默认事务传播Propagation.REQUIRED
1.transfer1方法有事务,transfer2方法有事务,此时transfer2加入到transfer1事务
2.transfer1方法没有事务,transfer2方法有事务,此时transfer2创建事务
public void transfer1(){
jdbcTemplate.update("update user set age = ? where id = ?",10,1);
userService2.transfer2();
}
@Transactional(propagation= Propagation.REQUIRED)
public void transfer2(){
jdbcTemplate.update("update user set age = ? where id = ?",10,2);
int num = 1/0;
}
执行结果:transfer2回滚,数据未修改,transfer1没有事务,修改了数据。