一、Spring概述
什么是Spring?
Spring是分层的Java SE/EE应用的全栈式轻量级开源框架,以IoC(翻转控制)和AOP(面向切面编程)为内核,提供了表现层SpringMVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库。
image-20200606232357062.png
Spring的两大核心
Spring的发展历程和优势
Spring的体系架构
二、Spring中的IoC
2.1 IoC
1.什么是IoC?
概念:控制反转。把创建对象的权利交给了框架,即不再是用new + 类名的方法,而是通过工厂模式,让工厂通过反射代理取得对象,是框架的重要特征。它包括依赖注入(DI),依赖查找(DL)。目的是削减计算机的耦合(降低代码间的依赖关系)。
2.Spring基于XML配置的IoC环境搭建和入门
- 导入依赖坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId> //spring-context就包含了spring运行的基本组件
<version>5.2.5.RELEASE</version>
</dependency>
- spring的配置文件(bean.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans //约束文档
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置对象映射,把对象的创建交给Spring框架处理,id表示获取的对象引用,通过class的全限定名反射新建对象-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/> //Spring框架的BeanFactory生产bean对象,通过通过类全限定名class来反射newInstance对象,id是其key,在调用getBean方法时传入的参数
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"/>
</beans>
- 配置client类
*ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下,不然加载不了 类路径:项目的src下,
*FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
*AnnotationConfigApplicationContext:它用于读取注解创建容器
* 核心容器的两个接口引发出的问题:
* ApplicationContext :
* 它在构建核心容器时,创建对象采取的策略是立即加载的方式,只要一执行读取配置文件操作就马上创建配置文件中对应的对象
* BeanFactory :
* 它在创建核心容器时,创建对象采取的策略是延迟加载的方式,什么时候根据id获取对象了,什么时候真正创建对象
// 1、获取核心容器对象 使用ApplicationContext立即加载
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //核心容器(Core Container)对象包含Beans、Core、Context、SpEL
// 2、根据id获取Bean对象
AccountService as = (AccountServiceImpl)ac.getBean("accountService"); //getBean表示去核心容器中获取bean对象(由bean标签中的id唯一标识)
AccountDao ad = (AccountDaoImpl)ac.getBean("accountDao");
- 在bean.xml中往容器注入对象的三种方式
<--方式一:反射中采用默认构造函数创建:
> 在Spring的配置文件中使用bean标签,配以id和class属性后,且没有其他属性和标签时
采用的就是该类中默认构造函数创建bean对象,如果类中没有默认构造函数(比如写了带参构造而没有写无参构造),则对象无法创建-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"/>
<--方式二:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象并存入Spring容器)-->
<bean id="accountServiceFactory" class="com.itheima.service.impl.AccountServiceFactory"/>
<!--factory-bean映射前一句的accountServiceFactory,调用该类中的方法创建accountService-->
<bean id="accountService" factory-bean="accountServiceFactory" factory-method="getAccountService"/>
<--方式三:使用某个类中的静态方法创建对象并存入Spring容器-->
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceFactory" factory-method="GetAccountService"/>
- bean对象的作用域
<-- scope属性:
作用:指定bean的作用范围
取值:常用的就是单例(多次getBean只创建一个对象)和多例(每次getBean都新建一个对象)
singleton:单例
prototype:多例
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),例如服务器集群(多个服务器),session作用于全部服务器-->
<bean id="accountServiceFactory" class="com.itheima.service.impl.AccountServiceFactory" scope="prototype"/>
- bean对象的生命周期
<--bean对象的生命周期
-单例对象:
出生:当容器创建;活着:容器还在,对象就在;死亡:容器销毁,对象死亡
-多例对象:
出生:使用对象时创建 活着:在使用过程中一直活着 死亡:当对象长时间不用且没有别的对象引用时,由java的垃圾回收器回收-->
- 依赖注入的三种方式
<--Spring中的依赖注入
依赖关系的维护,就称为依赖注入(或者理解为一个bean中有很多属性,通过注入的方式给这些属性赋值)
能注入的数据,有三类:
①基本类型和String②其他bean类型(在配置文件中或者注解配置过的bean)③复杂类型\集合类型
注入的方式,有三种:
①使用构造函数提供②使用set方法提供③使用注解提供
->
- 构造函数注入
<--构造函数注入 当没有提供默认构造函数,只有带参构造函数时,用构造函数方式创建bean对象必须提供所有参数 (一般不用,需要提供大量参数,如果参数无用也得提供)
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据的构造函数中指定索引位置的参数,索引位置从0开始
name:用于指定给构造函数中指定名称的参数赋值(常用)
value:赋值,用于给基本类型和String类型赋值
ref:用于指定其他的bean类型数据(在Spring的IoC核心容器中出现过的bean对象(xml或注解中配置的bean对象))
弊端:必须提供所有的参数
-->
<bean id="AccountDao" class="com.itheima.dao.impl.AccountDaoImpl"/> //此处定义的一个bean类型,在下一个bean类型中用ref标签注入,因为value不能注入这种类型
<bean id="AccountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="校长"/>
<constructor-arg name="accountDao" ref="AccountDao"/>
</bean>
- set方法注入
用该方法注入时,bean所指的对象中必须有默认构造方法,因为该方法也是方式一创建bean对象,也必须有setter方法
<--set方法注入
涉及的标签:property
出现的位置:bean标签的内部
标签的属性:
name:用于指定注入时所调用的set方法名称,且是该方法名去掉set后的名称首字母小写
value:赋值,用于给基本类型和String类型赋值
ref:用于指定其他的bean类型数据(在Spring的IoC核心容器中出现过的bean对象(xml或注解中配置的bean对象))
优势:创建对象时没有明确的限制,可以直接使用默认构造函数,可以不给某些属性设置值
弊端:如果某个属性必须有值,可能set方法没有执行或者忘记添加
-->
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="id" value="19"/>
<property name="name" value="Chris"/>
</bean>
- 复杂类型、集合类型的注入(基于set方法)
<--复杂类型的注入\集合类型的注入
用于给List结构集合注入的标签:
list array set
用于给Map结构集合注入的标签:
map props
结构相同时,标签可以互换,即选取其中任意一个都可以-->
<bean id="test" class="com.itheima.service.impl.Test">
<!--注入数组-->
<property name="str">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<!--注入List集合-->
<property name="list">
<list>
<value>A</value>
<value>B</value>
<value>C</value>
</list>
</property>
<!--注入Map集合-->
<property name="map">
<map>
<entry key="1" value="s1"/>
<entry key="2" value="s2"/>
</map>
</property>
<!--注入Properties-->
<property name="prop">
<props>
<prop key="s1">2</prop>
<prop key="s2">4</prop>
</props>
</property>
</bean>
- 注解方式注入
- 注解分类:
- 用于创建对象的:他们的作用就和在XML配置文件中编写一个<bean></bean>标签实现的功能一样
- 用于注入数据的:他们的作用就和在XML配置文件中编写一个<property></property>标签的作用一样
- 用于改变作用范围的:他们的作用就和在XML配置文件中编写一个使用<bean></bean>标签中的scope属性一样
- 和生命周期相关的:他们的作用就和在bean标签中使用init-method和destroy-method作用一样
- 扫描包
<--告知Spring在创建容器时(读取配置文件),需要扫描的包,扫描这些包中
哪些地方加了component注解,并创建他们的bean对象。配置所需要的标签不是在bean标签
而是在一个名为context的名称空间和约束中-->
<context:component-scan base-package="com.itheima"/>
数据注入
- Autowired
> * 用于注入数据的:
> * Autowired
> * 作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就注入成功
> * 如果变量类型为接口,bean对象类型为该接口的实现类,则也可以成功注入
> * 当容器中有多个bean对象类型和变量类型匹配时
> * ①先找到和变量类型匹配的所有对象
> * ②找bean对象名和变量名相同的进行注入,如果没有,则注入失败
> * 注解出现位置:可以是变量上,也可以是方法上
> * 细节:在使用注解注入时,set方法就不是必须的了,可以不生成setter方法
> */
>
> @Service(value = "service")
> public class AccountServiceImpl implements AccountService {}
> @Autowired
> private AccountDao AccountDao;
>
> //如下所示,对于要注入的类型AccountDao,以下有两个bean对象可以匹配,所以选择和id相同的AccountDao进行注入,如果id都不相同,则注入失败
> @Repository(value = "AccountDao2")
> public class AccountDaoImpl2 implements AccountDao {}
> @Repository(value = "AccountDao")
> public class AccountDaoImpl implements AccountDao {}
- Qualifier
> *Qualifier
> * 作用:在Autowired的基础上,添加要注入的bean的id,必须和Autowired配套使用
> * 属性:value:用于指定要注入的bean的id
> @Service(value = "service")
> public class AccountServiceImpl implements AccountService {}
>
> @Autowired
> @Qualifier(value = "AccountDao3")
> private AccountDao AccountDao;
- Resource
> *Resource
> * 作用:直接按照bean的id注入,它可以独立使用
> * 属性:name:指定bean的id
> @Service(value = "service")
> public class AccountServiceImpl implements AccountService {
>
> @Resource(name = "AccountDao2")
> private AccountDao AccountDao;
> }
SpEL
*以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现
*另外,集合类型的注入只能用XML来实现
*Value
- 作用:用于注入基本类型和String类型的数据
- 属性:value:用于指定数据的值,它可以使用Spring的SpEL(也就是Spring的el表达式)
- SpEL的写法:${表达式}
> //@Value注解
> @Value(value = "13")
> private Integer id;
> @Value(value = "Chris")
> private String name;
> //SpEL表达式
> * PropertySource\PropertySources
> * 作用:用于指定properties文件的位置
> * 属性:
> * value:指定文件的名称和路径,关键字classpath,表示在类路径下
> @PropertySource(value = "classpath:jdbc.properties") //通过这一步,就读取了properties文件的内容,然后通过SpEL表达式注入
> @Value("${Driver}")
> private String Driver;
>
> @Value("${url}")
> private String url;
>
> @Value("${user}")
> private String user;
>
> @Value("${password}")
> private String password;
- 改变bean的作用范围
@Scope(value = "prototype") @Service(value = "service") public class AccountServiceImpl implements AccountService {}
- 声明bean生命周期
> ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //使用子类声明才能调用close方法,父类ApplicationContext中没有该方法
> // 2、根据id获取Bean对象
> AccountService as = (AccountServiceImpl)ac.getBean("service");
> ac.close();
>
> @PostConstruct //bean创建后执行
> public void init(){
> System.out.println("开始了");
> }
> @PreDestroy //调用close方法后执行
> public void destroy(){
> System.out.println("销毁了");
> }
- Spring基于XML的IoC访问数据库实例
accountService需要注入accountDao
accountDao需要注入queryRunner
queryRunner需要注入dataSoure,带参构造注入,并且queryRunner被多个sql调用会产生线程安全问题,scope设置为prototype多例对象
dataSource需要注入4个连接数据库的属性,用set方法注入
> <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>
>
> <--QueryRunner(DataSource)带参构造,datasource是c3p0连接数据库的四个基本属性-->
> <--queryRunner在Dao层被多个sql语句同时使用,可能产生线程安全问题,因此采用多例模式-->
> <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
> <constructor-arg ref="dataSource"/>
> </bean>
>
> <--注入dataSource的4个数据库连接属性,-->
> <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="1234"/>
> </bean>
- Spring基于注解的IoC案例
bean.xml配置文件
<context:component-scan base-package="com.itheima"/>
<!--QueryRunner(DataSource)带参构造,datasource是c3p0连接数据库的四个基本属性-->
<!--queryRunner在Dao层被多个sql语句同时使用,可能产生线程安全问题,因此采用多例模式-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<constructor-arg ref="dataSource"/>
</bean>
<!--注入dataSource的4个数据库连接属性,-->
<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="1234"/>
</bean>
Service层和Dao层
@Service(value = "AS")
public class AccountServiceImpl implements AccountService {
@Resource(name = "AD")
private AccountDao accountDao;
}
@Repository(value = "AD")
public class AccountDaoImpl implements AccountDao {
@Resource(name = "queryRunner")
private QueryRunner queryRunner;
}
基于注解配置文件并读取配置文件
* 配置类,作用与bean.xml的作用一样
* Spring中的注解
// Configuration:
* 作用:指定当前类是一个配置类,@Configuration只能注解主配置类,不要去标注子配置文件
* 细节:当Configuration修饰的那个类作为AnnotationConfigApplicationContext(Class<?>[])的参数
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
* 传递时,可以不用Configuration注解,因为传递了就代表一定会去读取这个类
// ComponentScan:
* 作用:用于指定Spring在创建容器时要扫描的包,扫描单个的包
* 使用了该注解就相当于在XML中配置了<context:component-scan base-package="com.itheima"/>
* basePackages是一个字符型数组,里面是类路径,可以放入多个路径
* ComponentScans:里面都是ComponentScan类型的注解,扫描多个包
// Bean
* 作用:用于把当前方法的返回值作为bean对象存入Spring的IoC容器中
* 属性:
* name:用于指定bean的id,如果不写时,默认为当前方法的名称
* 细节:
* 当使用注解配置方法时,如果方法有参数,Spring框架会去IoC容器中查找有没有对应类型的bean对象
* 查找方法和Autowired一样,先匹配类型,然后看名称。所以参数得是容器中的bean对象,比如:
@Bean(name = "queryRunner")
public QueryRunner createQueryRunner(@Qualifier(value = "ds") DataSource ds){ //对应DataSource,配置中有多个DataSource,这时候就可以用@Qualifier指定是哪一个
return new QueryRunner(ds);
}
// Import
* 作用:加载其它子配置文件,从而实现项目中只有一个主配置文件,其它文件都被import到这个主配置文件
* 然后在AnnotationConfigApplicationContext里面只读取主配置文件即可
* 属性:
* value:用于指定其它配置类的字节码
* 当使用Import的注解之后,有Import注解的类就称为父配置类,导入的为子配置类
*/
3.Spring基于注解的IoC实例
- 主配置文件
@Configuration //主配置文件
@Import(jdbcConfig.class) //导入子配置文件
@ComponentScan(basePackages = {"com.itheima"}) //在 ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class)的时候扫描, 创建被注解Component\Service等修饰的对象
public class SpringConfiguration {
}
- 子配置文件
子配置文件
@Configuration
public class jdbcConfig {
@Bean(name = "queryRunner") //创建为一个bean对象放入容器中
public QueryRunner createQueryRunner(DataSource ds){
return new QueryRunner(ds);
}
@Bean(name = "ds")
public DataSource createDataSource(){
try {
ComboPooledDataSource source = new ComboPooledDataSource();//这里使用的是C3P0连接池
source.setDriverClass("com.mysql.jdbc.Driver");
source.setJdbcUrl("jdbc:mysql:///spring");
source.setUser("root");
source.setPassword("1234");
return source;
}catch (PropertyVetoException e){
throw new RuntimeException("连接数据库参数错误");
}
}
}
Spring整合junit
1、导入Spring整合junit的jar包(pom坐标)
2、使用junit提供的一个注解
@Runwith 把原有的main方法替换掉,替换成Spring提供的
3、告知Spring的运行器,Spring和IoC容器创建时基于XML还是基于注解,并且说明位置
@ContextConfiguration
locations:指定XML配置文件位置,一定加上==classpath关键字==,表示在类路径下
classes:指定注解配置类所在的位置(一般是写主配置类)4、当使用Spring版本5.x及以上时,要求junit的版本在4.1.2以上
- 导入Maven依赖坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
//使用Spring整合junit后,就可以在测试的时候,不用再新建ApplicationContext
@RunWith(SpringJUnit4ClassRunner.class) //这一步替换junit中的main方法为Spring的main方法
@ContextConfiguration(classes = SpringConfiguration.class) //这一步相当于原来的读取配置文件
public class Test {
@Autowired
private AccountService as;
@org.junit.Test
public void findAll(){
List<Account> all = as.findAll();
for (Account account : all)
System.out.println(account);
}
}
2.2 AOP
- 什么是AOP?
AOP(Aspect Oriented Programming)是面向切面编程。通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。简单来说就是把程序中的重复代码抽取出来,在需要执行的时候,通过动态代理的技术,在不修改源码的基础上,对已有的方法进行增强。
- 相关术语
连接点,也就是接口中的所有方法,位于前置增强和后置增强之间,也就是代理对象本来应该执行的方法切入点,某一个连接点,对哪些连接点进行拦截(if ().equals(method.getName)),要执行的方法。
Spring基于XML的AOP配置
导入坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
- 配置AOP,实现在执行AccountService的方法时,通知一个Logger.printLog方法
XML中配置AOP的步骤
<-- Spring中基于XML的AOP配置步骤:
<1、把通知Bean也交给Spring来管理>
<2、使用aop:config标签表明开始AOP的配置>
<3、使用aop:aspect标签表明配置切面(切面:切点和增强方法等)>
属性:①id属性:给该切面提供一个唯一标识②ref属性:指定bean的id
<4、在aop:aspect标签内部配置通知的类型(前置通知、后置通知等等)>
<--aop:before表示前置通知
method属性用于指定通知类里哪个方法被执行
pointcut指定切入点:切入点表达式
①切入点表达式标准写法:
pointcut="execution关键字(权限修饰符 返回值类型 包名... 方法名)"
②权限修饰符可以省略。
③返回值可以使用通配符表示任意返回值*
④包名可以用通配符表示,*.*.*.* 四级目录,也可以用*..表示任意包下的任意子包
⑤类名和方法名也可以用通配符实现通配 *.* 所有类的所有方法
⑥参数列表
可以直接写数据类型 int等
引用类型写包名、类名的方式 java.lang.String
可以使用通配符表示任意类型,但是前提是必须有参数(*)
可以用(..)表示有无参数均可,有参数可以是任意类型
⑦全通配写法:
* *..*.*(..)
<实际开发中切入点表达式的通常写法:>
切入到业务层实现类下的所有方法:定位到包,通配类和方法
* com.itheima.service.impl.*.*(..)
如果是一个包下,要用两级.如:
expression = execution(* com.zt.service..*(..))
可以通过点击左侧的按钮来检查切入点方法配置到了哪个方法上
-->
XML配置AOP示例
<-- 配置Spring的IoC,加入Service对象-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
<-- 配置Logger类-->
<bean id="logger" class="com.itheima.utils.Logger"/>
<-- 配置AOP-->
<aop:config> //表示开启AOP配置
<aop:aspect id="logAdvice" ref="logger"> //配置切面名称和用于增强的类,下面引用类中的方法
<aop:before method="printLog" pointcut="execution(public void com.itheima.service.impl.AccountServiceImpl.saveAccount())"/> //配置通知类型,切入点表达式,方法
</aop:aspect>
</aop:config>
client类验证
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
AccountService accountService = (AccountService) ac.getBean("accountService");
accountService.saveAccount();
四种常用的通知
<aop:aspect id="logAdvice" ref="logger">
<!--前置通知--> 在切入点方法执行之前执行
<aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"/>
<!--后置通知--> 在切入点方法执行之后执行
<aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"/>
<!--异常通知--> 当切入点方法出现异常时执行,它和后置通知只能执行一个
<aop:after-throwing method = "afterThrowingPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"/>
<!--最终通知--> finally里面的内容,一定会最后执行
<aop:after method="afterFinalPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"/>
</aop:aspect>
通用切入点表达式配置
<--通用化切入点表达式,要在aop:aspect标签之前,当配置在外部时,可被多个切面使用,如果配置在单个aspect里面,就只能被当前切面使用-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<aop:aspect id="logAdvice" ref="logger">
<!--前置通知-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"/> pointcut-ref引用pointcut表达式
配置环绕通知
public Object aroundPrintLog(ProceedingJoinPoint pjp){ //固定参数写法,环绕通知有一个Object的返回值
Object Value =null;
try {
Object[] args = pjp.getArgs(); //获取方法参数
// 这一步就相当于执行invoke方法
Value = pjp.proceed(args); //执行方法
return Value;
}catch (Throwable t){
throw new RuntimeException(t);
}finally {
System.out.println("环绕通知"); //把这个增强的方法写在不同位置,就代表不同的通知类型
}
}
- Spring基于注解的AOP配置
configuration类
@org.springframework.context.annotation.Configuration //Configuration类只能有一个
@ComponentScan(basePackages = "com.itheima")
@EnableAspectJAutoProxy //开启Spring对注解代理的支持
public class Configuration {
}
以上等价于XML中
<context:component-scan base-package="com.itheima"/>
<!-- 使能注解模式-->
<aop:aspectj-autoproxy/> //这里也可以用注解的方式使能和扫描,见前面的@configuration
配置切面
@Component(value = "logger")
@Aspect //表示当前类是一个切面 在使用注解配置时,不要起名
public class Logger {
@Pointcut("execution(* com.itheima.service.impl.*.*(..))") //注解在方法上,下面调用@Before()里面要加括号pt( )
public void pt(){}
// 用于打印日志:让其在切入点方法执行之前执行(切入点方法就是业务层方法)
@Before(value = "pt()")
public void beforePrintLog(){
System.out.println("前置通知记录日志");
}
}
基于注解配置的切面在调用顺序上有所不同,最终通知在后置通知和异常通知之前。解决办法:使用环绕通知,自定义。比如
@Around(value = "pt()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object Value =null;
try {
Object[] args = pjp.getArgs();
// 这一步就相当于执行invoke方法
System.out.println("前置通知记录日志");
Value = pjp.proceed(args);
System.out.println("后置通知记录日志");
return Value;
}catch (Throwable t){
System.out.println("异常通知记录日志");
throw new RuntimeException(t);
}finally {
System.out.println("最终通知记录日志");
}
}
三、SpringJdbcTemplate
3.1 基本配置
- 在类中配置jdbc
JdbcTemplate jt = new JdbcTemplate();
// Spring的内置数据源DriverManagerDataSource
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql:///spring");
ds.setUsername("root");
ds.setPassword("1234");
jt.setDataSource(ds);
- 使用IoC注入
<bean id="template" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="password" value="1234"/>
<property name="url" value="jdbc:mysql:///spring"/>
<property name="username" value="root"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
</bean>
- JdbcTemplate的CRUD操作
/*
* execute:不带参数的执行
* query:查询,BeanPropertyRowMapper<类型>(类型.Class) 自动封装查询结果到该类型,并且如果是多个结果自动封装为List
* update:带参数的增删改。jt.update("insert into test(name,salary) values(?,?)","zhou",1000);
*/
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
JdbcTemplate jt = (JdbcTemplate)ac.getBean("template");
jt.execute("insert into test(name,salary) values('xx',10000)");
List<User> users = jt.query("select * from test",new BeanPropertyRowMapper<User>(User.class)); //Spring的BeanPropertyRowMapper封装查询结果对象,可以查询一个或多个
for (User u : users)
System.out.println(u);
- JdbcDaoSurpport
public class UserDaoImpl extends JdbcDaoSupport implements UserDao{ //让DaoImpl继承JdbcDaoSupport,在xml配置中只需要为UserDaoImpl注入datasource就可以用父类的getJdbcTemplate方法获得template,但是这种方法就没有办法再用注解注入或生成bean对象,只能xml配置
@Override
public List<User> findUserByID(Integer id) {
return super.getJdbcTemplate().query("select * from test where id = ?", new BeanPropertyRowMapper<User>(User.class),id);
}
@Override
public List<User> findUserByName(String name) {
return super.getJdbcTemplate().query("select * from test where name = ?",new BeanPropertyRowMapper<User>(User.class),name);
}
}
}
//JdbcDaoSurpport源码中的注入方法
public final void setDataSource(DataSource dataSource) {
if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
this.jdbcTemplate = createJdbcTemplate(dataSource); //在注入datasource的时候就创建一个template,可以用getJdbcTemplate方法调用
initTemplateConfig();
}
}
四、Spring的事务控制
4.1 基于XML的声明式事务控制配置
- 导入事务控制的约束文档,搜索xmlns:tx。
基于XML的声明式事务管理配置步骤
<--Spring基于XML的声明式事务管理控制配置-->
<--步骤
1、配置事务管理器
2、配置事务的通知
此时需要导入事务的约束 tx:xxx
tx:advice标签配置事务通知
属性:
id:起名
transaction-manager:给事务提供一个事务管理器引用
3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系
5、配置事务的属性,是在事务的通知tx:advice标签内部
-->
<--1、配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<--2、配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<--5、配置事务的属性
isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认隔离级别
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,当产生其他异常时,事务回滚,没有默认值,表示任何异常都回滚
propagation:用于指定事务的传播行为,默认值是REQUIRED,表示一定有事务,进行增删改时选择这个。查询方法用SUPPORTS
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不会滚。没有默认值,表示任何异常都回滚。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时,单位是秒-->
<tx:attributes>
<!--find*表示以find开头的所有方法,优先级高于全通配*-->
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<!--*表示全通配所有方法-->
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
</tx:attributes>
</tx:advice>
<--3、配置aop-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt" expression="execution(* zt.service.UserServiceImpl.*(..))"/>
<--4、建立切入点表达式和事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
4.2 基于注解的声明式事务控制
- 在XML中配置事务管理器DataSourceTransactionManager,也可以在Configuration类里面注入,但是这样较为麻烦。
- 用Configuration类来注入事务管理器
public class TransactionConfig {
// 创建事务管理器,并放入容器
@Bean(value = "TransactionManager")//Bean注解将方法的返回值注入IoC容器,默认名称为方法名首字母小写
public PlatformTransactionManager getTxManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
- 用注解来注入DataSource
public class jdbcTemplate {
@Value("${Driver}") //Value注解用于注入非复杂类型,集合类型
private String Driver;
@Value("${url}")
private String url;
@Value("${user}")
private String user;
@Value("${password}")
private String password;
// 把方法返回的bean对象放入IoC容器中
@Bean(name = "jdbcTemplate")
public JdbcTemplate getTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean(name = "DataSource")
public DataSource getDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setPassword(password);
dataSource.setUsername(user);
dataSource.setUrl(url);
dataSource.setDriverClassName(Driver);
return dataSource;
}
}
<!--2、开启Spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
- 在Service层需要事务的地方添加注解@Transactional
用注解的好处是可以定向确定哪些方法需要配置事务管理,劣势是每新增一个方法就需要配置注解。相比较XML而言,XML可以一劳永逸。
@Service(value = "us")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true) //先配置全局
public class UserServiceImpl implements UserService{
@Autowired
UserDao ud;
@Override
public List<User> findUserByID(Integer id) {
return ud.findUserByID(id);
}
@Override
public List<User> findUserByName(String name) {
return ud.findUserByName(name);
}
@Override
@Transactional(propagation = Propagation.REQUIRED,readOnly = false) //再配置局部
public void transfer(String sourceName, String targetName, Double money) {
ud.transfer(sourceName,targetName,money);
}
@Override
public void update(User user) {
ud.update(user);
}
}