Spring入门笔记2

Spring入门笔记2

1. Spring整合junit

注意:spring5.x必须对应junit4.12及以上版本,否则报init初始化错误

就是为了避免重复代码

这些写一次就好了.png

其实可以用junit的注解@Before去做的,但是公司开发的时候开发工程师和测试工程师时两个人,测试工程师只关注service.findAll()这个接口,对于上面红色框框出的代码,可能测试工程师不懂spring,所以必须由开发工程师写,但是开发又不写@before代码块,只写这个空测试类

所以可以通过注解/xml的方式在空类上面创建ioc容器


  1. 导坐标


  1. 测试类上方加注解

@RunWith(SpringJUnit4ClassRunner.class)


  1. 告知spring的运行器用的是注解还是xml

@ContextConfiguration

两个属性:locations:指定xml的位置,记得加上classpath:

classes:指定注解类所在位置

步骤2 3示意图


  1. 注入

注入


2. Spring中的AOP

2.1 AOP的概念以及作用(了解,代码看看就行)

AOP的本质是动态代理,就是方法增强,比如原来有一个方法funA(),里面输出了一句”world”,现在通过AOP/动态代理等方式对funA()进行改造,使得可以在执行funA之前先输出一句”hello”,在执行funA之后输出”!!!!”,这就是方法增强。

为什么要这么做呢?在实际开发中体现为:我们在业务层编写的时候只需要关注业务逻辑,而不用对每个方法都

开启事务

业务逻辑

提交事务(正常)

回滚事务(异常)

释放连接

如果每个方法都要这样写就太麻烦了,因此我们通过aop来实现对业务层的方法增强,之后事务管理器负责:

开启事务

提交事务(正常)

回滚事务(异常)

释放连接

而这样依赖业务层只有:

业务逻辑


使用AOP/动态代理后

而事务的管理交给代理类(这里先用动态代理介绍方法增强,AOP原理相同,随便看一下代码,后期我们使用spring的事务管理器,就不用自己写代码了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 用于创建Service的代理对象的工厂
*/
public class BeanFactory {

private AccountService accountService;

public final void setAccountService(AccountService accountService) {
this.accountService = accountService;
}

private TransactionManager txManager;

public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}

public AccountService getAccountService() {
return (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行事务
returnValue = method.invoke(accountService, args);
//3.提交事务
txManager.commit();
//4.返回结果
return returnValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release();
}
}
});
}
}

至于里面的txManager我们用的是自己写的事务管理器(随便看一下代码,后期我们使用spring的事务管理器,就不用自己写代码了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* 和事务管理相关的工具类:开启事务。提交事务,回滚事务和释放连接
*/
public class TransactionManager {

private ConnectionUtils connectionUtils;

public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}

/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}

/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}

/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}

/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close(); //还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}

同时,为了解决事务和线程没有绑定,导致有异常不能回滚的现象,我们还编写了一个事务和线程绑定的类(随便看看就行,后期我们用spring自带的事务管理器,避免了这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
* 在两次dao.update之间抛出或者主动写一个错误如(int i = 1 / 0;)
* 则第一个update提交了,第二个没有,导致一个账户钱减少了另一个账户前却没有增加。这不是我们想要的效果,我们想让前面的操作事务回滚。
* 并不是传统意义上的事务回滚,事务回滚指的是update方法内部错误,回到没有update之前的状态。
* 这里指的是包括前面3次事务已经提交并且成功,后面的事务连提交都没有。
* 所以错不在事务,而在连接。
* 并不是没有事务(没有事务的话save,find方法也不能提交),原因是因为dao方法获取了4次连接,每个connection都有自己的事务。
*/

/**
* 在service中将connection和线程绑定,从而使一个线程中只有一个能控制事务的connection对象,要么一起发生,要么都不发生。
* 事务的控制应该是在业务层而不是在持久层
* @author wjw
*/
public class ConnectionUtils {

private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

private DataSource dataSource;

public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

public Connection getThreadConnection(){
try {
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}

/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}

2.1.1 动态代理的写法(了解,小插曲)

有人说,哎呀 我不会写动态代理,这又是匿名内部类的又是什么Proxy

我说你会你就会,你认真看啊

动态代理

其实要记的就只有

  1. 你得知道动态代理是什么类下的什么方法吧:Proxy.newProxyInstance()

  2. 参数列表其实是不用记的,你敲下1就会有提示,按类型填就行了,比如他会告诉你第一个参数是类加载器,你只需要记住填的是“要增强的方法的类”的类加载器,就不会错,第二个也是,你只要记住是“要增强的方法的类”原本实现的接口就行

  3. new InvocationHandler(),这个是要记住的,你敲下前几个字母就会自动补全下面的invoke方法了

  4. 至于里面的方法,我们方法增强是为了事务管理,所以里面才写事务的相关代码,如果方法增强是为了别的,那就视情况而定呀,不是固定代码,要注意的只有method.invoke()返回值是个Object的returnValue对象


关于动态代理的内容,在此不做过多的展开(单独做了动态代理笔记),只是为了说明方法增强是怎么回事,如何解决事务等方法重复代码的问题,以及了解AOP其实本质就是动态代理,实现了方法增强。


2.2 spring中基于xml的AOP配置步骤

  1. 把通知Bean也交给spring来管理

  2. 使用aop:config标签表明开始AOP配置

  3. 使用aop:aspect标签表明配置切面

  • id:是给切面提供一个标识

  • ref:指定通知类bean的id

  1. 在aop:aspect标签内部使用对应标签来配置通知的类型

我们现在的示例是想在切入点方法执行前执行printLog,所以是前置通知

aop:before:表示配置前置通知

  • method属性:指定Logger类中哪个方法是前置通知

  • pointcut属性:指定切入点的表达式,指的是对业务层中哪些方法增强


2.3 切入点表达式的写法:

关键字:execution(表达式)

表达式:访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)

  • 标准写法:public void com.wjw.service.impl.AccountServiceImpl.saveAccount()

  • 访问修饰符可以省略:void com.wjw.service.impl.AccountServiceImpl.saveAccount()

  • 返回值可以使用通配符*表示任意返回值:* com.wjw.service.impl.AccountServiceImpl.saveAccount()

  • 包名可以用通配符表示任意包,但是有几级包(从com开始计算),就有多少个*.:* *.*.*.*.AccountServiceImpl.saveAccount()

    或者可以使用*..表示当前包及其子包:* *..AccountServiceImpl.saveAccount()

  • 类名和方法名都可以用*来实现通配:* *..*.*()

  • 参数列表可以直接写数据类型:

 基本类型直接写名称:* *..*.*(int)

 引用类型写包名.类名:* *..*.*(java.lang.String)

  可以实现通配:

   可以用*实现通配,代表有参数且任意类型:* *..*.*(*)

   可以用..实现通配,有无参数都可以且可以任意类型:* *..*.*(..)

  • 全通配写法:* *..*.*(..)


实际开发中常用切入点表达式写法都是切入到业务层下的所有方法:* com.wjw.service.impl.*.*(..)


2.4 通知类型

<aop:before>前置通知,在切入点方法执行前执行

<aop:after-returning>后置通知,在切入点正常执行后执行

<aop:after-throwing>异常通知,在切入点方法执行异常后执行(相当于catch)

<aop:after>最终通知,无论是否正常都会执行(相当于finally)

通知标签

<aop:around>环绕通知,其实环绕通知就是上面所有通知的总和,在环绕通知中,代码写在哪里就是什么通知。Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。该接口可以作为环绕通知的方法参数

84MKTx.png

简单地说,spring的环绕通知就是我们可以用代码的方式写所有的通知,位置就是不同的方式。(其实就是动态代理本来的写法)


2.5 简化重复pointcut,可以单独抽取出来

<aop:pointcut>标签 id是唯一标识,expression就是原来的表达式

原来表达式不再用pointcut而是用point-ref,值就是<aop:pointcut>的id

也可以将<aop:pointcut>标签配置在<aop:aspect>外面,这样所有切面可用,注意:一定要在aspect前面

位置


3. 注解方式AOP

要使用注解AOP,必须如下配置

要注意,这段约束在官网是没有现成的,先搜xmlns:aop的约束复制下来,再找xmlns:context找到红框中的三段复制下来

<aop:aspectj-autoproxy>是开启注解AOP的钥匙


切面类有这几点

3.1 @Aspect

类上方添加,表明这是一个切面


3.2 @Pointcut

<aop:pointcut>一样,代表一个表达式,注意写法是通过空函数的形式

1
2
3
@ Pointcut(“execution(“* com.wjw.service.impl.*.*(..)”)”)

public void pt1(){}

3.3 @Before(“methodName()”)

注意不要少了括号,前置通知


3.4 @AfterRuturning(“methodName()”)

注意不要少了括号,后置通知


3.5 @AfterThrowing(“methodName()”)

注意不要少了括号,异常通知


3.6 @After(“methodName()”)

注意不要少了括号,最终通知


注意事项:

spring的注解方式AOP的通知顺序是:前置 最终 后置/异常

最终通知的位置不对

而使用环绕通知的话,因为是自己写的,所以位置和xml还有理解中的是一致的


完全不用XML的方式

1
2
3
4
5
@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

就是在“完全不用XML方式实现ioc”的那个配置类上方再加上@EnableAspectJAutoProxy


4. Spring自带的事务管理器:声明式事务控制

还记得之前“九.1”那个案例吗(翻回去看),我们现在使用AOP代替了动态代理,同样解决了事务重复代码的问题,对事务控制可以抽取出来了

可是,之前案例中的两个类是自己写的:

TransactionManager:事务控制的类(包含提交,回滚等方法),自己写太不现实,我们现在要改用Spring自带的事务管理器。

ConnectionUtils:这是之前为了防止异常无法回滚的问题而写的类,现在我们通过Spring自带的事务管理器,可以避免这个问题。


4.1 基于XML的声明式事务控制

使用步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring中基于xml的声明式事务控制基本步骤
1.配置事务管理器
2.配置事务通知
导入事务的约束,在data access里xmlns:tx,注意这里其实是包含了aop的,事务通知离不开aop
使用tx:advice标签配置事务通知
id:唯一标识
transactionManager:给事务通知提供一个事务管理器引用,注意不是ref
3.配置AOP中的通用切入点表达式
4.建立事务通知和切入点表达式的对应关系
5.配置事务的属性,在事务的通知tx:advice内部
isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库默认级别
propagation:用于指定事务的传播行为,默认值是REQUIRED,表示一定会有事务(增删改),查询方法用SUPPORTS
read-only:用于指定事务是否只读,只有查询方法才能设置为true,默认为false表示读写
rollback-for:用于指定一个异常,当该异常产生时事务回滚,产生其他异常时事务不回滚,没有默认值 表示任何异常都回滚
no-rollback-for:用于指定一个异常,当该异常产生时事务不回滚,产生其他异常时事务回滚,没有默认值 表示任何异常都回滚
timeout:用于指定事务超时时间,默认为-1表示永不超时,秒为单位

也就是说,对于前置通知是事务的开启,后置通知是事务的提交,异常通知是事务的回滚,最终通知是连接的释放这种基本上是固定操作

spring封装了一个DataSourceTransactionManager自动完成,只要是在2中的transaction-manager中引用了这个类,那么就不必关注这几个操作了

更多的是关注不同方法对于事务的处理(事务的属性)比如在上一个事务未完成前下一个事务是否可以读取(find就可以其他操作则不行)

数据源还是原来的,也给出截图


4.2 基于注解的声明式事务控制

使用步骤

1
2
3
4
spring中基于注解的声明式事务控制基本步骤
1.配置事务管理器
2.开启spring对注解事务的支持
3.在需要事务支持的地方使用@Transactional注解(service层实现类上方)

惊喜的发现,由于spring的事务控制是写好的,所以连aop那些@Aspect啊@Before啊都不用了,spring知道执行之后commit,在异常之后AfterThrowing,只要@Transactional后所有方法都自动添加了事务


别忘了这些细节的东西

84QYCT.png

所以只能自己补上(@AutoWired可以不要,用的是xml)


4.3 纯注解的声明式事务控制

算了,别整这些有的没的,反正也没人用


附录:

ioc下通过继承JdbcDaoSupport如何注入数据源

这是由于类中使用的是继承方式

事实上就自动获得了所有父类的方法,而父类的class文件里我们可以看到setDataSource方法,因此我们只要根据这个set方法设置dao的注入就好了

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×