AspectJ框架的通知类型
1)@Before前置通知
2)@AfterReturning后置通知
3)@Around环绕通知
4)@After最终通知
5)@Pointcut给切入点表达式起别名
什么是切入点表达式
决定切入切面功能的位置,是一个由规范的公式
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
简化公式:
execution(方法返回值 方法声明(参数))
常见符号:
.. 在路径中,代表当前路径下所有子目录及所有类; 在方法参数中,代表任意参数列表
* 代表任意字符
示例:
execution(public * *(..)):公共访问权限的所有方法
execution(* set*(..)):以set开头的所有方法
execution(* com.xyz.service.impl.*.*(..)):impl包下的所有类中所有方法的任意参数任意返回值类型的方法
execution(* com.xyz.service..*.*(..)):service及其子包下的所有类中所有方法的任意参数任意返回值类型的方法
execution(* *..service.*.*(..)) :只要是以service包结尾的所有类中的所有方法的任意参数任意返回值类型的方法
execution(* *.service.*.*(..)) :service前只有一级目录
如何添加AspectJ框架
- 在pom.xml文件中添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
- 开发业务接口
- 开发业务接口实现类
- 开发切面类,开发切面方法完成切面功能
- 添加切入点表达式和通知决定切哪儿,什么时候切入切面功能
- 添加applicationContext.xml配置文件,配置参数
@Before前置通知
在目标方法执行前切入切面
@Aspect //此注解就是交给AspectJ框架去处理切面功能
public class MyAspect {
/**
* 切面类中所有切面的功能都是由切面方法来实现的
* 前置通知切面方法的规范
* 1)访问权限是public
* 2)切面方法没有返回值void
* 3)切面方法的名称自定义
* 4)切面方法一般没有参数,如果有参数则只能是JoinPoint类型
* 5)使用@Before注解声明是前置通知
* 参数:
* value:指定切入点表达式
*/
@Before("execution( * *..impl.*.*(..))")
public void myBefore() {
System.out.println("前置通知功能实现...");
}
@Before("execution(* *..impl.*.*(..))")
public void myBefore(JoinPoint joinPoint) {
System.out.println("前置功能...");
System.out.println("方法签名: " + joinPoint.getSignature());
System.out.println("方法参数: " + Arrays.toString(joinPoint.getArgs()));
}
}
@AfterReturning后置通知
在目标方法执行后切入切面功能
可以在切面方法中得到目标方法的返回值
目标方法的返回值是通过切面方法的参数传入,就会涉及到传参的值传递和引用传递,如果目标方法的返回值是简单类型(8中基本类型 + String类型)则不可变,如果是引用类型则可改变
@Aspect
public class MyAspect {
/**
* 后置通知切面方法的规范
* 1)访问权限是public
* 2)切面方法没有返回值
* 3)切面方法名称自定义
* 4)切面方法可以没有参数(目标方法没有返回值),可以有参数,参数就是目标方法的返回值
* 5)使用@AfterReturning注解声明是后置通知
* 参数:
* value: 指定切入点表达式
* returning: 接目标方法的返回值,此参数的值与切面方法形参的名称完全一致
*/
@AfterReturning(value = "execution(* *..impl.*.*(..))", returning = "obj")
public void myAfterReturning(Object obj) {
System.out.println("后置通知...");
//确定是否有返回值
if (obj != null) {
//进行类型判断
if (obj instanceof String) {
String result = (String) obj;
System.out.println("在切面方法中改变 了目标方法的返回值: " + result.toUpperCase());
}
if (obj instanceof Student) {
Student student = (Student) obj;
student.setName("御霭");
System.out.println("在切面方法中改变了目标方法的返回值: " + student);
}
}
}
}
@Around环绕通知
它是通过拦截目标方法,在其前后增强切面功能,它是功能最强大的通知
它可以随意改变目标方法的返回值
事务就是使用的环绕通知
环绕通知切面方法的返回值就是目标方法的返回值
环绕通知切面方法的参数就是目标方法本身
环绕通知可以控制目标方法的访问
@Aspect
@Component
public class MyAspect {
/**
* 环绕通知切面方法的规范
* 1)访问权限public
* 2)切面方法有返回值,而且此返回值就是目标方法的返回值
* 3)切面方法名称自定义
* 4)切面方法有参数,参数就是目标方法本身,参数类型是ProceedingJoinPoint
* 5)回避异常Throwable
* 6)使用注解@Around声明是环绕通知
* 参数:
* value:指定切入点表达式
*/
@Around(value = "execution(* *..impl.*.*(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//根据目标方法的第一个参数,判断如果是张三则可访问目标方法,否则拒绝访问
Object[] params = pjp.getArgs();
if (params != null && params.length > 1) {
String name = (String) params[0];
if ("张三".equals(name)) {
//前切功能实现
System.out.println("环绕通知中的前切功能实现...");
//目标方法调用
String result = (String) pjp.proceed(params);
//后切功能实现
System.out.println("环绕通知中的后切功能实现...");
//返回值
return result.toUpperCase();
}
}
System.out.println("您无权访问目标方法...");
return null;
}
}
最终通知@After
无论目标方法是否正常执行,最终通知的代码都会被执行,它有点类似与finally的语句
它主要用来关闭资源,清理对象,做一些善后的工作
@Aspect
@Component
public class MyAspect {
/**
* 最终通知的切面方法的规范
* 1)访问权限是public
* 2)切面方法没有返回值void
* 3)切面方法名称自定义
* 4)切面方法没有参数,如果有则是JoinPoint
* 5)使用@After注解声明是最终通知
* 参数:
* value: 指定切入点表达式
*/
@After("execution(* *..impl.*.*(..))")
public void myAfter() {
System.out.println("我是最终通知");
}
}
给切入点表达式起别名@Pointcut
@Pointcut 定义切入点别名,使用一个无参无返回值实现的方法名称作为别名,在所有的切入点表达式中使用些别名
@Aspcet
@Component
public class MyAspect {
@AfterReturning(value = "myCut()")
public void myAfterReturning() {
System.out.println("我是后置通知...");
}
@Pointcut("execution(* *..impl.*.*(..))")
public void myCut() {
}
}
切换AspectJ框架动态代理的类型
- 默认是JDK动态代理
- 注册时: aop:aspectj-autoproxy/
- 接动态代理对象的类型只能是接口的类型,实现类不可以
- SomeService someService = (SomeService) app.getBean(“someServiceImpl”);
- 如果使用实现类去接,则报异常java.lang.ClassCastException: com.sun.proxy.$Proxy16 cannot be cast to
icu.sandink.service.impl.SomeServiceImpl
- 可以设置为CGLIB动态代理(子类代理)
- 注册: <aop:aspectj-autoproxy proxy-target-class=“true”/>
- 接动态代理对象时可接口可实现类
- CGLIB动态代理对象的类型:class icu.sandink.service.impl.SomeServiceImpl E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB2d35d014
- SomeServiceImpl someService = (SomeServiceImpl) app.getBean(“someServiceImpl”);
- SomeService someService = (SomeService) app.getBean(“someServiceImpl”);
总结: 一定用接口来接动态代理对象
SM整合
为了验证事务的效果,必须访问数据库,使用MyBatis框架进行数据库的访问,要完成SM的整合
整合步骤:
1)添加新的模块,选择quickstart模板
2)添加可视化,先删除users表,再键users用户表和accounts账户表
3)修改目录
4)修改pom.xml文件(老师提供)
5)添加jdbc.properties文件
6)添加mybatis-config.xml文件和XXXMapper.xml文件的模板
7)添加mybatis-config.xml并开发(删光)
8)添加applicationContext_mapper.xml文件(接管mybatis-config.xml文件中的大部分功能)
9)添加applicationContext_service.xml文件并开发
10)添加实体类Users和Accounts
11)添加mapper层,添加UsersMapper接口和UsersMapper.xml文件并开发
12)添加service层,添加UsersService接口和UsersServiceImpl实现类
13)添加测试类进行功能测试
applicationContext_mapper.xml 接管 mybatis-config.xml
<!--applicationContext_mapper.xml-->
<beans>
<!--先读取properties文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--创建数据源-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置SqlSession-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="dataSource"/>
<!--配置实体类-->
<property name="typeAliasesPackage" value="icu.sandink.pojo"/>
<!--配置mybatis-config.xml-->
<property name="configLocation" value="mybatis-config.xml"/>
</bean>
<!--注册mapper.xml文件-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="icu.sandink.mapper"/>
</bean>
</beans>
<!--mybatis-config.xml-->
<beans>
<!--设置日志输出-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注册插件-->
<!--<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>-->
</beans>
事务
什么是事务
事务原本是数据库中的概念,在实际项目的开发中,进行事务的处理一般是在业务逻辑层,即Service层.
这样做是为了能够使用事务的特性来管理关联操作的业务.
Spring事务处理的两种方式:
1)注解事务(编程式事务)
2)声明式事务(推荐使用)
事务管理器
所有事务的处理,必须添加事务管理器
不同的技术或框架,底层提交或回滚事务的对象是不一样的
JDBC | Connection | con.commint(); | con.rollback(); |
---|---|---|---|
MyBatis | SqlSession | sqlSession.commit(); | sqlSession.rollback(); |
Hibernate | Session | session.commit(); | session.rollback(); |
使用事务管理器根据不同的技术或框架创建相应的提交或回滚事务的对象
MyBatis框架使用的事务管理器是DataSourceTransactionManager ⇒ 在applicationContext_service.xml文件中进行设置
Spring事务的四大隔离级别
1)未提交读(Read Uncommitted): 允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
2)提交读(Read Committed): 只能读取已提交的数据. Oracle等多数数据库默认都是该级别(不重复读)
3)可重复度(Repeated Read): 在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读
4)串行读(Serializable): 完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
使用数据库默认的隔离级别
mysql数据库的默认的隔离级别是可重复读
注解事务(编程式事务)的添加
如果业务逻辑层的方法在执行过程中出错了,则回滚数据库中已经执行的操作
添加步骤
- 1)在applicationContext_service.xml文件中添加事务管理器
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<!--切记切记要配置数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
- 2)在applicationContext_service.xml文件中添加事务的注解驱动
<tx:annotation-driven/>
- 3)在类上或方法上添加@Transactional注解,声明事务管理
public class XX {
@Transactional(noRollbackFor = ArithmeticException.class)
public int save(Accounts accounts) {
int num = accountsMapper.save(accounts);
System.out.println("帐户增加成功!num= " + num);
//手工抛出异常
// System.out.println(1 / 0);
return num;
}
}
@Transactional参数解析
@Transactional(
noRollbackFor = ArithmeticException.class, //发生指定的异常不回滚,使用异常的类型
noRollbackForClassName = "ArithmeticException", //发生指定的异常不回滚,使用异常的类型
rollbackFor = NullPointerException.class, // 发生指定的异常回滚,使用异常的类型
rollbackForClassName = "NullPointerException", //发生指定的异常回滚,使用异常的名称
isolation = Isolation.DEFAULT, //使用数据库默认的隔离级别,它的默认值就是DEFAULT
timeout = -1, //设置转接超时,-1为永不超时,默认值就是-1
readOnly = true, //如果是查询,必须设置为true,默认是false
propagation = Propagation.REQUIRED //必须添加事务管理
)
事务的传播特性
事务的传播特性用来设置事务之间的互斥,加入,归并等.它决定最后事务的提交和回滚.
常用
PROPAGATION_REQUIRED:必被包含事务
PROPAGATION_REQUIRES_NEW:自己新开事务,不管之前是否有事务
PROPAGATION_SUPPORTS:支持事务,如果加入的方法有事务,则支持事务,如果没有,不单开事务
PROPAGATION_NEVER:不能运行中事务中,如果包在事务中,抛异常
PROPAGATION_NOT_SUPPORTED:不支持事务,运行在非事务的环境
不常用
PROPAGATION_MANDATORY:必须包在事务中,没有事务则抛异常
PROPAGATION_NESTED:嵌套事务
声明式事务的开发
可以在applicationContext_service.xml文件中进行声明式事务的设定.一次设定,所有项目均有效.
声明式事务的添加,要求方法必须命名规范.
增加功能: insert add save create
删除操作: delete drop remove clear
更新操作: update modify change set
查询操作: select find get query search
添加声明式事务的步骤
1)在applicationContext_service.xml文件中添加事务管理器
2)在applicationContext_service.xml文件中事务切面
3)绑定切面和切入点
声明式事务代码
<beans>
<!--导入applicationContext_mapper.xml-->
<import resource="applicationContext_mapper.xml"/>
<!--添加包扫描-->
<context:component-scan base-package="icu.sandink.service.impl"/>
<!--添加事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--添加事务切面-->
<tx:advice id="myadvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*select*" read-only="true"/>
<tx:method name="*find*" read-only="true"/>
<tx:method name="*query*" read-only="true"/>
<tx:method name="*search*" read-only="true"/>
<tx:method name="*get*" read-only="true"/>
<tx:method name="*insert*" propagation="REQUIRED"/>
<tx:method name="*add*" propagation="REQUIRED"/>
<tx:method name="*save*" propagation="REQUIRED"/>
<tx:method name="*create*" propagation="REQUIRED"/>
<tx:method name="*delete*" propagation="REQUIRED"/>
<tx:method name="*drop*" propagation="REQUIRED"/>
<tx:method name="*clear*" propagation="REQUIRED"/>
<tx:method name="*remove*" propagation="REQUIRED"/>
<tx:method name="*update*" propagation="REQUIRED"/>
<tx:method name="*set*" propagation="REQUIRED"/>
<tx:method name="*change*" propagation="REQUIRED"/>
<tx:method name="*modify*" propagation="REQUIRED"/>
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!--绑定切面和切入点-->
<aop:config>
<aop:pointcut id="mycut" expression="execution(* com.bjpowernode.service.impl.*.*(..))"/>
<aop:advisor advice-ref="myadvice" pointcut-ref="mycut"/>
</aop:config>
</beans>
SpringBean的生命周期
Spring创建对象的过程
@Service
public class UsersServiceImpl implements UsersService {
@Autowired
UsersMapper usersMapper;
@Autowired
AccountsService accountsService;
Book book; //要访问数据库进行数据填充
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insertUsers(Users u) {
int num = usersMapper.insertUsers(u);
System.out.println("用户增加成功! num = " + num);
//为了验证事务的传播特性,在UsersServiceImpl实现类中调用帐户的增加方法
//帐户增加AccountsMapper可以,AccountsServiceImpl也可以,该用谁???
//结论:必须使用AccountsServiceImpl类的对象
num += accountsService.save(new Accounts(200, "李四", "帐户好的呢!"));
return num;
}
}
SpringBean的生命周期
1)实例化:调用类的无参构造方法创建出本类的一个实例(半成品)usersServiceImpl.
2)依赖注入(属性填充):Spring提供了三级缓存来进行对象的依赖注入.当依赖注入的对象不存在(usersMapper)时,暂停当前对象的创建,转去创建依赖注入的对象,创建成功后放入单例池,再完成当前的依赖注入.
3)初始化:对类中复杂成员的注入值book.
4)AOP判断:如果有切面,则在半成品上切入切面的功能,并返回动态代理对象
5)放入单例池.
Spring框架中常见设计模式
1)工厂模式:Spring通过工厂模式BeanFactory,ApplicationContext创建Bean对象。
2)代理设计模式:SpringAOP的实现,底层使用了动态代理模式。
3)单例模式:Spring中的Bean默认都是单例的。
4)模板方法模式:Spring中jdbcTemplate,hibernateTemplate等以Template结尾的类都用到了模板模式。
5)装饰模式:我们的项目需要连接多个数据库,而不同的客户在访问时可能会访问不同的数据库,这种模式可以让我们根据用户的需求动态的切换数据库。
6)观察者模式:Spring的事件驱动是观察者模式的应用。
7)适配器模式:SpringAOP的增强功能使用到了适配器模式。