一、IOC部分
1. 以往在使用Spring时,采用Java代码加上xml配置文件的方式
AbstractXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:smart.spring.xml");
随着SpringBoot的兴起,开始大量采用注解的方式来配置IOC容器和装配Bean,这样就可以不再维护xml配置文件了,但对开发人员的技能有了更高的要求
学习一些常用的Spring注解,才能跟上Spring开发使用的步伐
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApplicationConfig.class);
开发中常用的注解有@Controller,@Service,@Component,@Repository...
2. @Configuration注解告诉Spring这是一个配置类,相当于Spring的一个xml配置文件,我们可以在其中定义Bean信息,修改IOC容器配置等
@Configuration
public class ApplicationConfig
{
}
3. @Bean给容器中注册一个Bean,Id默认使用方法名,Bean类型为方法的返回值类型,可通过注解的name或value属性修改Id名称,
通过initMethod和destroyMethod属性来指定初始化和销毁方法
@Configuration
public class ApplicationConfig
{
@Bean(name = "user")
public User user()
{
return new User("RealJt", 20);
}
}
4. @ComponentScan包扫描,下面为Web环境经典的包扫描配置方式
4.1 使用excludeFilters排除
@Configuration
@ComponentScan(value = "com.realjt.smart", excludeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = { Controller.class, ControllerAdvice.class }) })
public class ApplicationConfig
{
}
4.2 使用includeFilters包含,默认useDefaultFilters=true,即扫描加载所有Bean定义注解,若需要只包含配置的过滤器结果,
需要配置useDefaultFilters=false
@Configuration
@ComponentScan(value = "com.realjt.smart", useDefaultFilters = false, includeFilters = {
@Filter(type = FilterType.ANNOTATION, classes = { Controller.class, ControllerAdvice.class }) })
public class WebApplicationConfig
{
}
4.3 FilterType的取值类型
FilterType.ANNOTATION 按照注解类型
FilterType.ASSIGNABLE_TYPE 按照具体的类型
FilterType.ASPECTJ 使用ASPECTJ表达式
FilterType.REGEX 使用正则表达式
FilterType.CUSTOM 自定义规则,必须是TypeFilter的实现类
4.4 FilterType.CUSTOM自定义过滤规则,class的值必须是TypeFilter的实现类
public class SmartTypeFilter implements TypeFilter
{
/**
* @param metadataReader
* 读取到的当前正在被扫描类的信息
* @param metadataReaderFactory
* 可以获取到其他任何类信息
* @return 是否添加到容器中
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException
{
// 获取当前正在被扫描处理的类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
// 获取当前正在被扫描处理的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
// 获取当前类资源(类的路径)
Resource resource = metadataReader.getResource();
// 获取当前正在被扫描处理的类名称
String className = classMetadata.getClassName();
return true;
}
}
5. @Scope作用域
ConfigurableBeanFactory.SCOPE_SINGLETON 默认是单实例的,容器启动时就调用方法创建对象,后面每次从容器中获取的都是同一个对象
ConfigurableBeanFactory.SCOPE_PROTOTYPE 多实例的,每次获取的时候都会调用方法创建一个对象
WebApplicationContext.SCOPE_REQUEST Web环境,一个请求一个实例
WebApplicationContext.SCOPE_SESSION Web环境,一个会话一个实例
@Configuration
public class ApplicationConfig
{
@Bean(name = "user")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public User user()
{
return new User("RealJt", 20);
}
}
6. @Lazy懒加载
加Lazy注解的单实例Bean在容器启动的时候并不会创建对象,而是在首次获取该对象时才调用方法创建对象
@Configuration
public class ApplicationConfig
{
@Bean(name = "user")
@Lazy
public User user()
{
return new User("RealJt", 20);
}
}
7. @Conditional按照条件添加组件,value值为Condition的实现类
@Conditional也可以放在类上,满足条件时类中定义的Bean才会向容器中注册
@Configuration
public class ApplicationConfig
{
/**
* 如果WindowsCondition类的matches返回true,则向容器中添加该组件
*/
@Bean(name = "user")
@Conditional(value = { WindowsCondition.class })
public User user()
{
return new User("Bill Gates", 50);
}
}
public class WindowsCondition implements Condition
{
/**
* @param context
* 判断条件能使用的上下文
* @param metadata
* 注解信息
* @return 是否匹配,配置则向容器中添加组件
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
{
// 能获取到容器使用的BeanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 获取到类加载器
ClassLoader classLoader = context.getClassLoader();
// 获取到环境信息
Environment environment = context.getEnvironment();
// 获取到Bean定义信息
BeanDefinitionRegistry registry = context.getRegistry();
String osName = environment.getProperty("os.name");
if (osName.contains("Windows"))
{
return true;
}
return false;
}
}
8. @Import向容器中注册组件
8.1 向容器中导入一个User对象,Id默认是User类的全类名
@Configuration
@Import(value = { User.class })
public class ApplicationConfig
{
}
8.2 使用ImportSelector添加组件,自定义类实现ImportSelector接口
// 向容器中添加多个组件
@Configuration
@Import(value = { User.class, SmartImportSelector.class })
public class ApplicationConfig
{
}
public class SmartImportSelector implements ImportSelector
{
/**
* @param importingClassMetadata
* 当前标注@Import注解的类的所有注解信息
* @return 返回需要导入组件的全类名数组,可以返回空数组,不能返回null
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata)
{
return new String[] { "com.realjt.domain.Blue", "com.realjt.domain.Red" };
}
}
8.3 使用ImportBeanDefinitionRegistrar注册组件
// 向容器中添加多个组件
@Configuration
@Import(value = { User.class, SmartImportSelector.class, SmartImportBeanDefinitionRegistrar.class })
public class ApplicationConfig
{
}
public class SmartImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar
{
/**
* @param importingClassMetadata
* 当前类的注解信息
* @param registry
* 所有Bean的注册信息
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
{
// 可以判断需要的组件是否已注册
if (registry.containsBeanDefinition("com.realjt.domain.Red"))
{
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Yellow.class);
// 向容器中注册一个Yellow类的定义信息
registry.registerBeanDefinition("com.realjt.domain.Yellow", rootBeanDefinition);
}
}
}
9. 使用FactoryBean,例如注册数据源时的密码解密操作
@Configuration
public class ApplicationConfig
{
@Bean
public DataSourcePasswordFactoryBean password(@Value("${jdbc.password}") String password)
{
// 密码密文从配置文件中读取,并向容器中返回注册密码明文
DataSourcePasswordFactoryBean dataSourcePasswordFactoryBean = new DataSourcePasswordFactoryBean();
dataSourcePasswordFactoryBean.setPassword(password);
return dataSourcePasswordFactoryBean;
}
@Bean
public DataSource dataSource(String password)
{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setPassword(password);
return dataSource;
}
}
public class DataSourcePasswordFactoryBean implements FactoryBean<String>
{
private String password;
/**
* 得到对象
*/
@Override
public String getObject() throws Exception
{
// 这里返回之前可以做解密
return password;
}
/**
* 对象类型
*/
@Override
public Class<?> getObjectType()
{
return String.class;
}
/**
* 是否是单例
*/
@Override
public boolean isSingleton()
{
return true;
}
public void setPassword(String password)
{
this.password = password;
}
}
延伸:applicationContext.getBean("&password");可以得到工厂Bean本身,即DataSourcePasswordFactoryBean类对象
而不是DataSourcePasswordFactoryBean的泛型类对象
10. Bean的生命周期
10.1 配置initMethod和destroyMethod
在容器启动时先创建对象,给对象属性赋值,然后执行initMethod
在容器关闭时执行destroyMethod,但是配置为prototype的对象不会执行destroyMethod,因为容器并不会管理这个对象
@Configuration
public class ApplicationConfig
{
/**
* 初始化和销毁方法不能有参数
*/
@Bean(value = "car", initMethod = "init", destroyMethod = "destroy")
public Car car()
{
return new Car();
}
}
public class Car
{
public Car()
{
}
public void init()
{
}
public void destroy()
{
}
}
10.2 实现InitializingBean和DisposableBean接口
@Configuration
public class ApplicationConfig
{
@Bean(value = "car")
public Car car()
{
return new Car();
}
}
public class Car implements InitializingBean, DisposableBean
{
public Car()
{
}
/**
* 在对象创建完成,属性赋值后调用
*/
@Override
public void afterPropertiesSet() throws Exception
{
}
/**
* 在对象被销毁时调用
*/
@Override
public void destroy() throws Exception
{
}
}
10.3 使用@PostConstruct和@PreDestroy注解(JSR250)
public class Car
{
public Car()
{
}
/**
* 对象装配完成后调用
*/
@PostConstruct
public void init()
{
}
/**
* 在容器中被销毁对象时调用
*/
@PreDestroy
public void destroy()
{
}
}
10.4 BeanPostProcessor后置处理器
@Component
public class SmartBeanPostProcessor implements BeanPostProcessor
{
/**
* 在对象执行初始化方法之前调用
*
* @param bean
* Bean对象
* @param beanName
* Bean名称
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
{
// 在这里可以对bean进行包装或替换,再返回
return bean;
}
/**
* 在对象执行初始化方法之后调用
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
{
return bean;
}
}
11. @Value注解
字面值 @Value("")
SpEl @Value("#{}")
引用外部值 @Value("${}")
12. @PropertySource注解,用于引入外部配置文件
@Configuration
@PropertySource(value = { "classpath:main.properties" }, encoding = "UTF-8", ignoreResourceNotFound = true)
public class ApplicationConfig
{
@Bean(value = "car")
public Car car(@Value("${brand}") String brand)
{
return new Car(brand);
}
}
实际应用中也可以通过ConfigurableEnvironment来获取引入的配置信息
ConfigurableEnvironment environment = applicationContext.getEnvironment();
environment.getProperty("brand");
13. @Autowired自动装配,可放在属性上,也可以放在set方法,构造方法,参数上,可通过required设置是否是必须的
优先安装类型匹配查找,如果查找到多个相同类型的组件,再通过属性名作为Id查找
可结合@Qualifier注解明确指定要装配的组件
@Service
public class UserService
{
@Autowired(required = true)
public UserDao userDao;
}
@Service
public class UserService
{
@Autowired
@Qualifier("userDao")
public UserDao userDao;
}
容器中有多个相同类型的组件时,可以通过@Primary组件来设置该组件优先被装配
@Configuration
public class ApplicationConfig
{
/**
* 当容器中有多个Car类型的组件时,使用@Autowired注入Car类型的组件且没有使用@Qualifier明确指定装配Id时,优先装配这个Car
*/
@Primary
@Bean(value = "car")
public Car car()
{
return new Car();
}
}
14. Spring还支持@Resource(JSR250规范)和@Inject(JSR330规范)注解
14.1 @Resource默认使用名称匹配装配,可以通过name值来指定装配的组件,但不支持@Primary和required = true/false特性
@Service
public class UserService
{
@Resource(name = "userDao")
public UserDao userDao;
}
14.2 @Inject和@Autowired一样,但是不能配置required = true/false
需要以下导入jar包才能支持
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
15. Aware接口
ApplicationContextAware
BeanFactoryAware
BeanNameAware
EmbeddedValueResolverAware
EnvironmentAware
MessageSourceAware
16. @Profile指定在哪种环境下注入特定的组件,其value值默认default,具有排他性
@Profile也可放在类上
@Configuration
public class ApplicationConfig
{
@Bean("develop")
public DataSource dataSource(String password)
{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
return dataSource;
}
@Bean("test")
public DataSource dataSource(String password)
{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
return dataSource;
}
}
可通过启动参数指定profile的值-Dspring.profiles.active=develop
或通过代码方式切换profile的值,并且可以同时设置多个profile值
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApplicationConfig.class);
applicationContext.getEnvironment().setActiveProfiles("develop", "test");
applicationContext.register(ApplicationConfig.class);
applicationContext.refresh();
二、AOP部分
在程序运行期间将某段代码切入到指定方法的指定位置并运行的编程方式
需要使用@EnableAspectJAutoProxy开启支持AOP注解
@Configuration
@EnableAspectJAutoProxy
public class ApplicationConfig
{
}
经典的日志切面
@Repository
public class Calculator
{
public int add(int i, int j)
{
return i + j;
}
public int sub(int i, int j)
{
return i - j;
}
public int multi(int i, int j)
{
return i * j;
}
public int div(int i, int j)
{
return i / j;
}
}
@Component
@Aspect
public class LogAspect
{
/**
* 切点表达式
*/
@Pointcut("execution(com.realjt.spring.annotation.dao.Calculator.*(..))")
public void pointCut()
{
}
/**
* 前置通知,在目标方法执行前执行
*/
@Before("pointCut()")
public void before(JoinPoint joinPoint)
{
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("Before method = " + methodName + ", args = " + args);
}
/**
* 后置通知,在目标方法执行后执行,无论是否发生异常
*/
@After("pointCut()")
public void after(JoinPoint joinPoint)
{
String methodName = joinPoint.getSignature().getName();
System.out.println("After method = " + methodName);
}
/**
* 返回通知,在目标方法正常执行返回后执行,可以得到返回值
*/
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result)
{
String methodName = joinPoint.getSignature().getName();
System.out.println("AfterReturning method = " + methodName + ", result = " + result);
}
/**
* 异常通知,在目标方法执行发生异常后执行
*/
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception)
{
String methodName = joinPoint.getSignature().getName();
System.out.println("AfterThrowing method = " + methodName + ", exception = " + exception);
}
/**
* 环绕通知,手动推进目标方法执行,并处理异常
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable
{
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
Object result = null;
try
{
// 前置通知
System.out.println("Before method = " + methodName + ", args = " + args);
result = joinPoint.proceed();
// 返回通知
System.out.println("AfterReturning method = " + methodName + ", result = " + result);
}
catch (Throwable e)
{
// 异常通知,这里需要决定异常是吞掉还是继续向上抛出
System.out.println("AfterThrowing method = " + methodName + ", exception = " + e);
throw e;
}
finally
{
// 后置通知
System.out.println("After method = " + methodName);
}
return result;
}
}
三、声明式事务
需要用@EnableTransactionManagement注解开启支持事务
@Configuration
@EnableTransactionManagement
public class ApplicationConfig
{
/**
* 数据源配置
*/
@Bean
public DataSource dataSource() throws Exception
{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("");
dataSource.setJdbcUrl("");
dataSource.setUser("");
dataSource.setPassword("");
return dataSource;
}
/**
* 事务管理器
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource)
{
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
在需要事务的方法上添加@Transactional注解
主要参数
transactionManager 指定事务管理器
propagation 事务传播行为,当事务方法被另一个事务方法调用时,指定事务的应该如何传播,例如是维持在现有事务中执行还是新开启一个事务
isolation 事务隔离级别
timeout 超时时间
readOnly 是否只读
rollbackFor 针对哪些异常进行回滚
noRollbackFor 针对哪些异常不进行回滚
propagation 事务传播行为
Propagation.REQUIRED 默认值,已有事务,则在这个事务中运行,否则新开一个事务继续运行
Propagation.REQUIRES_NEW 必须新开启一个事务,并在这个新开启的事务中运行
Propagation.SUPPORTS 如果有事务则运行则事务中,否则可以不在事务中运行
Propagation.NOT_SUPPORTED 此方法不应该在事务中运行,如果有事务,则将该事务挂起
Propagation.MANDATORY 此方法必须运行在事务中,如果没有事务,则抛出异常
Propagation.NEVER 此方法不应该运行在事务中,如果有事务,则抛出异常
Propagation.NESTED 如果有事务,就在这个事务的嵌套事务内运行,否则就新开启一个事务,并在这个新开启的事务中运行
数据库并发问题
脏读
Session_1 将某条数据的age值从20改成了30
Session_2 读取了Session_1更新后的值30
Session_1 回滚,age值恢复到了20
Session_2 读取到的30是一个无效的值
不可重复读
Session_1 读取了某条数据的age值为20
Session_2 将age值修改为30
Session_1 再次读取age值为30,两次读取的值不一样
幻读
Session_1 读取了USER表中的一部分数据
Session_2 向USER表中插入了新的行
Session_1 再次读取USER表时,多了一些行
事务隔离级别
1.读未提交
2.读已提交
3.可重复读
4.串行化
隔离级别 | 脏读 | 不看重复读 | 幻读 |
Read UnCommitted | 有 | 有 | 有 |
Read Committed | 无 | 有 | 有 |
Repeatable Read | 无 | 无 | 有 |
Serializable | 无 | 无 | 无 |
隔离级别 | Oracle | MySql |
Read UnCommitted | 不支持 | 支持 |
Read Committed | 支持(默认) | 支持 |
Repeatable Read | 不支持 | 支持(默认) |
Serializable | 支持 | 支持 |
isolation 事务隔离级别
Isolation.DEFAULT
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
四、Spring扩展
1. BeanPostProcessor 在Bean初始化前后进行拦截工作
@Component
public class SmartBeanPostProcessor implements BeanPostProcessor
{
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
{
// 在执行Bean初始化方法前执行
// 这里可以修改Bean实例的属性
// 也可以新初始化任何类型的实例返回,从而实现替换已有实例
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
{
// 在执行Bean初始化方法后执行
// 这里可以修改Bean实例的属性
// 也可以新初始化任何类型的实例返回,从而实现替换已有实例
return bean;
}
}
2. BeanFactoryPostProcessor 在BeanFactory标准初始化后执行postProcessBeanFactory方法工作,
所有的Bean定义信息已经加载到beanFactory中,但所有Bean的实例还未创建
@Component
public class SmartBeanFactoryPostProcessor implements BeanFactoryPostProcessor
{
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
// 得到Bean定义信息数量
int count = beanFactory.getBeanDefinitionCount();
// 得到所有Bean定义的名字
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
}
}
3. BeanDefinitionRegistryPostProcessor 为BeanFactoryPostProcessor的子接口,postProcessBeanDefinitionRegistry方法在Bean定义信息
将要被加载时执行,此时Bean实例还未创建,先于postProcessBeanFactory方法执行,方法参数BeanDefinitionRegistry对象为Bean定义的存储中心,
BeanFactory就是按照BeanDefinitionRegistry中保存的Bean定义信息来创建Bean实例的
@Component
public class SmartBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor
{
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException
{
// Bean定义数量
int count = registry.getBeanDefinitionCount();
// 注册一个Bean定义信息
RootBeanDefinition beanDefinition = new RootBeanDefinition(User.class);
registry.registerBeanDefinition("user", beanDefinition);
// 移除一个Bean定义信息
registry.removeBeanDefinition("user");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
}
}
4. ApplicationListener 应用监听器,通过回调方式监听Spring IOC容器中发布的事件,泛型为要监听的事件类型,
4.1 实现ApplicationEvent接口
例如ContextRefreshedEvent为容器启动完成事件,ContextClosedEvent为容器关闭事件
可以通过AbstractApplicationContext.publishEvent(ApplicationEvent)来发布一个自定义事件
@Component
public class SmartApplicationListener implements ApplicationListener<ApplicationEvent>
{
@Override
public void onApplicationEvent(ApplicationEvent event)
{
// 收到事件
}
}
4.2 使用@EventListener注解
@Service
public class UserService
{
@EventListener(classes = { ApplicationEvent.class })
public void listen(ApplicationEvent event)
{
// 收到事件
}
}