Spring模块
整体架构
一 Spring IOC和DI
1、什么是IOC
IOC:控制反转,由Spring容器管理bean的整个生命周期。通过反射实现对其他对象的控制,包括初始化、创建、销毁等,解放手动创建对象的过程,同时降低类之间的耦合度。
IOC的好处:降低了类之间的耦合,对象创建和初始化交给Spring容器管理,在需要的时候只需向容器进行申请。
DI:(依赖注入):在Spring创建对象的过程中,把对象依赖的属性注入到对象中。有两种方式:构造器注入和属性注入。
1.1 ioc容器
Spring主要有两种ioc容器,实现了BeanFactory接口的简单容器和ApplicationContext高级容器。
- BeanFactory :延迟注入(使用到某个bean的时候才会注入),相比于BeanFactory来说会占用更少的内存,程序启动速度更快。BeanFactory提供了最基本的ioc容器的功能(最基本的依赖注入支持)。
- ApplicationContext :容器启动的时候,一次行创建所有bean。ApplicationContext扩展了BeanFactory,除了有BeanFactory的功能外还有额外更多功能,所以一般开发人员使用ApplicationContext会更多。
ApplicationContext提供了BeanFactory没有的新特性:
- 支持多语言版本;
- 支持多种途径获取Bean定义信息;
- 支持应用实践,方便管理Bean;
DefaultListableBeanFactory 实现了 ioc 容器的基本功能,其他 ioc 容器如 XmlBeanFactory和ApplicationContext 都是通过持有或扩展DefaultListableBeanFactory获得基本的 ioc 容器的功能。
1.2 容器初始化
ioc容器初始化过程:BeanDefinition 的资源定位、解析和注册。
- 从XML中读取配置文件。
- 将bean标签解析成 BeanDefinition,如解析property 元素,并注入到 BeanDefinition 实例中。
- 将 BeanDefinition 注册到容器BeanDefinitionMap 中。
- BeanFactory 根据 BeanDefinition 的定义信息创建实例化和初始化bean。
单例bean的初始化以及依赖注入一般都在容器初始化阶段进行,只有懒加载(lazy-init为true)的单例bean是在应用第一次调用getBean()时进行初始化和依赖注入。
// AbstractApplicationContext
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
多例bean在容器启动时不实例化,即使设置 lazy-init 为 false 也没用,只有调用了 getBean()才进行实例化。
loadBeanDefinition 采用了模板模式,具体加载BeanDefinition 的逻辑由子类完成。
1.3 Bean生命周期
- 对bean进行实例化
- 依赖注入
- 如果Bean实现了BeanNameAware接口,Spring将调用setBeanName(),设置Bean id (xml文件中bean标签的id)
- 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()
- 如果Bean实现了ApplicationContextAware接口,Spring容器将调用setApplicationContext()
- 如果存在BeanPostProcessor,Spring将调用它们的postProcessBeforeInitialzation(预初始化)方法,在Bean初始化前对其进行处理
- 如果Bean实现了InitializingBean接口,Spring将调用他的afterPropertiesSet方法,然后调用xml定义的 init-method 方法,两个方法作用类似,都是在初始化bean的时候执行。
- 如果存在BeanPostProcessor,Spring将调用他们的postProcessAfterInitialization(后初始化)方法,在Bean初始化后对其进行处理
- Bean初始化完成,供应用使用,直到应用销毁
- 如果Bean实现了DisposableBean接口,Spring将调用他的destory方法,然后调用在xml中定义的 destory-method 方法,这两个方法作用类似,都是在Bean实例销毁前执行。
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
1.4 bean注入容器的方法
将普通类交给Spring容器管理,通常有以下方法:
1、使用 @Configuration 与 @Bean 注解
2、使用 @Controller @Service @Repository @Component 注解标注该类,然后启用 @ComponentScan 自动扫描
3、使用 @Import 方法
@Import 注解把bean导入到当前容器中。
//@SpringBootApplication
@ComponentScan
/*把用到的资源导入到当前容器中*/
@Import({Dog.class, Cat.class})
public class App {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
System.out.println(context.getBean(Dog.class));
System.out.println(context.getBean(Cat.class));
context.close();
}
}
1.5 bean的作用域
Spring创建bean默认是单例,每一个Bean的实例只会被创建一次,通过getBean()获取的是同一个Bean的实例。可使用<bean>标签的scope属性来指定一个Bean的作用域。
<!-- 作用域:prototype -->
<bean name="accountDao" scope="prototype" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl"/>
通过注解来声明作用域:
@Scope("prototype")
public class AccountDaoImpl {
//......
}
容器在创建完一个prototype实例后,就不会去管理这个bean了,会把它交给应用自己去管理。
一般情况下,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。所谓有状态就是该bean保存有自己的信息,不能共享,否则会造成线程安全问题。而无状态则不保存信息,可以共享,spring中大部分bean都是单例的,整个生命周期过程只会存在一个。
request作用域:对于每次HTTP请求到达应用程序,Spring容器会创建一个全新的Request作用域的bean实例,且该bean实例仅在当前HTTP request内有效,整个请求过程也只会使用相同的bean实例,而其他请求HTTP请求则创建新bean的实例,当处理请求结束,request作用域的bean实例将被销毁。
session 作用域:每当创建一个新的HTTP Session时就会创建一个Session作用域的Bean,并该实例bean伴随着会话的结束(session过期)而销毁。
2、创建Spring项目
Spring不涉及Web模块,所以创建java项目即可。
1.Spring项目核心jar包:(缺一不可)
commons-logging-1.1.1.jar
spring-beans-4.3.10.RELEASE.jar
spring-context-4.3.10.RELEASE.jar
spring-core-4.3.10.RELEASE.jar
spring-expression-4.3.10.RELEASE.jar
2.创建spring配置文件(使用配置方式往容器中加入组件)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
<!--
一个<bean>标签可以注册一个组件(对象或类)
class 要写组件的全类名
id 这个对象的唯一标识
-->
<bean id="xxx" class="xxx.class" >
<!--
property标签为对象的属性赋值(通过setXxx()方法)
name 指定属性名
value 为属性赋值
-->
<property name="" value=""></property>
</bean>
</beans>
补充:IDEA要引入命名空间只需输入< ,然后在alt+/即可
3.创建IOC容器
//ApplicationContext: 代表着IOC容器
//ClassPathXmlApplicationContext():当前应用的xml配置文件在ClassPath下;
//【参数为spring配置文件的位置】
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
//通过getBean()方法获取IOC容器中的对象
context.getBean("beanName");
ApplicationContext的两个重要实现类
ClassPathXmlApplicationContext(configLocation):配置文件在项目的类路径下
FileSystemXmlApplicationContext(configLocation):配置文件在电脑的硬盘上
总结:
- ApplicationContext:IOC容器的接口
- 组件的创建是IOC容器完成的
- 容器中的对象是什么时候创建的呢?创建容器的时候完成的时候创建对象
- 同一个组件在ioc容器中是单例的;容器启动完成都已将创建好了。
- 容器中没有这个组件,依然获取该组件,报异常:
- ioc容器在创建这个组建对象的时候,(property)会利用setter方法为JavaBean的属性进行赋值;
- JavaBean的属性名是有什么决定的?getter/setter方法是属性名;setXxx()去掉后面那一串首字母小写就是属性名,即xxx
3.Spring管理连接池
数据库连接池最后是单例的,一个项目就一个连接池,连接池里面管理很多链接。连接是从连接池中获取的。
1.编写连接池的Bean:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
2.测试获取连接:
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
DataSource dataSource = (DataSource) ioc.getBean("dataSource");
System.out.println(dataSource.getClass());
3.引用外部配置文件db.properties
引入外部配置文件需依赖context名称空间
<!--引入外部配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
然后再使用**${key}**获取外部配置文件的内容
<bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
坑:user的key不能是username,因为username是spring的一个关键字,获取到的是当前系统名称,可以是jdbc.username,可以是user…但不能是username,
4.基于XML的自动装配(自定义类型自动赋值)
javaBean(基本类型没有自动赋值,如果是自定义类型的属性是一个对象,这个对象可能在容器中存在)
**手动赋值:**使用ref="car"应用已存在的bean
<!--为Person里的自定义car属性赋值-->
<bean id="car" class="com.atcpl.entity.Car">
<property name="productName" value="奥迪"></property>
</bean>
<bean id="person" class="com.atcpl.entity.Person">
<property name="lastName" value="张三"></property>
<property name="car" ref="car"></property>
</bean>
自动装配:
<bean id="car" class="com.atcpl.entity.Car">
<property name="productName" value="奥迪"></property>
</bean>
<!--
autowire="default/no":不自动装配
autowire="byName":按名字自动装配
private Car car;以该属性的属性名作为bean的id去容器中找这个组件,给他赋值,如果找不到就装null
相当于 car = ioc.getBean("car");
autowire="byType":按类型自动装配
private Car car;以该属性的类型为查找依据去容器中找这个组件;
如果容器中有多个这种类型的组件则报错,如果容器中没有该类型的组件,则装配null
相当于 car = ioc.getBean(Car.class);
autowire="constructor":按照构造器自动装配
1、先按照类型进行装配,没有则装配null;(如果有多个该类型的组件,则继续按构造起的参数名作为id继续匹配),找到就装配,找不到就null
-->
<bean id="person" class="com.atcpl.entity.Person" autowire="byName">
</bean>
5.SpEL
应用场景:在SpEL中使用字面量、引用其他bean、引用其他bean的某个属性值、调用非静态方法、调用静态方法、使用运算符.
使用字面量:
<bean id="person01" class="com.atcpl.entity.Person">
<property name="age" value="#{2*10}"></property>
</bean>
引用其他bean:
<bean id="person01" class="com.atcpl.entity.Person">
<property name="car" value="#{car}"></property>
</bean>
引用其他bean的某个属性值:
<bean id="person" class="com.atcpl.entity.Person" autowire="constructor">
<property name="lastName" value="李四"></property>
</bean>
<bean id="person01" class="com.atcpl.entity.Person">
<property name="lastName" value="#{perosn.productName}"></property>
</bean>
**调用非静态方法:**语法:对象.方法名
<bean id="person" class="com.atcpl.entity.Person" autowire="constructor">
<property name="lastName" value="李四"></property>
<property name="gender" value="男"></property>
</bean>
<bean id="person01" class="com.atcpl.entity.Person">
<property name="gender" value="#{person.getGender()}"></property>
</bean>
**调用静态方法:**语法:#{T(全类名).静态方法名()}
<bean id="person" class="com.atcpl.entity.Person" autowire="constructor">
<property name="lastName" value="李四"></property>
</bean>
<bean id="person01" class="com.atcpl.entity.Person">
<property name="lastName" value="#{person.lastName}"></property>
<property name="email" value="#{T(java.util.UUID).randomUUID().toString()}"/>
</bean>
6.spring注解的使用
通过给bean上添加某些注解,可以快速的将bean加入到ioc容器中;某个类上添加任何一个注解都能快速将这个组件添加到ioc容器的管理中。
1.Spring有四个注解:
@Controller:控制器
@Service:业务逻辑
@Repository:持久化层
@Component:给不属于以上层的类
2.使用注解将组件快速的加入到容器中步骤:
1)、给要添加的组件上标上四个注解的其中一个
2)、告诉Spring,自动扫描加了注解的组件;依赖context名称空间
<context:component-scan base-package=""></context:component-scan>
3)、一定要导入aop包,来支持注解模式
使用注解加入到容器中的组件,和使用配置加入到容器中的组件行为都是一致的。
1、组建的id,默认就是组建的类名首字母小写(也可以在注解后自定义组件id,如:@Repository(“bookdao”))
2、组件的作用域,默认就是单例的(可以使用注解改成多实例@Scope(value = “prototype”))
3. 指定扫描包时不包含的类
<context:component-scan base-package="com.atcpl">
<context:exclude-filter type="annotation" expression=""></context:exclude-filter>
</context:component-scan>
type="annotation":按照注解进行排除,标注了指定注解的组件不要 expression="":注解的全类名
type="assignable":指定排除某个类,按照类排除 expression="":类的全类名
type=“aspectj”:aspectj表达式
type=“custom”:自定义一个TypeFilter;自己写代码决定哪些使用
type=“regex”:还可以写正则表达式
4.指定扫描包时要包含的类
<context:component-scan base-package="com.atcpl">
<!--指定只扫描哪些组件-->
<context:include-filter type="annotation" expression=""></context:include-filter>
</context:component-scan>
7.@Autowired自动装配
原理:
@Autowired
BookDao bookDao;
public void service(){
String dao = bookDao.dao();
System.out.println("这是service层");
}
1)、先按照类型去容器中找到对应的组件,找到就赋值,没找到抛异常;
相当于bookService =ioc.getBean(BookService.class)
2)、如果资源类型的bean不止一个,默认根据@Autowired注解标记的成员变量作为id查找bean,进行装配
3)、如果根据成员变量名作为id还是找不到bean,可以使用@Qualifier注解明确指定目标bean的id
4)、如果是在找不到,可以使用@Autowired注解的required属性指定某个属性是允许不被设置
8.@Autowired和@Resource的区别
@Autowired、@Resource、@Inject,都是自动装配
@Autowired:最强大,spring自己的注解
@Resource:j2ee;java自己的标准
区别:
@Resource:扩展性更强,因为是java的标准;如果换成另外一个容器框架,@Resource还是可以使用的,@Autowired就不行了。
9.Spring的单元测试
1、导包:spring-test-4.3.10.RELEASE.jar
2、使用@ContextConfiguration(locations = “classpath:spring-config.xml”)来指定Spring的配置文件的位置
3、@RunWith()指定用哪种驱动进行单元测试,默认就是junit
@RunWith(SpringJUnit4ClassRunner.class)是使用Spring的单元测试模块来执行标注了@Test注解的测试方法;
@ContextConfiguration(locations = "classpath:spring-config.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class IOCTest {
@Autowired
BookController bookController;
@Test
public void test02(){
String s = bookController.doGet();
System.out.println(s);
}
}
以前@Test注解只有junit执行
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-config.xml");
@Test
public void test01(){
BookController controller = ioc.getBean(BookController.class);
String result = controller.doGet();
System.out.println("这是最终结果"+result);
}
使用Spring的单元测试好处是不用再使用ioc.getBean()获取组件了,直接自动装配组件。Spring为我们自动装配
10.泛型依赖注入(难点)
实现:
BookDao:
@Repository
public class BookDao extends BaseDao<Book>{
@Override
public void save() {
System.out.println("保存图书...");
}
}
UserDao:
@Repository
public class UserDao extends BaseDao<User> {
@Override
public void save() {
System.out.println("保存用户...");
}
}
BaseDao:
/**
* BaseDao中写基本的增删改查
* @param <T>
*/
public abstract class BaseDao<T> {
public abstract void save();
}
BookeService:
@Service
public class BookService extends BaseService<Book> {
//该类继承了BaseService 所以有BaseService类中的所有方法及属性
}
UserService:
@Service
public class UserService extends BaseService<User>{
}
BaseService:
public class BaseService<T> {
@Autowired
BaseDao<T> baseDao;
public void save(){
System.out.println("自动注入的dao"+baseDao);
baseDao.save();
}
}
//该类虽然没有标注@Service注解,也就意味着没有在IOC容器中。因此@Autowired无用,BaseDao无法装配。但他的子类标注了@Service注解,在IOC容器中,且子类有该类的所有属性方法,所以可以在IOC容器中注册。
IOCTest:
public class IOCTest {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-config.xml");
@Test
public void test01(){
UserService user = ioc.getBean(UserService.class);
BookService book = ioc.getBean(BookService.class);
//UserService 中没有任何方法也可以调用BaseService类中的save()方法
user.save();
book.save();
}
}
控制台输出:
解释:
原理:
Spring中可以使用带泛型的父类类型来确定这个子类的类型。
11.IOC总结
Ioc是一个容器,帮我们管理所有的组件。
- 依赖注入:@Autowired 自动赋值
- 某个组件要使用Spring提供的更多功能,必须加入到容器中。
- 容器启动。创建所有单实例bean
- Autowired自动装配的时候,是从容器中找这些符合要求的bean
- ioc.getBean() :也是从容器中找到这个bean
- 容器中包括了所有的bean
- 调试spring的源码,容器到底是什么?其实就是一个map,这个map中保存所有创建好的bean,比ing提供外界获取功能。
二 Spring AOP
1、定义
aop:面向切面编程;基于OOP(面向对象编程)基础之上新的编程思想;
指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的这种编程方式。
AOP有两种实现方式:静态代理和动态代理。
1.1 静态代理
静态代理:代理类在编译阶段生成,在编译阶段将通知织入Java字节码中,也称编译时增强。AspectJ使用的是静态代理。
缺点:代理对象需要与目标对象实现一样的接口,并且实现接口的方法,会有冗余代码。同时,一但接口增加方法,目标对象与代理对象都要维护。
1.2 动态代理
动态代理:代理类在程序运行时创建,AOP框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。
1.3 Spring AOP动态代理
Spring AOP中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理。
-
JDK动态代理(生成的代理类实现类接口)。如果目标类实现了接口,Spring AOP会选择使用JDK动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
缺点: 目标类必须有实现的接口。如果某个类没有实现接口,那么这个类就不能用JDK动态代理。
-
CGLIB来动态代理(通过继承)。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。
CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么他是无法使用CGLIB做动态代理的。
优点:目标类不需要实现特定的接口,更加灵活。
什么时候采用哪种动态代理?
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
- 如果目标对象没有实现了接口,必须采用CGLIB库
区别:
- jdk动态代理使用jdk中的类Proxy来创建代理对象,他使用反射技术来实现,不需要导入其他依赖。cglib需要引入相关依赖:asm.jar,他是用字节码增强技术来实现。
- 当目标类实现了接口的时候Spring AOP默认使用jdk动态代理方式来增强方法,没有实现接口的时候使用cglib动态代理方式增强方法。
实现原理:
Spring会为目标对象生成代理对象。当调用代理对象方法的时候,会触发CglibAopProxy.intercept(),然后将目标对象的增强包装成拦截器,形成拦截器链,最后执行全部拦截器和目标方法。
2、场景(例子):
使用动态代理实现计算机运行计算方法的时候进行日志记录
Calculator:
/**
* 计算机方法的接口
*/
public interface Calculator {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
CalculatorImpl:
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
CalculatorProxy:
LogUtils:
public class LogUtil {
public static void logStart(Method method, Object... args){
System.out.println("【"+method.getName()+"】开始执行,参数列表【"+ Arrays.asList(args) +"】");
}
public static void logReturn(Method method, Object result){
System.out.println("【"+method.getName()+"正常执行,计算结果为"+result+"】");
}
public static void logException(Method method,Exception e){
System.out.println("【"+method.getName()+"方法执行出现异常,异常信息是"+e.getCause()+"】");
}
public static void logEnd(Method method){
System.out.println("【"+method.getName()+"方法执行结束】");
}
}
增加日志的Calculator:
/**
* 帮Calculator生成代理的类
*/
public class CalculatorProxy {
/**
* 为传入的参数创建一个动态代理对象
* @param calculator
* @return
*
* Calculator calculator 被代理对象
*/
public static Calculator getProxy(final Calculator calculator) {
//被代理对象的类加载器
ClassLoader loader = calculator.getClass().getClassLoader() ;
Class<?>[] interfaces = calculator.getClass().getInterfaces() ;
//方法执行器。帮目标对象执行目标方法
InvocationHandler h = new InvocationHandler() {
/**
*
* @param proxy 代理对象,任何时候都不要动这个对象
* @param method 当前将要执行的目标对象的方法
* @param args 这个方法执行时外界传入的参数值
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
LogUtil.logStart(method,args);
//利用反射执行目标方法
result = method.invoke(calculator, args);
LogUtil.logReturn(method,result);
}catch (Exception e){
LogUtil.logException(method,e );
}finally {
LogUtil.logEnd(method);
}
//invoke:目标方法执行后的返回值
return result;
}
} ;
Object proxyInstance = Proxy.newProxyInstance(loader, interfaces, h);
return (Calculator) proxyInstance;
}
}
测试:
public class AOPTest {
@Test
public void aopTest(){
Calculator calculator = new CalculatorImpl();
//如果拿到了目标方法的代理对象,则执行加减乘除
Calculator proxy = CalculatorProxy.getProxy(calculator);
//代理对象类型: class com.sun.proxy.$Proxy4
System.out.println("代理对象的类型:=="+proxy.getClass());
//代理对象与被代理对象之间唯一能产生的关联就是实现了同一个接口
System.out.println("代理对象实现的接口:=="+Arrays.asList(proxy.getClass().getInterfaces()));
}
}
可以看到,代理对象也实现了Calculator接口,而被代理对象CalculatorImpl也实现了Calculator接口,所以二者产生关联的点就是这个接口,即代理对象与目标对象产生关联的地方就是接口。从而也反映出,如果没有实现接口的对象则不能使用动态代理。
使用动态代理的缺点:
- 写起来很难
- JDK默认的动态代理,如果目标对象没有实现任何接口,则无法为他创建代理对象的
所以Spring推出了AOP功能,其底层就是动态代理
优点:
- 可以利用Spring一句代码都不写的去创建动态代理
- 实现简单,而且没有强制要求目标对象必须实现接口
3、AOP专业术语
4、Spring AOP简单配置
1、步骤:
1、导包
commons-logging-1.2.jar
hamcrest-core-1.3.jar
junit-4.12.jar
spring-aop-4.3.18.RELEASE.jar
spring-beans-4.3.18.RELEASE.jar
spring-context-4.3.18.RELEASE.jar
spring-core-4.3.18.RELEASE.jar
spring-expression-4.3.18.RELEASE.jar
Spring支持面向切面编程的包是:
spring-aspects-4.3.18.RELEASE.jar(基础版)
加强版的面向切面编程(即使目标对象没有实现任何接口也能创建动态代理) aopalliance-1.0.jar
aspectjweaver-1.5.3.jar
cglib-3.2.10.jar
2、基于注解的AOP
1)、将目标类和切面类(封装了通知方法(在目标方法执行前后执行的方法))加入到IOC容器中
2)、告诉Spring哪个类是切面类@Aspect
3)、告诉Spring,切面类中的每一个方法,都是何时何地运行
@Aspect
@Component
public class LogUtil {
@Before("execution(public int com.atcpl.entity.CalculatorImpl.*(int,int))")
public static void logStart(){
System.out.println("【】开始执行,参数列表【】");
}
@AfterReturning("execution(public int com.atcpl.entity.CalculatorImpl.*(int,int))")
public static void logReturn(){
System.out.println("【正常执行,计算结果为】");
}
@AfterThrowing("execution(public int com.atcpl.entity.CalculatorImpl.*(int,int))")
public static void logException(){
System.out.println("【方法执行出现异常,异常信息是】");
}
@After("execution(public int com.atcpl.entity.CalculatorImpl.*(int,int))")
public static void logEnd(){
System.out.println("【】");
}
}
4)、开启基于注解的AOP模式
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3、测试
public class AOPTest {
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-config.xml");
@Test
public void aopTest(){
//注意:从ioc容器中拿到目标对象,注意:如果想要用类型,一定要用他的接口类型,不要用它本类
Calculator calculator = ioc.getBean(Calculator.class);
int i = calculator.sub(1, 2);
System.out.println(i);
System.out.println(calculator);
System.out.println(calculator.getClass());
}
}
细节1.
AOP的底层就是动态代理,容器中保存的对象就是代理对象:$Proxy12,所以说拿本类CalculatorImpl是获取不到的;可能存在疑问:
这个类上明明标注着@Service注解,容器一启动就会为他创建对象。注意:这里创建的是他的代理对象com.sun.proxy.$Proxy12;前边第二节有说到过,代理对象与目标对象就是同一个接口。
所以说,这个地方要拿接口。那么又有疑问了:
![image-20220905145649010](https://images-1313675740.cos.ap-shanghai.myqcloud.com/images/image-20220905145649010.png)
Calculator接口没有加入到容器中,ioc.getBean(Calculator.class)为什么能拿到这个组件呢?这里要反向思考,ioc.getBean(Calcultor.class)要拿这个类型的组件,会去ioc容器中找已有的组件,恰巧容器中有
这个类型的组件。
补充:
CalculatorImpl没有实现接口:
没有接口就拿本类
可以看到cglib帮我们创建了代理对象。
总的来说,有实现接口就使用JDK创建代理对象,没有接口就使用cglib创建代理对象。
4、5个通知注解
前置通知-@Before:目标方法之前运行
后置通知-@After:目标方法结束之后运行
返回通知-@AfterReturning:目标方法正常返回之后运行
异常通知-@AfterThrowing:目标方法抛出异常之后运行
环绕通知-@Around:环绕通知
切入点表达式:execution(访问权限符 返回类型 方法全类名(参数列表))
通配符:
//匹配一个或多个任意字符
@Before("execution(public int com.atcpl.entity.Calcul*Impl.*(int,int))")
public static void logStart(){
System.out.println("【】开始执行,参数列表【】");
}
//匹配任意一个参数
@Before("execution(public int com.atcpl.entity.CalculatorImpl.add(int,*))")
public static void logStart(){
System.out.println("【】开始执行,参数列表【】");
}
//只能匹配一层路径
- …
//匹配任意多个参数,任意类型参数
@Before("execution(public int com.atcpl.entity.CalculatorImpl.*(..))")
public static void logStart(){
System.out.println("【】开始执行,参数列表【】");
}
//匹配任意多层路径
@Before("execution(public int com.atcpl..entity.CalculatorImpl.*(..))")
public static void logStart(){
System.out.println("【】开始执行,参数列表【】");
}
最模糊的写法:,不推荐写
最精确的:
5、通知方法的执行顺序
try{
@Before
method.invoke(obj,args);
@AfterReturning
}catch (){
@AfterThrowing
}finally {
@After
}
6、JoinPoint
joinPoint封装了目标方法的详细信息
@Before("execution(public int com.atcpl.entity.CalculatorImpl.add(int,int))")
public static void logStart(JoinPoint joinPoint){
//获取方法名
String name = joinPoint.getSignature().getName();
//获取参数列表
Object[] args = joinPoint.getArgs();
System.out.println("【"+name+"】开始执行,参数列表【"+ Arrays.asList(args) +"】");
}
throwing、returning来指定参数
@AfterReturning(value = "execution(public int com.atcpl.entity.CalculatorImpl.*(..))" ,returning = "result")
public static void logReturn(JoinPoint joinPoint,Object result){
System.out.println("【"+joinPoint.getSignature().getName()+"】方法正常执行,计算结果为【"+result+"】");
}
@AfterThrowing(value = "execution(public int com.atcpl.entity.CalculatorImpl.*(int,int))",throwing = "e")
public static void logException(JoinPoint joinPoint,Exception e){
System.out.println("【"+joinPoint.getSignature().getName()+"】方法执行出现异常,异常信息是【"+e+"】");
}
7、抽取可重用的切入点表达式
1.声明一个没有实现的空的返回类型为void的空方法
2.给方法上标注@Pointcut注解
这个点如果和环绕通知一起使用,要注意jar包的版本。可能会报错
8、环绕通知
@Around:环绕通知是Spring中最强大的通知方法,他就是一个动态代理。
环绕通知中一个重要参数:ProceedingJoinPoint
@Around("execution(public int com.atcpl.entity.CalculatorImpl.add(int,int))")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//参数列表
Object[] args = proceedingJoinPoint.getArgs();
//目标方法名
String name = proceedingJoinPoint.getSignature().getName();
Object proceed = null;
try {
System.out.println("环绕前置通知..."+name+"方法执行");
//就是利用反射调用目标方法即可,这句话就是method.invoke(obj,args)
proceed = proceedingJoinPoint.proceed(args);
System.out.println("环绕返回通知..."+name+"方法返回值为:"+proceed);
} catch (Exception e) {
System.out.println("环绕异常通知..."+name+"方法出现异常,异常信息为:"+e);
}finally {
System.out.println("环绕后置通知..."+name+"方法执行完毕");
}
//反射调用后的返回值也一定返回出去
return proceed;
}
环绕通知优先于普通通知执行;
执行顺序:
try{
环绕前置通知
前置通知
环绕通知执行目标方法
环绕返回通知
}catch (){
环绕异常通知
<!--注意:这个地方一定要把异常跑出去,否则普通的异常通知捕获不到异常信息-->
}finally {
环绕后置通知
}
}
环绕前置–>普通前置–>目标方法执行–>环绕正常返回/出现异常–>环绕后置–>普通后置==>普通返回或者异常
9、多切面运行顺序
可以使用@Order()改变切面顺序,数值越小优先级越高
即使加上环绕通知,也只会影响加入环绕通知的切面本身。
10、AOP的应用
AOP使用场景:
1、AOP加日志保存到数据库
2、AOP做权限验证
3、AOP做安全检查
4、AOP做事务控制
12、基于XML配置的AOP
重要的信推荐使用配置,其他用注解。
三 事务
事务就是一系列的操作原子执行。
Spring事务机制主要包括声明式事务和编程式事务。
声明式事务将事务管理代码从业务方法中分离出来,通过aop进行封装。用 @Transactional 注解开启声明式事务。
Spring声明式事务使得我们无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。
1、配置数据源
外部配置文件:db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/2020db?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false
jdbc.username=root
jdbc.password=root
配置数据源:
<!--引入外部配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
测试:
ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-config.xml");
@Test
public void test01(){
Object dataSource = ioc.getBean("dataSource");
System.out.println(dataSource.getClass());
}
2、JdbcTemplate(非重点)
Spring提供了一个类:JdbcTemplate,来操作数据库。
配置JDBCTemplate:
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
测试:
@Test
public void test02(){
JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
String sql = "update s set status = ? where sno = ?";
int i = jdbcTemplate.update(sql, 30, "s1");
System.out.println("更新成功"+i+"条数据");
}
批量插入:
@Test
public void test03(){
String sql = "insert into s values(?,?,?,?)";
//List的长度就是SQL要执行的次数
//Object[] 每次执行要用的参数
List<Object[]> batchArgs = new ArrayList<>();
batchArgs.add(new Object[]{"S7","苏荷",20,"江苏"} );
batchArgs.add(new Object[]{"S8","AAA",10,"苏州"} );
batchArgs.add(new Object[]{"S9","小苏",40,"无锡"} );
jdbcTemplate.batchUpdate(sql, batchArgs);
}
查询一条数据将结果封装成Java对象:
@Test
public void test04(){
//jdbcTemplate.query() 查询集合
//jdbcTemplate.queryForObject() 查询单个对象
String sql = "select * from s where Sno = ?";
//rowMapper 每一行记录和JavaBean的属性如何映射
SClass s = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(SClass.class), "s9");
System.out.println(s);
}
将查询结果集封装成List集合
@Test
public void test05(){
String sql = "select * from s where status > ?";
List<SClass> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(SClass.class), 20);
for (SClass item : list) {
System.out.println(item);
}
}
使用带有具名参数的SQL语句插入一条记录,并以Map形式传入参数值:
- 具名参数: 具有名字的参数,参数不是占位符 ? 而是一个变量名
- 语法格式: :参数名
Spring有一个支持具名参数的jdbcTemplate:
<!--配置一个具有剧名参数功能的jdbcTemplate :NamedParameterJdbcTemplate -->
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
@Test
public void test06(){
String sql = "insert into s values(:SNO,:SName,:Status,:City)";
Map<String,Object> map = new HashMap<>();
//键就是剧名参数的名称
map.put("SNO", "S10");
map.put("SName", "BBB");
map.put("Status", "30");
map.put("City", "北京");
int i = namedJdbcTemplate.update(sql, map);
System.out.println(i);
}
封装:Dao
@Repository
public class MyJavaBeanDao {
@Autowired
JdbcTemplate jdbcTemplate;
public void save(MyJavaBean myJavaBean){
String sql = "insert into s values(?,?,?,?)";
jdbcTemplate.update(sql,myJavaBean.getSno(),myJavaBean.getSName(),myJavaBean.getStatus(),myJavaBean.getCity());
}
}
@Test
public void test07(){
MyJavaBeanDao bean = ioc.getBean(MyJavaBeanDao.class);
MyJavaBean myJavaBean = new MyJavaBean();
myJavaBean.setSno("S12");
myJavaBean.setSName("DDD");
myJavaBean.setStatus(30);
myJavaBean.setCity("上海");
bean.save(myJavaBean);
}
3.声明式事务环境搭建
spring-config配置文件
<!--扫描包-->
<context:component-scan base-package="com.atcpl"></context:component-scan>
<!--导入外部配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--1.配置事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.开启基于注解的事务控制模式-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
<!--3.给事务方法加上注解@Transactional
以前通过复杂的编程来编写一个事务,替换为只需要告诉spring那个方法是事务方法即可。Spring自动进行事务控制。
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
而且这个切面Spring已经给写好了,就是事务管理器(事务切面)
这个事务管理器就可以在目标方法运行前后进行事务控制。
4.注解@Transactional详细
参数 | 类型 | 解释 |
---|---|---|
isolation | Isolation | 事物的隔离级别 |
propagation | Propagation | 事务的传播行为 |
noRollbackFor | Class[] | 哪些异常可以不回滚 |
noRollbackForClassName | String[] | |
rollbackFor | Class[] | 哪些异常事务需要回滚 |
rollbackForClassName | String[] | |
timeout | int | 事务超出指定执行时长后自动终止并回滚 |
readOnly | boolean | 设置事务为只读事务(可以加快查询速度) |
异常分类:
- 运行时异常(非检查异常):可以不用处理,默认都回滚
- 编译时异常(检查异常):要么try-catch,要么抛出异常,默认都不回滚
事物的回滚:默认发生运行时异常都回滚,发生编译时异常不会回滚。
5.事务的隔离级别
各个隔离级别解决并发问题的能力:
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
READ UNCOMMITTED (读未提交:一个事务读取另一个未提交数据的数据) | 有 | 有 | 有 |
READ COMMITTED (读提交:一个事务等另一个事务提交后才能读取事务) | 无 | 有 | 有 |
REPEATABLE READ (可重复读:在开始读取数据时,不在允许修改操作) | 无 | 无 | 有 |
SERIALIZABLE (串行化) | 无 | 无 | 无 |
各种数据库产品对事务隔离级别的支持程度:
Oracle | MySQL | |
---|---|---|
READ_UNCOMMITTED | × | √ |
READ_COMMITTED | √ | √ |
REPEATABLE_READ | × | √(默认) |
SERIALIZABLE | √ | √ |
MySQL事务隔离级别常用命令
- 修改MySQL隔离级别
- set [session | global] transaction isolation level {READ UNCOMMITTED | READ COMMITTED | REPEATABLE_READ | SERIALIZABLE}
- 查询MySQL的隔离级别
- select @@global .tx_isolation; //查询全局隔离级别
- select @@session.tx_isolation; //查询当前会话隔离级别
- select @@tx_isolation; //查询当前会话隔离级别
- 事务操作
- 开启事务 start transcation;
- 提交事务 commit;
- 回滚事务 rollback;
隔离级别案例:
读未提交下的脏读:
读已提交下的避免脏读,没有避免不可重复读:
可重复读情况下的避免了不可重复读等所有问题:
6.Spring事务传播机制
1.PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,这也是通常我们的默认选择。
2.PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
3.PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
4.PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
5.PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
6.PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:
使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务是两个独立的事务。一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。
使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
事务传播案例:
四 Spring IOC源码分析
1.BeanFactory 体系
2.SpringIOC核心源码分析AbstractApplicationContext
3.源码分析
new ClassPathXmlApplicationContext("spring-config.xml")
实例化容器入口调用本身的构造器(有很多个)
所有的构造器都调用的是带了三个参数的this函数
ClassPathXmlApplicationContext:
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}
AbstractApplicationContext:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//Spring解析xml配置文件将要创建的所有bean的配置信息保存起来
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
实例化所有的(非懒加载)单例。
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
@Override
public String resolveStringValue(String strVal) {
return getEnvironment().resolvePlaceholders(strVal);
}
});
}
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);
// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();
// Instantiate all remaining (non-lazy-init) singletons.
//初始化剩余的但实力bean
beanFactory.preInstantiateSingletons();
}
Defau ltLi stabl eBeanFactory:
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
//拿到所有要创建的bean的名字
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
//按顺序创建bean
for (String beanName : beanNames) {
//根据spring配置文件中bean的id获取bean的定义信息
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
//判断bean不是抽象,是单实例不是懒加载的
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
}
// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
smartSingleton.afterSingletonsInstantiated();
return null;
}
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}
AbstractBeanFactory:
public Object getBean(String name) throws BeansException {
//BeanFactory中所有得getBean调用的都是doGetBean
return doGetBean(name, null, null, false);
}
doGetBean(name, null, null, false):
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
//先从已经注册的所有单实例bean中看有没有这个bean,第一次创建bean是没有的
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
//保证当前bean所依赖的bean的初始化。
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
getBean(dep);
}
}
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// Check if required type matches the type of the actual bean instance.
if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
try {
return getTypeConverter().convertIfNecessary(bean, requiredType);
}
catch (TypeMismatchException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to convert bean '" + name + "' to required type '" +
ClassUtils.getQualifiedName(requiredType) + "'", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<Exception>();
}
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
//没创建一个bean都会加入到map中
addSingleton(beanName, singletonObject);
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
DefaultSingletonBeanRegistry:
/** Cache of singleton objects: bean name --> bean instance
创建好的对象最终会保存在一个map中;
ioc就是一个容器,单实例bean保存在一个map中
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
补充:
面试题:BeanFactory和ApplicationContext的区别
ApplicationContext是BeanFactory的子接口
BeanFactory是Bean工厂,用来创建bean实例,容器里面保存的所有单例bean其实是一个map;而ApplicationContext是容器接口,更多的负责容器功能的实现;(可以基于BeanFactory创建好的对象之上完成强大的容器)容器可以从这个map中获取bean。
BeanFactory是最低层的接口,ApplicationContext是留给程序员使用的ioc容器接口;ApplicationContext是BeanFactory的子接口
五 Spring 启动过程
-
读取web.xml文件。
-
创建 ServletContext,为 ioc 容器提供宿主环境。
-
触发容器初始化事件,调用 contextLoaderListener.contextInitialized()方法,在这个方法会初始化一个应用上下文WebApplicationContext,即 Spring 的 ioc 容器。ioc 容器初始化完成之后,会被存储到 ServletContext 中。
public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
-
初始化web.xml中配置的Servlet。如DispatcherServlet,用于匹配、处理每个servlet请求。
六 Spring Bean线程安全
Spring Bean默认是单例的,大部分的Spring bean没有可变的状态(比如Service类和DAO类),是线程安全的。如果Bean带有状态,可以将bean设置为prototype或者使用ThreadLocal确保线程安全。
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
//没创建一个bean都会加入到map中
addSingleton(beanName, singletonObject);
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
DefaultSingletonBeanRegistry:
```java
/** Cache of singleton objects: bean name --> bean instance
创建好的对象最终会保存在一个map中;
ioc就是一个容器,单实例bean保存在一个map中
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
补充:
面试题:BeanFactory和ApplicationContext的区别
ApplicationContext是BeanFactory的子接口
BeanFactory是Bean工厂,用来创建bean实例,容器里面保存的所有单例bean其实是一个map;而ApplicationContext是容器接口,更多的负责容器功能的实现;(可以基于BeanFactory创建好的对象之上完成强大的容器)容器可以从这个map中获取bean。
BeanFactory是最低层的接口,ApplicationContext是留给程序员使用的ioc容器接口;ApplicationContext是BeanFactory的子接口
五 Spring 启动过程
-
读取web.xml文件。
-
创建 ServletContext,为 ioc 容器提供宿主环境。
-
触发容器初始化事件,调用 contextLoaderListener.contextInitialized()方法,在这个方法会初始化一个应用上下文WebApplicationContext,即 Spring 的 ioc 容器。ioc 容器初始化完成之后,会被存储到 ServletContext 中。
public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
-
初始化web.xml中配置的Servlet。如DispatcherServlet,用于匹配、处理每个servlet请求。
六 Spring Bean线程安全
Spring Bean默认是单例的,大部分的Spring bean没有可变的状态(比如Service类和DAO类),是线程安全的。如果Bean带有状态,可以将bean设置为prototype或者使用ThreadLocal确保线程安全。