前言
spring是一款轻量级的java开发框架,其主要的功能ioc解决了人为的管理对象创建的繁琐,将对象的创建交给spring容器,由容器进行统一的管理和属性的赋值,降低了对象之间的耦合度,aop就是面向切面编程,将其他的功能称为切面,利用动态代理,实现功能的增强,就是可以在不改变源代码的情况下实现功能的增强,
一、spring的简单使用
1、新建maven的java项目
2、导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
3、创建spring配置文件
使用xml配置文件创建bean对象,id是这个对象的对象名,唯一值,class是类的全限定名称
<bean id="someService" class="com.test.service.impl.SomeServiceImpl"></bean>
4、测试类
public class SomeServiceImpl implements SomeService {
public SomeServiceImpl(){
System.out.println("无参构造方法执行了");
}
public SomeServiceImpl(String str){
System.out.println("str");
}
public void doSome() {
System.out.println("SomeServiceImpl中的doSome方法执行了");
}
}
5、测试程序
public class test {
@Test
public void testSomeServiceImpl(){
SomeService service = new SomeServiceImpl();
service.doSome();
}
@Test
public void testSomeServiceImpl2(){
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
SomeService someService = (SomeService) ac.getBean("someService");
if(someService!=null){
someService.doSome();
}
}
}
6、程序分析
1)创建的对象默认采用无参构造方法创建对象,
2)创建spring容器对象,
ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext.xml”);
路径是指当前类路径下。
3)获取spring容器中的对象
ac.getBean(“对象名”);返回的是一个Object类型。
二、ioc
1、什么是ioc
将对象的创建,属性的赋值,以及对象之间的管理交给容器管理,而不是人为的手动创建和管理。降低了代码之间的耦合度。
2、对象的创建
xml配置文件:使用bean标签创建,id名唯一
注解:使用component,service,controller等等,需要指定的对象的名字,也可以不指定,默认是类名首字母的小写。
3、基于xml的DI
可以分为三类:
1)set方法注入
在bean标签下,使用property标签给属性赋值
如果是基本数据类型(包括String),那么使用
< property name=“属性名” value=“属性值” />
如果是引用数据类型,那么采用
< property name=“属性名” ref=“属性值” />
原理是调用类的set方法给属性赋值,所以类中必须有相应的set方法才可以,set+属性名(不区分大小写),
2)构造方法注入
< constructor-arg index/name="" value/ref="">;
index 表示按照构造方法中形参的下标赋值,从0开始。
name 表示按照构造方法中形参的名字赋值,
如果构造方法中有多个参数,那么使用了构造方法赋值,就必须同时为这些形参一一赋值。
原理是调用的类的构造方法给属性赋值。
3)引用类型自动注入
在bean标签中,加入autowire=“byName/byType”
byName:是根据类型中引用数据类型的属性名对应bean的id来实现的,没有
byType:根据类型赋值,类型相同的bean,具有继承,实现的关系的bean,都会被加载到,如果有多个符合条件,那么程序会出错。
4)集合/数组/Map
<bean id="stu" class="com.atguigu.spring5.collectiontype.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java 课程</value>
<value>数据库课程</value>
<ref bean="stu"></ref>
</array>
</property>
<!--list 类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
<ref bean=""></ref>
</list>
</property>
<!--map 类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
<entry key="dd" ref=""></entry>
</map>
</property>
<!--set 类型属性注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>
4、基于注解的DI
前提是在spirng配置文件中开启组件扫描器context:component-scan,其子标签context:exclude-filter可以用来设置不扫描指定注解的类
<context:component-scan base-package="com.atguigu">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
1)类注解->创建对象:component,service,controller,repository,可以不指定对象名,默认的名字是类名首字母的小写。
2)属性上注解->属性赋值:
Ⅰ.如果是基本数据类型(包括String),使用@Value赋值,无论是int,还是Ⅱ.string,value中都是需要加上引号的
Ⅱ.如果是引用数据类型:
①使用Autowired(required=false),默认是使用byType进行自动注入,如果没有找到或者有多个符合条件,那么会报错(required的值设置为false不会报错,会赋值为null),也可以再在属性名加上@Qualifier(“bean的id”),使用byName方式赋值,
②使用resource(name="",type=""):如果没有赋值,那么默认采用byName方式赋值,没有找到对应的bean,那么就回退按照原始类型匹配/装配/注入。如果设置name/type属性的值,那么根据name/type进行赋值。如果同时设置了name和type,那么按照类型相同和bean的名字相同实现自动注入。
3)方法上的注解:和属性上的注解一致,只是调用方法为属性赋值而已
5、基于注解开发(所有注解均是用来替代xml中的配置的)
1、对象的创建依赖注解repository,controller,service,component
2、@Configuration注解在配置类上,代替xml配置文件,可使用AnnotationApplicationContext(配置类.class)类扫描,同ClassPathXmlApplicationContext一样,可以在当前配置类中使用@Bean注解采用方法形式返回一个对象,方法名就是对象的名称,@Scope()可以设置作用域
3、@ComponentScan(basePackages={}),扫描包下的所有注解类,不要扫描controller层,应该有springmvc来进行管理
4、@EnableAspectjAutoProxy(proxyTargetClass=true),开启aop功能,
5、@MapperScan();扫描mapper接口,与xml创建factroy对象和生成dao实现类的配置一致,并且创建数据源对象,创建工厂对象(扫描mapper,取别名,设置插件,开启缓存,不用再扫描mybatis.xml主配置文件)。
6、@EnableTransactionManagent,创建事务管理器对象
package cn.edu.hbpu.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@ComponentScan("cn.edu.hbpu.service.Impl")
@MapperScan("cn.edu.hbpu.dao")
@EnableTransactionManagement
public class MySpringConfig {
@Bean
public DruidDataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/student?useSSL=false&serverTimezone=UTC");
dataSource.setUsername("common");
dataSource.setPassword("15572420317laoy");
dataSource.setMaxActive(20);
System.out.println("创建DruidDataSource");
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory(@Autowired DataSource dataSource) throws Exception {
System.out.println(dataSource);
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
/* Spring 资源解析器 */
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
/* 设置 mapper.xml 所在的路径 */
//factoryBean.setConfigLocation(resolver.getResource("classpath:mybatis.xml"));//获取主配置文件
factoryBean.setMapperLocations(resolver.getResources("classpath:cn/edu/hbpu/dao/*.xml"));
factoryBean.setTypeAliasesPackage("cn.edu.hbpu.domain");
System.out.println("创建SqlSessionFactory");
return factoryBean.getObject();
}
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new
DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
6、bean的作用域
在注解中使用
singleton单例:在spring加载配置文件时就会创建类的实例,每次获取的都是这个实例对象。
prototype多例:对象会在使用beanFactory获取的时候进行实例,每次获取的都是不同的实例,每次都会执行init方法,销毁时执行destroy方法
7、bean 的生命周期
1)使用无参(有参)构造方法创建实例
2)利用set方法为属性赋值
3)调用init方法(如果有后置处理器,可以为在执行init方法前后加上方法)
4)通过BeanFactory获取使用
5)容器关闭时,执行销毁方法
三、aop
1、什么是aop
aop实际上就是动态代理(功能增强,访问权限),动态代理可以由JDK中proxy反射实现(需要目标类实现接口,),也可以由cglib来实现(需要目标类可以继承,子类就是代理类),统一了动态代理的实现方式。
2、aop中的重要概念
1)切面aspect:将通知应用到切入点
通知advice:功能增强方法执行时机,目标方法的前面还是后面
连接点joinpoint:表示执行位置:哪些类中的方法需要使用切面,需要功能增强。
切入点pointcut:多个连接点的集合
2)常用通知
①前置通知:目标方法之前执行,开启事务,日志
@Before(value=“execution()”);
②后置通知:事务的结束,日志
@AfterReturning(value=“execution()”,returning=“返回值的形参”)
③环绕通知:自定义
@Arround()
通知方法中可以获取到执行的方法的形参(ProceedingJoinPonit)。相当于invocationhandler,如果业务方法返回了一个值,那么环绕通知方法也必须有相同的返回值。
④最终通知:总是会执行的
⑤异常通知:抛出异常时执行
⑥Pointcut(""),使用通知方法的名字可以获取到这个切入点表达式,复用
所有的通知方法(不能和环绕通知中的ProceedingJoinPoint同时出现,会报错)都可以有形参JointPoint类型,之后环绕通知才能有ProceedingJointPoint形参对象,后置通知和异常通知,都必须指定获取的返回值/异常对象的名字,然后再形参中声明。
3)切入点表达式:
execution = “public void 包名.方法名(参数名) 异常”
修饰符和包名以及异常都是可以省略的。
*表示任意字符,
…表示多级路径/任意参数
+表示接口及其实现类或者当前类及其子类
execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“…”出现在类名中时,后
面必须跟“*”,表示包、子包下的所有类。
3、使用apectj实现aop
依赖:spring-aspectj可以完成注解,配置文件方式没有尝试过,依赖:aspectjweaver可以完成三中实现方式
1)注解方式
①定义目标类(业务)
②定义切面类 @Aspectj
③在spring配置文件中,创建切面类对象和目标类对象,声明基于aspectj的自动代理生成器,< aop:aspectj-autoproxy>,首先会找Aspect注解的类,然后根据类中的通知方法里面的切入点表达式找到需要切面功能的类,,然后会修改这些类创建的对象的内存结构
④如果多个切面类对同一个方法进行功能增强,那么在切面类加上@Order(数字),数字越小表示优先级越高,也就越先执行。
2)使用配置文件配置切入点表达式
①、加入依赖
②、创建切面类实现相应的接口(通知方法的接口),定义切面方法
③、明确切面功能的切入点:执行位置;
< aop:config>
那些方法需要切面,切面的执行位置
< aop:pointcut id="" excpression=“切入点表达式”>
< aop:advisor advice-ref=“切面类的对象” pointcut-ref=“切入点表达式的id” />
< /aop:config>
3):使用配置文件配置通知和切入点表达式
①加入依赖
②创建切面类,定义切面方法
< aop:config>
定义切面类
< aop:aspect ref=“切面类对象”>
< aop:pointcut id="" expression=“切入点表达式” />切入点表达式,需要功能增强的方法
< aop:before/after method=“增强的方法” pointcut-ref="">
< /aop:aspect>
< /aop:config>
四、spring集成mybatis
在以前的mybatis中,是我们通过mybatis的动态代理实现dao层接口实现类对象的创建,现在我们可以将mybatis交给spring来进行管理
新建web项目
加入依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
加载监听器
在web.xml加载创建spring容器对象的监听器
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
通过context-param修改加载配置文件的路径。
创建mybatis和spring主配置文件
1)mybatis主配置文件:不需要连接数据库的标签,放在spring配置文件中使用druid连接池管理,指定mapper
2)spring配置文件:创建druid连接池,创建SqlSessionFactoryBean对象,创建dao层对
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/student?useSSL=false&serverTimezone=UTC" />
<property name="username" value="common" />
<property name="password" value="15572420317laoy" />
<property name="maxActive" value="20" />
</bean>
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource"></property>
<property name="configLocation" value="classpath:mybatis.xml"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="factory"></property>
<property name="basePackage" value="cn.edu.hbpu.dao"></property>
</bean>
3)在为连接池设置属性,可以通过properties配置文件设置,在spring的配置文件中引入,使用${参数}取值即可
<context:property-placeholder location="classpath:jdbc.properties"/>
使用JDBCTemplate连接数据库,而不使用mybatis
使用@Repository注解开发,不需要在配置类中创建SqlSessionFactory对象以及利用MapperScannerConfigurer创建mapper对象,只需要创建JdbcTemplate实例即可,指定数据源,通过JdbcTempate提供的方法操作数据库/font>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入 dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
int update = jdbcTemplate.update(sql, args);
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
五、事务
1、什么是事务
1)概念:事务指在mysql数据库中是最小的执行单元,事务中包含多个sql语句,在事务执行完毕之后,根据sql执行的情况,事务可以回滚和提交。
2)使用:通常涉及到多个表的操作时,例如银行转账,需要两个账户的操作同时成功才可以提交,反之事务应该回退,
3)事务的特征:持久性,原子性,隔离性,
4)隔离级别:
读已提交ReadCommitted:不可重复读
读未提交ReadUnCommitted:脏读
可重复读:RepeatableRead:幻读
序列化读:Serializable:只有事务提交之后才可以读取,解决了所有的问题,但是效率低
2、spring中使用事务时的重要概念
1)传播行为propagation:事务方法调用之间是否使用同一个事务
①REQUIRED:如果第一个业务使用了事务,那么后面的业务方法也使用该事务,如果没有事务,那么自动开启一个事务,后面的事务使用当前的事务
②SUPPORTS:有事务就加入到事务中,没有事务也可以不用事务
③REQUIRES-NEW:每一个业务方法都需要一个自己的事务,之前的事务挂起直到当前事务回滚或者提交
2)事务提交的机制:
发生了编译时异常,事务自动提交
发生了运行时异常,事务自动回滚
没有异常,事务自动提交
3)超时时间,通常是不设置的。有可能是网络延迟的问题
4)事务的隔离级别:
3、基于aop的事务实现
前提:需要加入spring,mybatis的依赖,不同的框架实现事务,还需要加上不同的依赖
1)spring注解实现事务(中小型项目):
①在业务方法上Transactional注解:
propagation:传播行为
timeout:超时时间,默认是-1
isolation:隔离级别
readonly:对表的操作是可读的
rollbackfor:class类型的集合,表示遇到指定异常回滚
rollbackforclassname:string类型的集合,表示遇到异常回滚
②声明事务管理器对象:
< bean id="" class="">< property name=“DataSourceTransactionManager” ref ="">< /bean>
③加入注解驱动,指定事务管理器对象
< x:annotation-driven transaction-manager=“事务管理器对象” />
原理:spring使用aop机制,在业务方法执行之前开启事务,在业务方法中,如果发生了异常,如果在rollbackfor范围内,那么会自动的回滚,如果不是,那么检查是不是运行时异常,如果是回滚,如果不是,那么就是编译时异常了会自动提交
2)aspectj配置文件实现事务(大型项目)
①声明事务管理器对象:DataSourceTransactionManager
指定德鲁伊连接池
②指定业务方法的事务属性
<tx:advice id="advice" transaction-driven="transactionManager">f
<tx:attributes>
<tx:method name="方法名" isolation="" propagation="" readOnlu="" rollback-for="异常类的全限定名称"/>
</tx:attributes>
</tx:advice>
③配置aop
<aop:config>
<aop:pointcut id="pc唯一标识" expression="execution()" />
<aop:advisor advice-ref="" pointcut-ref="" />
</aop:config>
4、事务实现
@Transactional(
propagation=Propagation.SUPPORTS,
timeout=-1,
readOnly=true,
rollbackForClassName = {"NullPointException","IlegalGoodsNumsExeception"}
)
public void buyGoods(Sale sale) throws IlegalGoodsNumsExeception {
//用户数据的合法性,为了测试事务是否回滚,我们将其放在后面
//增加销售记录
saleDao.insertSale(sale);
//修改商品的数量信息
Goods goods = goodsDao.selectGoodsById(sale.getGid());
if(null==goods){
System.out.println("商品Id不存在!");
throw new NullPointerException();
}else if(sale.getNums()>goods.getCount()){
System.out.println("商品库存不足!");
throw new IlegalGoodsNumsExeception();
}
//手动抛出自定义异常会怎么样
//throw new IOException("抛出编译时异常");//抛出异常之后的代码不会执行
//System.out.println("如果上面抛出异常,后面的代码不会执行");
goodsDao.updateGoodsCount(sale);
//如果两个方法都成功,那么提交事务。反之回滚事务。
}
六、其他
1、多个spring配置文件(主要针对使用xml实现DI的配置文件)
设置一个主配置文件,用来加载其他的配置文件,
< import resource="" />
可以采用通配符的形式,一次加载多个,但是主配置文件不得在其中,不然是死循环。
也可以一个一个的添加,
2、细节
1)spring的transactional注解以及aspect的事务属性,通常使用默认值的属性就可以了,都是使用数据库默认的隔离级别,和REQUIRED传播行为,只需要指定rolbackfor就可以了
总结
spring是一个容器,通过IOC用来管理对象的创建,以及属性的赋值,,通过aop的切面可以为主业务方法添加,日志,事务等这些与业务方法无关的一些功能,和方法调用类似,减少了代码量,采用动态代理的方式,在不改变源代码的情况,将切面功能织入了业务方法中,