Spring 框架的特点
Spring 框架的主要模块
Core Container(核心容器)
是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成
Core(spring-core):封装了Spring框架的底层部分,包括资源访问、类型转换以及Spring 框架基本的核心工具类
Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,也可以在自己的应用系统中使用这些工具类
Beans(spring-beans):提供了框架的基础部分,包括访问配置文件、控制反转(IOC)和依赖注入(DI)
SpEL(spring-expression):提供了强大的表达式语言支持,用于在运行时查询和处理对象图。该语言支持设置和获取属性值;属性赋值,方法调用,访问数组的内容,收集和索引器,逻辑和算术运算,命名变量,并从Spring的IOC容器的名字对象检索,它也支持列表选择和投影以及常见的列表聚合
Context(spring-context):上下文模块,建立在 Core 和 Beans 模块的基础之上,集成Beans模块并添加资源绑定、数据验证、国际化、Java EE支持、容器生命周期、事件传播等
为Spring 核心提供了大量扩展。可以找到使用Spring ApplicationContext特性时所需的全部类,JDNI 所需的全部类,instrumentation组件以及校验Validation 方面的相关类。
依赖关系:
Data Access/Integration(数据访问与集成)
包含模块:JDBC、ORM、OXM、JMS、Transaction
JDBC(spring-jdbc):包含对Spring 对JDBC 数据访问进行封装的所有类
ORM(spring-orm):提供了与“对象-关系”映射框架集成的API,包括 JPA、JDO、Hibernate、MyBatis等。
OXM(spring-oxm):提供了 Object/XML 映射的抽象层实现,如 JAXB、Castor、XMLBean、JiBX、XStream
将 java对象映射成为XML数据,或者将 XML 数据映射成java对象
JMS(spring-jms):java消息服务,提供了一套“消息生产者、消息消费者”模板,JMS用于两个应用程序之间,或者分布式系统中发送消息,进行异步通信
Transaction(spring-tx):事务控制,为JDBC、Hibernate、JDO、JPA、Beans等提供的一致的声明式和编程式事务管理支持
依赖关系:
Web
包括模块:Web、servlet、websocket、portlet
Web(spring-web):提供了基本web集成特性,例如:多文件上传、使用 Servlet 监听器的 IOC 容器初始化以及 web 应用上下文
包含Web 应用开发时,用到Spring 框架时所需的核心类,包括自动载入Web ApplicationContext 特性的类、Struts 与JSF 集成类、文件上传的支持类、Filter 类和大量工具辅助类
servlet(spring-webmvc):提供了 SpringMVC Web 框架实现。SpringMVC 提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等以及一套非常简易的 JSP 标签
包含Spring MVC 框架相关的所有类,包括框架的Servlets,Web MVC框架,控制器和视图支持
如果应用使用了独立的MVC 框架,则无需这个JAR 文件里的任何类
WebSocket:提供了简单的接口,用户只需要实现接口就可以快速搭建 websocket server,从而实现双向通讯
Portlet(spring-webmvc-portlet):提供了Portlet环境中MVC实现
依赖关系:
AOP、aspects、spring-instrument 、messaging
aop部分包含4个模块
AOP(spring-aop):提供了面向切面编程,提供了比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态把这些功能添加到需要的代码中
Aspects(spring-aspects):提供与AspectJ的集成,是一个功能强大且成熟的面向切面编程框架
以便可以方便的将面向方面的功能集成进IDE中,比如Eclipse AJDT
InStrumentation(spring-instrument):检测,提供了类工具的支持和类加载器的实现
Messaging(消息处理):提供了对消息传递体系结构和协议的支持
依赖关系:
Test:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能。
Spring Framework 的API体系异常庞大,暂时只介绍 BeanFactory 和 ApplicationContext:
BeanFactory
ApplicationContext
BeanFactory 和 ApplicationContext 的区别:
beanFactory 主要是面向 Spring 框架的基础设施,也就是供 spring 自身内部调用
Applicationcontext 主要面向 Spring 的使用者
BeanFactroy 是在第一次使用到某个Bean(调用getBean()方法)时,才对该Bean进行加载实例化
ApplicationContext 是在容器启动时,一次性创建并加载了所有的Bean
Spring Boot 也是 Spring 生态中一个极其重要的项目,Spring Boot 是对 Spring Framework 的扩展,也可以说它是一个服务于框架的框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程
Spring Boot 的服务范围是简化配置文件,它消除了设置 Spring 应用程序所需的XML配置,即尽可能的自动配置 Spring 应用
Spring Boot 直接嵌入了 Tomcat、Jetty 或 Undertow(无需部署WAR文件),并且提供生产就绪功能,例如指标、运行状况检查和外部化配置等等,为更快和更高效的开发应用程序铺平了道路
IOC(控制反转)是一种设计思想,目的是指导设计出更加松耦合的程序:
Spring IOC 容器 是对 IOC 思想的一种实现:
Spring 的 IOC容器 工作原理:
当 Spring 的 IOC 容器加载时,会读取配置文件中的诸多 bean 配置
根据 bean 的 class 的值寻找对应的 Class 字节码文件
通过反射技术,创建出一个个对象
创建的对象会被存放到内部的一个 Map 结构中,等待被使用
当需要使用具体的对象时就无须手动创建,而是直接从 Spring 的IOC容器中
案例:通过Spring中内置的容器获取对象
操作步骤:
创建工程导入依赖
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.6.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
创建Dao接口和实现类(略)
创建Spring配置文件
标签,定义对象id和实现类全类名在resource目录下创建applicationContext.xml配置文件
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///spring"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"/>
bean>
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner"/>
bean>
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
bean>
beans>
测试
import cn.test.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 测试从容器中获取对象
*/
public class UserDaoTest {
public static void main(String[] args) {
//1、根据配置文件获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//2、调用容器的方法,获取对象
UserDao userDao = (UserDao)ac.getBean("userDao");
//3、测试
userDao.save();
}
}
执行过程分析
方式1:默认无参构造函数
IOC容器通过反射调用,根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。
<bean id="userDao" class="cn.test.dao.impl.UserDaoImpl">bean>
方式2:工厂模式创建对象
在Spring中还可以通过工厂模式来创建对象。工厂模式又分两种:
(1)java代码编写工厂类
public class FactroyCreateBean {
// 静态工厂
public static UserDao createUserDao(){
return new UserDaoImpl();
}
// 实例工厂
public UserDao createUserDaoSimple(){
return new UserDaoImpl();
}
}
(2)xml配置文件配置
<bean id="userDao1" class="cn.test.factory.FactroyCreateBean" factory-method="createUserDao"/>
<bean id="factroyCreateBean" class="cn.test.factory.FactroyCreateBean"/>
<bean id="userDao2" factory-bean="factroyCreateBean" factory-method="createUserDaoSimple"/>
Bean对象生命周期指的是Bean创建到销毁的这么一段时间。
粗粒度生命周期:
spring中单例对象的生命周期为:
出生:IOC容器加载时出生
存活:IOC容器运行时存活
死亡:IOC容器销毁时死亡
spring中多例对象的生命周期为:
出生:使用对象时出生
存活:一直存活
死亡:由java垃圾回收器负
细粒度生命周期:
出生过程
实例化bean对象【IOC】
为对象属性赋值【DI】
处理实现的Aware接口
① 如果这个Bean已经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String beanId) 方法,此处传递的就是Spring配置文件中Bean的id值。
② 如果这个Bean已经实现了 BeanFactoryAware 接口,会调用它实现的 setBeanFactory() 方法,传递的是Spring工厂自身。
③ 如果这个Bean已经实现了 ApplicationContextAware 接口,会调用 setApplicationContext(ApplicationContext) 方法,传入Spring上下文。
通过 BeanPostProcessor 接口的 postProcessBeforeInitialization 方法对bean对象进行预处理
通过 InitializingBean 接口的 afterPropertiesSet 方法对bean对象进行处理
通过指定 init-method 方法对bean对象进行处理
通过 BeanPostProcessor 接口的 postProcessAfterInitialization 方法对bean对象进行后处理,这一步bean对象已经彻底创建成功了,可以做一些类似缓存的工作
死亡过程
生命周期流程图
测试案例:
public class UserDaoImpl implements UserDao, BeanNameAware, BeanFactoryAware,ApplicationContextAware, InitializingBean, DisposableBean {
public UserDaoImpl() {
System.out.println("IOC");
}
private Integer id;
public void setId(Integer id) {
System.out.println("DI");
this.id = id;
}
@Override
public void setBeanName(String s) {
System.out.println("BeanNameAware:"+s);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("BeanFactoryAware");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("ApplicationContextAware");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean的afterPropertiesSet");
}
public void initMethod(){
System.out.println("init-method");
}
@Override
public void save() {
System.out.println("保存成功!");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean的destroy");
}
public void destroyMethod(){
System.out.println("destroy-method");
}
}
public class MyBeanProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor的before");
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor的after");
return bean;
}
}
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" init-method="initMethod" destroy method="destroyMethod">
<property name="id" value="1"/>
bean>
<bean class="com.itheima.processor.MyBeanProcessor"/>
beans>
// 测试类
public class UserDaoImplTest {
@Test
public void save() {
ClassPathXmlApplicationContext ac = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = ac.getBean(UserDao.class);
userDao.save();
ac.close();
}
}
参考:https://blog.csdn.net/weixin_38676276/article/details/90382350
singleton:单例。在Spring IOC容器中仅存在一个共享的Bean实例。默认值
当spring创建applicationContext容器的时候,spring会初始化所有的该作用域实例,加上lazy-init则可以避免预处理
prototype:原型(多例)
每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()相当于执行new XxxBean()
创建后spring将不再对其管理
(下面是在web项目下才用到的作用域)
request:仅在当前HTTP request内有效
每次HTTP请求都会创建一个新的Bean
spring创建后会继续监听,当处理请求结束后销毁实例
session:仅在当前HTTP Session内有效
同一个HTTP Session共享一个Bean,不同Session使用不同Bean
spring创建后会继续监听,当HTTP Session最终被废弃时,在该HTTP Session作用域内的bean也会被废弃掉
global-session:全局的web域。类似于servlet中的application。一般用于Portlet应用环境
id:唯一标志(获取的时候,调用容器的getBean(“id”))
class:实现类的全限定类名
scope:对象作用域(singleton(默认) | prototype | request | session | global-session)
init-method:对象创建成功之后,指定的初始化方法
destroy-method:容器关闭对象销毁之前,执行的销毁方法
只有在scope=singleton(单例)的时候,才有效
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype"
init-method="init" destroy-method="destroy"/>
依赖注入:Dependency Injection(DI)。 它是 spring 框架核心 IOC 的具体实现。在编写程序时, 通过控制反转,把对象的创建交给了 spring,然后在代码中需要对象时,进行依赖对象的注入。
IOC 具有两个功能:依赖查找,依赖注入
本质:向对象中的私有属性赋值
构造方法
set方法调用
public class UserServiceImpl implements UserService {
private String name;
private Integer age;
public UserServiceImpl(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public void save() {
System.out.println(name + age);
}
}
<bean id="userService" class="cn.test.service.impl.UserServiceImpl">
<constructor-arg name="age" value="12">constructor-arg>
<constructor-arg name="name" value="王者荣耀">constructor-arg>
bean>
提供属性的set方法
在spring配置文件中,通过bean结合property配置set方法调用
<bean id="book" class="com.itheima.spring.Book">
<property name="name" value="程序员" />
<property name="price" value="1" />
<property name="publishDate" ref="date" />
bean>
给类中的集合成员传值,它用的也是 set方法注入的方式,只不过变量的数据类型都是集合。 这里介绍注入数组、List、Set、Map、Properties。
(1) 注入数组数据
配置set方法
public class UserServiceImpl implements UserService {
/**
* 注入数组(array数组,list集合,set集合)
*/
private String [] names;
private List<String> lists;
private Set<String> sets;
public void setNames(String[] names) {
this.names = names;
}
public void setLists(List<String> lists) {
this.lists = lists;
}
public void setSets(Set<String> sets) {
this.sets = sets;
}
@Override
public void save() {
}
}
spring配置文件
<bean id="book" class="com.itheima.spring.Book">
<property name="list">
<list>
<value>1value>
<value>2value>
list>
property>
<property name="set">
<set>
<value>3value>
<value>4value>
set>
property>
<property name="array">
<array>
<value>5value>
<value>6value>
array>
property>
<property name="map">
<map>
<entry key="7" value="7-1" />
<entry key="8" value="8-1" />
map>
property>
<property name="properties">
<props>
<prop key="9">9-1prop>
<prop key="10">10-1prop>
props>
property>
bean>
注解和 xml 是Spring提供的两种配置形式,二者的功能是完全一样的,都是要降低程序间的耦合
注解的好处是配置简单,xml的好处是修改配置不用改动源码,企业开发中两种方式灵活使用
Spring中注解配置
注意:在spring中使用注解开发,需要开启注解的功能支持(ioc注解,aop注解,事务注解)
注解和xml配置对应关系:
xml配置 | 注解配置 | 说明 |
---|---|---|
< bean id=“” class=“” > | @Component @Controller @Service @Repository | 将类的实例化对象放入Spring容器管理 |
< property name=“” ref=“”> | @Autowired @Qualifier @Resource | 从Spring容器中获取对象注入属性 |
< property name=“” value=“”> | @Value | bean的简单属性注入 |
< bean scope=“”> | @Scope | 控制bean的作用范围 |
< bean init-method=“init” destroy method=“destory” /> | @PostConstruct @PreDestroy | bean创建之后和销毁之前分别调用的方法 |
开启包扫描(即 开启对 IOC 注解的支持):
方式1:xml配置方式 开启包扫描
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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="cn.test">context:component-scan>
beans>
方式2:配置类注解方式 开启包扫描
/**
* 1、声明配置类:@Configuration
* 2、开启包扫描:@ComponentScan
*/
@Configuration
@ComponentScan(basePackages = "cn.test")
public class SpringConfig {
}
IOC 注解(对象创建的注解)说明:
标注在想要被 IOC 容器管理的类上,表明创建对象交给容器管理
当 Spring 容器启动时,根据包扫描配置自动的扫描到 IOC 注解,反射创建注解标注的对象,存入容器,委托容器管理
默认存入容器的 id(唯一标识)为当前类名首字母小写。可以通过 value 属性自定义存入容器中对象的 id
一共有四个:
**@Controller**:一般标注在表现层(web层)的类上
**@Service**: 一般标注在业务层(service层)的类上
**@Repository**: 一般标注在持久层(dao层)的类上
**@Component**:组件, 非三层模式范围的类上使用
相当于xml中的如下配置
<bean id="userDaoImpl" class="cn.test.dao.impl.UserDaoImpl">bean>
@Scope : 在类上配置对象的作用域
通过value属性指定作用范围(singleton|prototype),默认为 singleton(单例)
@PostConstruct : 配置对象创建后的触发动作
当对象创建完成之后,自动执行的方法。相当于 xml 配置文件中的 init-method
@PreDestroy :配置对象销毁前的触发动作(仅在 Scope = singleton 时有效)
容器关闭,对象销毁之前执行的方法。相当于 xml 配置文件中的destory-method
示例:
@Repository
@Scope(value="singleton")
public class UserDaoImpl implements UserDao {
public UserDaoImpl() {
System.out.println("创建UserDaoImpl");
}
@Override
public void save() {
System.out.println("调用dao保存数据");
}
//初始化方法:在对象创建完成之后执行
@PostConstruct
public void init() {
System.out.println("执行init方法");
}
//销毁方法:在容器关闭对象销毁之前执行
@PreDestroy
public void destory() {
System.out.println("执行destory方法");
}
}
DI 注解都相当于直接给属性赋值,而无需借助于 set 方法或构造方法
使用方式1:标注在属性上
直接给属性赋值(通过 @Autowired 依赖注入,不需要配置 set 方法)
默认是通过 by Type(根据类型,即接口类型)的形式从 IOC 容器中查找对象并给属性注入值
如果 IOC 容器中存在多个与属性同类型的对象(一个接口有多个实现类),
则会按照属性名(by Name)作为唯一标志从容器中查找对象并给属性注入值
也可以和 @Qualifier 注解共同使用,指定唯一标志从容器中查找对象并给属性注入值
注意:@Qualifier 只能结合 @Autowired 一起使用
使用方式2:标注在方法上
@Autowired 使用注意事项
只能在被 Spring 容器托管(标注了 @Controller 等 IOC 注解)的类中使用 @Autowired 注解
自动注入与权限修饰符无关,即使是 private 修饰的字段也可以自动注入
默认情况下,使用 @Autowired 注解进行自动注入的属性一定要被装配( Spring 容器托管)
如果在容器中找不到该类型的bean 进行注入,就会报错
如果允许不被装配,可以将 @Autowired 的 required 属性为 false
@Autowired 是基于类型的注入,如果当前类型属性在容器中只有一个 Bean,那么属性名不限制,但一般建议遵循类名首字母小写的规则
如果当前属性类型在容器中有个多个Bean,那么必须要通过属性名或者同时标注 @Qualifier 注解指定 Bean name
示例:
@Service
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier(value = "userDao2") // 从容器中获取指定唯一标志的对象
private UserDao userDao;
@Override
public void save() {
userDao.save();
}
}
配置在属性上。可用于简单数据类型的注入,相当于 < property name="" value="" >
,但通常不这么使用
一般用于解析 properties 配置文件或注册中心的配置文件中的内容
示例:
(1) 配置jdbc.properties文件
jdbc.username=root
jdbc.password=root
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///heima23
(2) 配置文件交给Spring
修改spring配置文件applicationContext.xml
<context:property-placeholder location="jdbc.properties">context:property-placeholder>
(3) 属性注入
@Repository(value = "userDao")
public class UserDaoImpl implements UserDao {
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
public void save() {
System.out.println(username);
System.out.println(password);
System.out.println(driver);
System.out.println(url);
System.out.println("调用dao11111完成保存");
}
}
jdk提供的依赖注入的注解,此注解在jdk9及以上版本已经移除
示例:
@Service
public class UserServiceImpl implements UserService {
@Resource(name = "userDao1")
private UserDao userDao;
@Override
public void save() {
userDao.save();
}
}
配置类中的常用注解:
@Configuration:标注在类上,声明该类为 Spring 配置类
Spring 在启动的时候会自动扫描并加载所有配置类,配置 Spring 容器(应用上下文),将配置类中的 Bean 放入容器管理
@Bean:标注在 Spring 配置类中的方法上,注册 bean 对象到 IOC 容器
在 Spring 容器启动的时候,自动的扫描并执行所有配置了 @Bean 的方法,并将返回值存入Spring容器
注意:
@ComponentScan:开启包扫描(Spring IOC注解支持),默认扫描当前包及子包下所有类
如果扫描到类上有 IOC 注解,就会把当前类交给 IOC 容器管理,当容器启动时,自动创建对象存入容器
如果扫描到属性上有 DI 注解,则依据依赖注入的规则,给属性注入值
@PropertySource:加载本地 properties 文件交给 Spring 容器管理
@Import:在一个配置类中导入其它配置类的内容
Spring 支持多配置类(配置类的模块),若配置类臃肿,可以拆分配置类,然后在主配置类中引入子配置类(子配置类上不用配置注解)
示例:
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
@Configuration // 声明配置类
@ComponentScan(basePackages = "cn.test") // 开启包扫描,并指定包扫描路径
@PropertySource(value="jdbc.properties") // 通过注解将此文件交给spring容器管理
@Import(value=DataSourceConfig.class) // 引入其他的配置类
public class SpringConfig {
@Bean
public QueryRunner getQueryRunner(DataSource dataSource) {
QueryRunner qr = new QueryRunner(dataSource);
return qr;
}
}
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
// 子配置类
public class DataSourceConfig {
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driver}")
private String driver;
@Bean
public DataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setUrl(url);
dataSource.setDriverClassName(driver);
return dataSource;
}
}
# properties文件
jdbc.username=root
jdbc.password=root
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///heima23
AOP((Aspect Oriented Programming)面向切面编程
Spring AOP 是对 AOP 思想的一种实现,将那些与业务无关,却为业务模块所共用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP还能解决一些系统层面上的问题,比如日志、事务、权限等。
Spring 底层是通过动态代理的方式实现的 AOP。同时支持 jdk 和 cglib 动态代理,Spring会根据被代理的类是否有接口自动选择代理方式:
如果有接口,就采用 JDK 动态代理(也可以强制使用 CGLIB 动态代理)
没有接口就采用用 CGLIB 动态代理的方式
Spring AOP工作流程:
开发阶段分别开发,运行阶段组装运行
开发阶段(开发者完成)
开发共性功能,制作成增强
开发非共性功能,制作成切点
在配置文件中,声明切点与增强之间的关系,即切面
运行阶段/容器启动阶段(AOP完成)
目标对象(target ):需要被增强的对象,即切入点方法所在对象
连接点(jointPoint):被代理对象中的方法
切点(pointCut):按照AOP的规则去切(匹配)连接点,匹配出来的就叫切点,即 需要被增强的方法
切入点表达式 : execution(方法的修饰符 返回值类型 包名.类名.方法名(参数))
切点表达式的作用:定义一组规则,用于在连接点(所有方法)中挑选切入点(被增强方法)
支持通配符:
* // 一个或多个任意字符
.. // 配置到参数上,标识任意参数
// 切入点的全统配方式,表示整个项目中所有类中所有方法都要被增强
execution(* *..*.*(..))
// 最常用的切入点表达式:精确到具体包路径
execution(* com.test.controller.*.*(..))
增强(通知)(advice):一个具体的增强功能。增强方法在切点方法的什么位置上执行
Spring AOP 通知(增强)分为5种类型:
前置通知(before):在切点运行之前执行
后置通知(after-returning):在切点正常运行结束之后执行
异常通知(after-throwing):在切点发生异常的时候执行
最终通知(after):在切点的最终执行
环绕通知(around):一种特殊的通知,在一个方法中定义多个增强逻辑(和手动定义动态代理类似)
环绕通知的自定义方法:
代理对象(proxy ):目标对象被增强后成为代理对象
切面(aspect):是一个设计概念,包含了Advice 和 Pointcut。切面 = 切入点 + 增强
Advice定义了Aspect的任务和什么时候执行,Pointcut定义在哪里切入
即 Aspect定义了一个什么样的增强功能,切入到哪个核心方法的哪个位置
织入(Weaving):一个动作。将增强代码加入到核心代码的过程就叫织入
xml文件 配置切入点
xml文件 配置切面
xml文件 配置通知类型
xml文件 配置AOP示例
<aop:config>
<aop:pointcut id="pt" expression="execution(* cn.test.dao.impl.*.*(..))"/>
<aop:aspect ref="logger">
<aop:before method="before" pointcut-ref="pt">aop:before>
<aop:after-returning method="afterReturning" pointcut-ref="pt">aop:after-returning>
<aop:after-throwing method="afterThrowing" pointcut-ref="pt">aop:after-throwing>
<aop:after method="after" pointcut-ref="pt">aop:after>-->
<aop:around method="around" pointcut-ref="pt">aop:around>
aop:aspect>
aop:config>
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 切面类:此类中具有所有的增强代码逻辑
*/
public class Logger {
/**
* 前置通知:执行被代理对象方法之前执行
* 方法:无参数,无返回值
*/
public void before() {
System.out.println("执行前置通知");
}
/**
* 后置后置:正常执行被代理对象方法获取返回值之后执行
*/
public void afterReturning() {
System.out.println("执行后置通知");
}
/**
* 异常通知:执行代码抛出异常的时候执行
*/
public void afterThrowing() {
System.out.println("执行异常通知");
}
/**
* 最终通知:finally代码块中执行的逻辑
*/
public void after() {
System.out.println("执行最终通知");
}
/**
* 环绕通知:在一个方法中定义多个增强逻辑
*/
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object obj = null;
try {
System.out.println("执行前置通知");
//执行被代理对象方法
obj = pjp.proceed();
System.out.println("执行后置通知");
}catch (Exception e){
System.out.println("执行异常通知");
}finally {
System.out.println("执行最终通知");
}
return obj;
}
AOP注解版有两种:
xml配置文件
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="cn.test">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* 配置类
*/
@Configuration
@ComponentScan(basePackages = "cn.test")
@EnableAspectJAutoProxy
public class SpringConfig {
}
@Aspect:标注在自定义类上,声明切面类
注意:该切面类同时需要标注 IOC注解(@Component),交给 Spring 容器管理
在切面类中的通知(增强)方法上通过注解配置通知类型:
通知注解的属性:
value / argNames 属性:切入点表达式 或 被 @Pointcut 标注的方法名()
@Around("pt()")
public Object around(ProceedingJoinPoint pjp)
@Pointcut:标注在切面类中的空的方法上,抽取公共的切入点表达式
@Pointcut(value="execution(* cn.test.service.impl.*.*(..))")
public void pt() {}
/**
* 切面类:此类中具有所有的增强代码逻辑
*/
@Component
@Aspect
public class Logger {
/**
* 前置通知:执行被代理对象方法之前执行
* 方法:无参数,无返回值
*/
//@Before(value="execution( * cn.test.dao.impl.*.*(..) )")
public void before() {
System.out.println("执行前置通知");
}
/**
* 后置通知:正常执行被代理对象方法获取返回值之后执行
*/
//@AfterReturning(value="execution( * cn.test.dao.impl.*.*(..) )")
public void afterReturning() {
System.out.println("执行后置通知");
}
/**
* 异常通知:执行代码抛出异常的时候执行
*/
// @AfterThrowing("execution( * cn.test.dao.impl.*.*(..) )")
public void afterThrowing() {
System.out.println("执行异常通知");
}
/**
* 最终通知:finally代码块中执行的逻辑
*/
//@After("execution( * cn.test.dao.impl.*.*(..) )")
public void after() {
System.out.println("执行最终通知");
}
/**
* 环绕通知:在一个方法中定义多个增强逻辑
*/
//@Around("execution( * cn.test.dao.impl.*.*(..) )")
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object obj = null;
try {
System.out.println("1执行前置通知");
//执行被代理对象方法
obj = pjp.proceed();
System.out.println("2执行后置通知");
}catch (Exception e){
System.out.println("3执行异常通知");
}finally {
System.out.println("4执行最终通知");
}
return obj;
}
// @Pointcut:抽取公共的切入点表达式
@Pointcut(value="execution(* cn.test.service.impl.*.*(..))")
public void pt() {}
}
参考:https://blog.csdn.net/qq_45193304/article/details/109430545
SpringAOP四大通知(前置、后置、异常、最终通知)的执行顺序非常复杂,跟Spring版本、配置先后顺序、配置方式(xml | 注解)都有关,故建议使用环绕通知!
xml方式正确顺序配置aop的执行顺序:
try {
// 前置通知(before) : 在切点运行之前执行
// 切点执行,被代理对象方法调用
// 后置通知(after-returning): 在切点正常运行结束之后执行
}catch (Exception e){
// 异常通知(after-throwing): 在切点发生异常的时候执行
}finally {
// 最终通知(after): 在切点的最终执行
}
注解方式正确顺序配置aop的执行顺序:
try{
try{
//@Before -- 首先执行前置通知
method.invoke(..); -- 然后执行切入点方法
}finally{
//@After -- 再而肯定会执行最终通知 --- 注解配置的注意点
}
//@AfterReturning -- 如果没有异常,则继续执行后置通知
return; -- 返回结果
}catch(){
//@AfterThrowing -- 如果有异常,则执行异常通知
}
Spring支持两种事务管理方式:编程式事务和声明式事务。官方大力推荐使用声明式事务。
编程式事务:将业务代码和事务代码放在一起书写,它的耦合性太高,开发中不使用
声明式事务:
声明式事务是建立在 AOP 的基础上的。
其本质是在方法前后进行拦截,在方法开始之前创建事务,在方法结束后根据执行情况提交或回滚事务。
声明式事务的优点:可以将事务代码和业务代码完全分离开发,然后通过配置的方式实现运行时组装运行。
声明式事务的不足:只能作用到方法级别,无法像编程式事务那样可以作用到代码块级别
Spring中的事务控制主要就是通过这三个API实现的:
三者的关系:事务管理器 通过读取 事务定义参数 进行事务管理,然后会产生一系列的 事务状态
PlatformTransactionManager 接口:
Spring进行事务管理的一个根接口,使用其实现类做事务管理器(增强的事务处理的功能)
接口方法:
// 获取事务的状态信息
TransactionStatus getTransaction(TransactionDefinition def)
// 提交事务
void commit(TransactionStatus status)
// 回滚事务
void rollback(TransactionStatus status)
常用实现类:
TransactionStatus 接口 :
事务运行的一个实时状态
接口方法
// 是否是新事物
boolean isNewTransaction()
// 是否有回滚点
boolean hasSavepoint()
// 设置为只回滚事务
void setRollbackOnly()
// 是否是只回滚事务
boolean isRollbackOnly()
// 刷新事务状态
void flush()
// 事务是否完成
boolean isCompleted()
TransactionDefinition 接口:
事务隔离级别
Spring中配置事务,支持所有的4中隔离级别
默认值:自动选择当前数据库合适的配置项
// 事务隔离级别相关【不设置事务隔离级别,可能引发脏读、不可重复读、幻读】
ISOLATION_READ_UNCOMMITTED 读未提交 mysq1支持四种,默认可重复度
ISOLATION_READ COMMITTED 读已提交 oracle支持两种(读己提交和串行化),默认是读已提交
ISOLATION_REPEATABLE READ 可重复度
ISOLATION SERIALIZABLE 串行化
**事务传播行为:**描述多个方法嵌套调用时,被调用方法对事务的支持
PROPAGATION_REQUIRED = 0(必须有事务,这是默认值)
如果存在一个事务,则加入到当前事务。如果没有事务则开启一个新的事务。
PROPAGATION_SUPPORTS = 1(支持有事务)
如果存在一个事务,则加入到当前事务。如果没有事务则非事务运行。
PROPAGATION_MANDATORY = 2(强制有事务,自己还不负责创建)
如果存在一个事务,则加入到当前事务。如果没有事务,则抛出异常。
PROPAGATION_REQUIRES_NEW = 3(必须有新的)
总是开启一个新的事务。如果存在一个事务,则将这个存在的事务挂起,重新开启一个新的事务。
PROPAGATION_NOT_SUPPORTED = 4(不支持有事务)
总是非事务地执行,并挂起任何存在的事务。
PROPAGATION_NEVER = 5(强制不要事务,自己还不负责挂起)
总是非事务地执行,如果存在一个活动事务,则抛出异常
PROPAGATION_NESTED = 6(嵌套事务)
如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则开启一个新的事务。
内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。
而内层事务操作失败并不会引起外层事务的回滚
是否只读事务
isReadOnly : true | false(默认)
只读事务: 只能查询,不能增 删 改。只读事务只能用于查询方法
事务超时时长
TIMEOUT_DEFAULT = -1 :事务的超时时间,需要底层数据库支持才能使用此配置,默认值是 -1,代表无限制。
超时时间:使用默认值
基于XML的方式完成声明式事务配置:
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="cn.test">context:component-scan>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root">property>
<property name="password" value="root">property>
<property name="driverClassName" value="com.mysql.jdbc.Driver">property>
<property name="url" value="jdbc:mysql:///heima23">property>
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" read-only="false">tx:method>
<tx:method name="update*">tx:method>
<tx:method name="delete*">tx:method>
<tx:method name="find*" propagation="SUPPORTS" read-only="true">tx:method>
<tx:method name="*">tx:method>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* cn.test.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt">aop:advisor>
aop:config>
beans>
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="cn.test">context:component-scan>
<tx:annotation-driven>tx:annotation-driven>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root">property>
<property name="password" value="root">property>
<property name="driverClassName" value="com.mysql.jdbc.Driver">property>
<property name="url" value="jdbc:mysql:///test">property>
bean>
beans>
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@ComponentScan(basePackages = "cn.test")
@EnableTransactionManagement
public class SpringConfig {
/**
* 创建datasource
*/
@Bean
public DataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setUrl("jdbc:mysql:///test");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
return dataSource;
}
/**
* 创建jdbctemplate
* 1、从容器中获取datasource
* 2、调用方法
*/
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
/**
* 创建事务管理器
*/
@Bean
public PlatformTransactionManager getManager(DataSource dataSource) {
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSource);
return manager;
}
}
@Transactional:配置事务
常用属性:
rollbackFor 属性:设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚
指定单一异常类:@Transactional(rollbackFor=Exception.class)
指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
readOnly 属性:是否只读事务 ( true | false(默认值) )
propagation 属性:事务传播行为 ( SUPPORTS | REQUIRED(默认值) )
transactionManager 属性:多个事务管理器托管在 Spring 容器中时,指定事务管理器的 bean 名称
isolation 属性:设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况
通常使用数据库的默认隔离级别即可,基本不需要进行设置
noRollbackFor 属性:设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚
标注位置说明:
import cn.test.dao.AccountDao;
import cn.test.domain.Account;
import cn.test.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional
//@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
public void transfer(String sourceName, String targetName, float money) throws Exception {
//1、根据账户名称查询两个账户
Account sourceAccount = accountDao.findByName(sourceName); //转出账户
Account targetAccount = accountDao.findByName(targetName); //转入账户
//2、操作金额转换(转出账户扣除金额,转入账户添加金额)
sourceAccount.setMoney(sourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
//3、更新账户
accountDao.update(sourceAccount);
int i=1/0;
accountDao.update(targetAccount);
}
}
当在单元测试中,点击 run 的时候,底层工作的其实是一个运行器,默认是 junit 提供的 ParentRunner 运行器,它是不认识Spring的环境,这也就意味着,它无法从 Spring 的容器中获取bean。
如果想要从 Spring 的容器中获取对象,需要使用 Spring 提供的运行器。
引入依赖
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.1.6.RELEASEversion>
dependency>
@RunWith 注解:设置单元测试的运行器,通过 value 属性指定单元测试运行环境
注:
SpringRunner extends SpringJUnit4ClassRunner.class
使用上 JUnit4.12 或更高版本以上 SpringRunner,SpringJUnit4ClassRunner 都可以使用
但是推荐使用 SpringRunner,final类型,安全
JUnit4.12 以下版本就只能使用 SpringJUnit4ClassRunner
@ContextConfiguration 注解:指定容器的配置信息
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AccountJunitTest {
@Autowired
private AccountService accountService;
//测试保存
@Test
public void testInsert() {
//3、调用方法保存
Account account = new Account();
account.setMoney(100f);
account.setName("小李1");
accountService.saveAccount(account);
}
}
若配置都集中配在了一个applicationContext.xml文件中,当开发人员过多时, 如果所有bean都配置到同一个配置文件中,会使这个文件巨大,而且也不方便维护。针对这个问题,Spring提供了多配置文件的方式,也就是所谓的配置文件模块化。
ApplicationContext act = new ClassPathXmlApplicationContext("beans1.xml","beans2.xml","...");
<import resource="beans1.xml" />
<import resource="beans2.xml" />
<context:property-placeholder location="classpath:db.properties"/>
注意事项:
Spring的bean默认都是单例的,某些情况下,单例是并发不安全的,以Controller举例,问题根源在于,若在Controller中定义成员变量,多个请求来临,进入的都是同一个单例的Controller对象,若对此成员变量的值进行修改操作,则会互相影响,无法达到并发安全(不同于线程隔离的概念)的效果。
抛出问题
多次访问此url,可以看到每次的结果都是自增的,所以这样的代码显然是并发不安全的。
@Controller
public class HomeController {
private int i;
@GetMapping("testsingleton1")
@ResponseBody
public int test1() {
return ++i;
}
}
解决方案
方案1:尽量避免使用成员变量
在业务允许的条件下,尽量避免使用成员变量,使用方法中的局部变量
方案2:使用并发安全的类
Java作为功能性超强的编程语言,API丰富,如果非要在单例bean中使用成员变量,可以考虑使用并发安全的容器,如ConcurrentHashMap、ConcurrentHashSet等,将成员变量(一般可以是当前运行中的任务列表等这类变量)包装到这些并发安全的容器中进行管理即可。
方案3:分布式或微服务的并发安全
如果还要进一步考虑到微服务或分布式服务的影响,方案2便不足以处理了,所以可以借助于可以共享某些信息的分布式缓存中间件如Redis等,这样即可保证同一种服务的不同服务实例都拥有同一份共享信息(如当前运行中的任务列表等这类变量)。
方案4:单例变原型
对web项目,可以Controller类上加注解@Scope(“prototype”)或@Scope(“request”),对非web项目,在Component类上添加注解@Scope(“prototype”)。
优点:实现简单
缺点:很大程度上增大了bean创建实例化销毁的服务器资源开销
不可用方案:线程隔离类ThreadLocal
web服务器默认的请求线程池大小为10,这10个核心线程可以被之后不同的Http请求复用。
ThreadLocal的方式可以达到线程隔离,但还是无法达到并发安全。
描述:
在一些工具类中可能会用到Ioc容器中的对象,而工具类中的成员变量往往是静态的,此时使用@Autowired
注解就会出现NullpointerException
(空指针异常)。
原理剖析:
静态变量、类变量不是对象的属性,而是一个类的属性,所以静态方法是属于类(class)的,普通方法才是属于实体对象(也就是New出来的对象)的,spring注入是在容器中实例化对象,所以不能使用静态方法。
而使用静态变量、类变量扩大了静态方法的使用范围。静态方法在spring是不推荐使用的,依赖注入的主要目的,是让容器去产生一个对象的实例,然后在整个生命周期中使用他们,同时也让testing工作更加容易。
一旦使用静态方法,就不再需要去产生这个类的实例,这会让testing变得更加困难,同时也不能为一个给定的类,依靠注入方式去产生多个具有不同的依赖环境的实例,这种static field是隐含共享的,并且是一种global全局状态,spring同样不推荐这样去做。
**解决方案1:**将@Autowire注解加到set方法上
@Component
public class Test {
private static SessionFactory sessionFactory;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
Test.sessionFactory = sessionFactory;
}
}
**解决方案2:**用@PostConstruct注解
@Component
public class Test {
private static SessionFactory sessionFactory;
@Autowired
private SessionFactory sessionFactory2;
@PostConstruct
public void beforeInit() {
SessionFactory = sessionFactory2;
}
}
**解决方案3:**将@Autowire注解加到构造方法上
@Component
public class Test {
private static SessionFactory sessionFactory;
@Autowired
public Test(SessionFactory sessionFactory) {
Test.SessionFactory = SessionFactory;
}
}
描述:
使用@Autowired注入对象时,一般被注入的类都带有@Coponent、@Controller、@Service 、@repository等注解才可以。注入类和被注入类都被spring所管理,可以完成调用。但是当非容器类(没加以上注解时)使用@Autowired调用容器中的类时,注入对象为空,报空指针异常。
解决方案:
创建工具类BeanUtils,在这个工具类中的getBean可以得到容器中的类,在非容器类中使用
@Component
public class BeanUtils implements ApplicationContextAware {
/**
* 以静态变量保存ApplicationContext,可在任意代码中取出ApplicaitonContext.
*/
private static ApplicationContext context;
/**
* 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
*/
@Override
public void setApplicationContext(ApplicationContext context) {
BeanUtils.context = context;
}
public static ApplicationContext getApplicationContext() {
return context;
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型. 方法返回值的类型由调用者决定
*/
public static <T> T getBean(String name) {
return (T) context.getBean(name);
}
/// 获取当前环境
public String getActiveProfile() {
return context.getEnvironment().getActiveProfiles()[0];
}
}
非容器类中使用容器中的类
public class StationFactory {
Map<String, StationOperation> map = new HashMap<>();
{
map.put("定损中心主管指标表", BeanUtils.getBean("leadDSZXOperation"));
map.put("定损中心员工指标表", BeanUtils.getBean("empDSZXOperation"));
map.put("视频查勘中心主管指标表", BeanUtils.getBean("leadVideoSurveyCenterOperation"));
map.put("视频查勘中心员工指标表", BeanUtils.getBean("empVideoSurveyCenterOperation"));
map.put("视频定损中心主管指标表", BeanUtils.getBean("leadVideoDSCenterOperation"));
}
}