目录
IOC(Inversion of Control)控制反转:
AOP(Aspect Oriented Programming)
概述
spring家族官网:https://spring.io,包括springFramework,spring boot,springcloud,SpringData,SpringSecurity等技术,可以开发web、微服务以及分布式系统,技术优势:
- IOC
- AOP
- 事务处理
- MyBatis、MyBatis-plus、Struts、Struts2、Hibernate
Spring Framework是Spring生态圈中最基础的项目,是其他项目的根基,Spring Framework的版本的变更史
目前最新的4版本架构图
- 核心层 Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块
- AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
- Aspects:AOP是思想,Aspects是对AOP思想的具体实现
- Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
- Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
- Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现
- Web层:这一层主要是SpringMVC框架
- Test层:Spring主要整合了Junit来完成单元测试和集成测试
IOC、IOC容器、Bean、DI
IOC(Inversion of Control)控制反转:
具体实现步骤:
- 容器管什么?主要管理项目中所使用到的类对象,比如(Service和Dao)
- 如何将被管理的对象告知IOC容器?使用配置文件applicationContext.xml
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"/>
- 如何获取到IOC容器从而获取对象?Spring框架提供相应的接口
public class App { public static void main(String[] args) { //获取IOC容器 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); } }
- 如何从容器中获取bean?调用Spring框架提供对应接口中的方法
BookService bookService = (BookService) ctx.getBean("bookService"); bookService.save();
- 使用Spring导入哪些坐标?在pom.xml添加对应的依赖spring-context
- 如何实现依赖注入?
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <!--配置server与dao的关系--> <!--property标签表示配置当前bean的属性 name属性表示配置哪一个具体的属性 ref属性表示参照哪一个bean --> <property name="bookDao" ref="bookDao"/> </bean>
-
如何设置bean的作用范围?Spring创建的bean对象默认都是单例的,prototype为非单例
<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope="singleton"/>
- 哪些bean对象适合交给容器进行管理?表现层对象,业务层对象,数据层对象,工具对象
-
哪些 bean 对象不适合交给容器进行管理?封装实例的域对象,因为会引发线程安全问题,所以不适合
bean的实例化
//静态工厂实例化,class:工厂类的类全名 factory-mehod:具体工厂类中创建对象的方法名
<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
实例工厂实例化
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
bean生命周期
- 初始化容器:1.创建对象(内存分配) 2.执行构造方法 3.执行属性注入(set操作) 4.执行bean初始化方法
- 使用bean :执行业务操作
- 关闭/销毁容器:执行bean销毁方法
方式一:添加初始化和销毁方法后配置生命周期,销毁之前运行destroy方法,需要将ApplicationContext更换成ClassPathXmlApplicationContext后调用close()方法或registerShutdownHook()方法关闭容器
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init"
destroy-method="destory"/>
方式二: 实现接口InitializingBean, DisposableBean并实现两个方法afterPropertiesSet和destroy
setter注入
在BookServiceImpl中声明userDao属性和set方法,在applicationContext.xml配置文件中使用property标签注入引用数据类型和简单数据类型
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<property name="databaseName" value="mysql"/>
<property name="connectionNum" value="10"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
构造器注入
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<constructor-arg name="databaseName" value="mysql"/>
<constructor-arg name="connectionNum" value="666"/>
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
name属性对应的值为构造函数中方法形参的参数名,必须要保持一致。
ref属性指向的是spring的IOC容器中其他bean对象。
依赖自动装配
- 按类型(常用)
- 按名称
- 按构造方法
- 不启用自动装配
<bean class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"
autowire="byType"/>
一个类型在IOC中有多个对象,还想要注入成功,这个时候就需要按照名称注入,按照名称注入只找其指定名称对应的bean对象,配置方式为:
<bean class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"
autowire="byName"/>
集合注入
在标签内部写<array>、<list>、<set>、<map>、<props>标签
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<!--数组注入-->
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<!--list集合注入-->
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
<!--set集合注入-->
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>
<!--map集合注入-->
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
<!--Properties注入-->
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
</bean>
配置管理第三方bean
管理Druid连接池对象:先导入druid的依赖,然后在配置文件中将druid制作成一个bean,让IOC容器进行管理
<!--管理DruidDataSource对象-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
Spring框架如何从配置文件中读取属性值来配置信息并使用这些信息来完成属性注入:需在applicationContext.xml中开context命名空间,使用context命名空间下的标签来加载properties配置文件,最后使用${key}来读取properties配置文件中的内容并完成属性注入
//classpath:代表的是从根路径下开始查找,但是只能查询当前项目的根路径
//system-properties-mode:设置为NEVER,表示不加载系统属性
<context:property-placeholder location="classpath:*.properties"
system-properties-mode="NEVER"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
核心容器
//类路径下的XML配置文件
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
//文件系统下的XML配置文件
ApplicationContext ctx = new
FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\s
rc\\main\\resources\\applicationContext.xml");
//使用BeanFactory来创建IOC容器,是延迟加载
Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
BookDao bookDao = bf.getBean(BookDao.class);
Bean的三种获取方式
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
BookDao bookDao = ctx.getBean(BookDao.class);//确保IOC容器中该类型对应的bean对象只能有一个
bean相关总结
依赖注入总结
注解开发
直接在类上添加@Component注解,即表示该类为spring管理的bean,在配置文件上进行包扫描,它会扫描指定包及其子包中的所有类上的注解
<context:component-scan base-package="com.itheima"/>
3.0版支持纯注解开发,创建一个配置类SpringConfig替换applicationContext.xml配置文件
@ComponentScan:扫描
@Configuration:表明是配置类
//@Configuration注解,将其标识为一个配置类
//@ComponentScan替换<context:component-scan base-package=""/>
@Configuration
@ComponentScan({"com.itheima.service","com.itheima.dao"})
@PropertySource("jdbc.properties")
public class SpringConfig {
}
//加载配置类初始化容器
ApplicationContext ctx = new
AnnotationConfigApplicationContext(SpringConfig.class);
注解开发bean作用范围与生命周期管理
@Repository
@Scope("prototype")
public class BookDaoImpl implements BookDao{....}
@Repository
//@Scope设置bean的作用范围
@Scope("singleton")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//@PostConstruct设置bean的初始化方法
@PostConstruct
public void init() {
System.out.println("init ...");
}
//@PreDestroy设置bean的销毁方法
@PreDestroy
public void destroy() {
System.out.println("destroy ...");
}
}
注解开发依赖注入
注解管理第三方bean
@Bean:将方法的返回值制作为Spring管理的一个bean对象
@Import:手动引入需要加载的配置类
直接将三方类写进jdbcConfig配置文件,在Spring的配置类上引入一下
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
@Configuration
//@ComponentScan("com.itheima")
//@Import:导入配置信息
@Import({JdbcConfig.class})
public class SpringConfig {
}
Spring与Mybatis及Junit的整合开发
导入整合需要的jar包mybatis-spring,创建Mybatis配置类并配置SqlSessionFactory
public class MybatisConfig {
//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
//设置模型类的别名扫描
ssfb.setTypeAliasesPackage("com.itheima.domain");
//设置数据源
ssfb.setDataSource(dataSource);
return ssfb;
}
//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
整合junit,需导入spring-test依赖,Junit运行后是基于Spring环境运行的,所以Spring提供了一个专用的类运行器,这个务必要设置,这个类运行器就在Spring的测试专用包中提供的,导入的坐标就是这个东西 SpringJUnit4ClassRunner
//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = SpringConfig.class)
//@ContextConfiguration(locations={"classpath:applicationContext.xml"})//加载
配置文件
public class AccountServiceTest {
//支持自动装配注入bean
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
System.out.println(accountService.findById(1));
}
}
AOP
AOP(Aspect Oriented Programming)
- 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
-
切入点 (Pointcut):需要被增强的具体方法
-
通知 (Advice): 在切入点处执行的操作,也就是共性功能
-
通知类:定义通知的类
-
切面(Aspect):描述通知与切入点的对应关系,进行关系的绑定具体步骤:1、添加依赖 aspectjweaver 2、定义接口和实现类 3、定义通知类和通知 4、定义切入点 5、制作切面 6、将通知类配给容器并标识其为切面类 7、在配置类开启注解格式AOP功能 @EnableAspectJAutoProxy
//通知类必须配置成Spring管理的bean @Component //设置当前类为切面类类 @Aspect public class MyAdvice { //设置切入点,要求配置在方法上方 @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt(){} //设置在切入点pt()的前面运行当前操作(前置通知) @Before("pt()") public void method(){ System.out.println(System.currentTimeMillis()); } }
AOP工作流程
Spring容器启动加载bean->读取所有切面配置中的切入点-> 初始化bean(判定 bean对应的类中的方法是否匹配到任意切入点,匹配成功会创建原始对象 的 代理 对象并运行代理对象的方法,在该方法中会对原始方法进行功能增强 ,匹配失败直接调用原始对象的方法)->获取 bean 执行方法本质:目标对象就是要增强的类 [ 如 :BookServiceImpl 类 ]对应的对象,也叫原始对象,不能说它不能运行,只能说它在运行的过程中对于要增强的内容是缺失的。SpringAOP 是在不改变原有设计 ( 代码)的前提下对其进行增强的,它的底层采用的是代理模式实现的,所以要对原始对象进行增强,就需要对原始对象创建代理对象,在代理对象中的方法把通知 [ 如 :MyAdvice 中的 method 方法 ] 内容加进去,就实现了增强 , 这就是我们所说的代理 (Proxy) 。AOP切入点表达式
切入点:要进行增强的方法
execution(public User com.itheima.service.UserService.findById(int))
* :单个独立的任意符号
..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(* com.itheima.*.*Service.find*(..))
将项目中所有业务层方法的以find开头的方法匹配
AOP通知类型
AOP通知获取切入点方法的参数、返回值、异常信息
JoinPoint:适用于前置、后置、返回后、抛出异常后通知获取参数
ProceedingJoinPoint:适用于环绕通知获取参数
获取切入点方法返回值:返回后通知 、环绕通知
//ProceedingJoinPoint:专用于环绕通知,是JoinPoint子类,可以实现对原始方法的调用
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;//修改原始方法的参数
Object ret = null;
try {
ret = pjp.proceed(args);
} catch (Throwable t) {
t.printStackTrace();
}
return ret;
}
//设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(JoinPoint jp,String ret) {
System.out.println("afterReturning advice ..."+ret);
}
//设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..."+t);
}
AOP事务管理
程序出问题后,我们需要让事务进行回滚,而且这个事务应该是加在业务层上
- 在需要被事务管理的方法上添加注解,@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上,建议写在实现类或实现类的方法上
//配置当前方法具有事务 @Transactional public void transfer(String out,String in ,Double money) { accountDao.outMoney(out,money); int i = 1/0; accountDao.inMoney(in,money); }
- 在JdbcConfig类中配置事务管理器
@Bean public DataSource dataSource(){....} //配置事务管理器,mybatis使用的是jdbc事务 @Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; }
-
在SpringConfig 的配置类中开启事务注解
//设置当前Spring环境中开启注解式事务支持 @EnableTransactionManagement public class SpringConfig { }
-
给业务层方法开启事务后,方法内的事务T1、T2就会加入该方法的事务中, 保证他们在同一个事务中,当业务层中出现异常,整个事务就会回滚,保证数据的准确性;
事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
事务配置
- readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true
-
timeout: 设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚, -1表示不设置超时时间
-
rollbackFor: 当出现指定异常进行事务回滚noRollbackFor: 当出现指定异常不进行事务回滚
-
rollbackForClassName 等同于 rollbackFor, 只不过属性为异常的类全名字符串noRollbackForClassName 等同于 noRollbackFor ,只不过属性为异常的类全名字符串
-
isolation 设置事务的隔离级别DEFAULT :默认隔离级别 , 会采用数据库的隔离级别READ_UNCOMMITTED : 读未提交READ_COMMITTED : 读已提交REPEATABLE_READ : 重复读取SERIALIZABLE: 串行化
事务传播行为
事务传播行为:事务协调员对事务管理员所携带事务的处理态度Spring 事务会把 T1,T2,T3都加入到事务T中,所以当转账失败后,所有的事务都回滚,导致日志没有记录下来,如何让log方法单独是一个事务呢?给logService的log方法加个注解propagation改变事务的传播行为即可,不管转账是否成功,都会记录日志@Service public class LogServiceImpl implements LogService { @Autowired private LogDao logDao; //propagation设置事务属性:传播行为设置为当前操作需要新事务 @Transactional(propagation = Propagation.REQUIRES_NEW) public void log(String out,String in,Double money ) { logDao.log("转账操作由"+out+"到"+in+",金额:"+money); } }