Spirng AOP
AOP概念
AOP(Aspect Oriented Programming) 面向切面编程
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP的作用及其优势
- 在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 减少重复代码,提高开发效率,并且便于维护
例如:
代码执行过程中,需要进行当前状态的记录并且打印出来。
日志记录、打印功能为一个单独模块
业务代码功能为一个单独模块
通过配置文件将两个模块关联到一起,单独看它们都是独立的存在,互不影响。
AOP底层实现
实际上,AOP的底层是通过Spring提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
AOP动态代理技术
常用动态代理技术
- JKD代理:基于接口的动态代理技术
- Cglib代理:基于父类的动态代理技术
JDK动态代理
下面是实现过程,这里是需要做一个了解,因为AOP对应的底层已经完成了。
public class Enhance {
public void before(){
System.out.println("前置增强模拟");
}
public void after(){
System.out.println("后置增强模拟");
}
}
//模拟:增强方法
//基于接口
public interface Target {
void save();
}
public class TargetImpl implements Target {
@Override
public void save() {
System.out.println("基于接口的JDK代理技术动态生成代理对象,由代理对象调用save方法");
}
}
//实现
public static void main(String[] args) {
final Enhance enhance = new Enhance();
final TargetImpl target = new TargetImpl();
// 返回值 动态生成的代理对象
//基于JDK代理技术:基于接口,因此动态生成的代理对象使用接口来对接
Target proxyInstance= (Target) Proxy.newProxyInstance(
//目标类加载器
target.getClass().getClassLoader(),
//目标对象相同的接口字节码对象数组
target.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 调用代理对象的任何方法 实质上执行的都是invoke方法
* @param proxy 代理对象
* @param method 目标方法
* @param args
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法增强
enhance.before();
//执行目标方法,这里invoke不是上面代理对象的invoke方法,而是反射中的方法
method.invoke(target,args);
enhance.after();
return null;
}
}
);
//调用代理对象的方法,实质调用invoke方法
proxyInstance.save();
}
/*
前置增强模拟
基于接口的JDK代理技术动态生成代理对象,由代理对象调用save方法
后置增强模拟
*/
cglib的动态代理
public static void main(final String[] args) {
final Enhance enhance = new Enhance();
final TargetImpl target = new TargetImpl();
// 返回值 动态生成的代理对象
//基于cglib代理
//1.创建增强器
Enhancer enhancer = new Enhancer();
//2.设置父类(目标)
enhancer.setSuperclass(TargetImpl.class);
//3.设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//执行前置
enhance.before();
//执行目标
method.invoke(target,args);
//执行后置
enhance.after();
return null;
}
});
//4.创建代理对象
TargetImpl target1 = (TargetImpl) enhancer.create();
target1.save();
}
/*
前置增强模拟
基于接口的cglib代理技术动态生成代理对象,由代理对象调用save方法
后置增强模拟
*/
上面都是一个理解,Spring AOP模式下都已经封装完成,需要做的只是在配置文件中做配置即可。
AOP相关概念
Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
在正式讲解AOP的操作之前,我们必须理解AOP的相关术语,常用的术语如下:
-
Target (目标对象) :代理的目标对象
-
Proxy (代理) : 一个类被AOP织入增强后,就产生一个结果代理类
-
Joinpoint (连接点) :所谓连接点是指那些被拦截到的点(方法)。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。能够被增强的方法就叫做连接点(每个公民都可以成为人大代表)
-
Pointcut(切入点):对于连接点进行拦截的定义(被选择成为人大代表的公民)
-
Advice(通知/增强):拦截到连接点之后要做的事情
-
Aspect(切面):切点+增强(通知) == 目标方法 + 增强
-
Weaving(织入): 把增强应用到目标对象来创建新的代理对象的过程。切点 和通知 结合到一起的过程。
AOP开发明确的事项
-
需要编写的内容
- 编写核心业务代码(目标类的目标方法)
- **编写切面类,**切面类中通知(增强功能方法)
- 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
-
AOP技术实现的内容
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
-
AOP底层中的代理方式
在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式
基于XML的AOP的开发
快速入门
①导入AOP相关坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
②创建目标接口和目标类(内部有切点)
public interface Target {
void save();
}
public class TargetImpl implements Target {
@Override
public void save() {
System.out.println("基于XML配置文件实现AOP");
}
}
③创建切面类(内部有增强方法)
public class Aspect {
//前置增强
public void before(){
System.out.println("前置增强");
}
}
④将目标类和切面类的对象创建权交给spring
<!-- 目标对象:切点-->
<bean class="AOP.impl.TargetImpl"></bean>
<!-- 切面对象:通知-->
<bean id="aspect" class="AOP.impl.Aspect"></bean>
⑤在applicationC ontext.xml中配置织入关系
<!-- 配置织入:告诉Spring框架哪些方法(切点)需要进行增强(通知)-->
<aop:config>
<!-- 声明切面-->
<aop:aspect ref="aspect">
<!-- 对应方法(切点)的前置增强-->
<aop:before method="before" pointcut="execution(public void AOP.impl.TargetImpl.save())"/>
</aop:aspect>
</aop:config>
⑥测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class test2 {
@Autowired
private Target target;
@Test
public void test1(){
target.save();
}
}
-
切点表达式的写法
<aop:before method="before" pointcut="execution(public void AOP.impl.TargetImpl.save())"/>
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号*****代表任意
- 包名与类名之间**一个点 .**代表当前包下的类,**两个点…**表示当前包及其子包下的类
- 参数列表可以使用**两个点…**表示任意个数,任意类型的参数列表
//例如: execut ion (public void com.ithema.aop.Target.method()) execution(void com.itheima.aop.Target.*(..)) //Target类下的 void类型的任意方法任意参数 execution(* com. itheima.aop.*.*(..)) //aop包下的任意类中任意方法任意参数都是切点 execution(* com.itheima.aop..*.*(..)) //aop包以及子包下的任意类中任意方法任意参数都是切点 execution(* *..*.*(..)) //所有
-
通知类型
通知的配置语法:
<aop:通知类型 method=“切面类中方法名” pointcut= “切点表达式"> </aop:通知类型>
<!-- 配置织入:告诉Spring框架哪些方法(切点)需要进行增强(通知)--> <aop:config> <!-- 声明切面--> <aop:aspect ref="aspect"> <!-- 对应方法(切点)的前置增强--> <!-- <aop:before method="before" pointcut="execution(public void AOP.impl.TargetImpl.save())"/>--> <aop:around method="around" pointcut="execution(* AOP.impl.*.*(..))"></aop:around> <aop:after-throwing method="afterThrowing" pointcut="execution(* AOP.impl.*.*(..))"></aop:after-throwing> <aop:after method="after" pointcut="execution(* AOP.impl.*.*(..))"></aop:after> </aop:aspect> </aop:config>
-
切点表达的抽取
<!-- 抽取表达式--> <aop:pointcut id="pointCut" expression="execution(* AOP.impl.*.*(..))"/> <aop:around method="around" pointcut-ref="pointCut"></aop:around> <aop:after-throwing method="afterThrowing" pointcut-ref="pointCut"></aop:after-throwing> <aop:after method="after" pointcut-ref="pointCut"></aop:after>
基于注解的AOP开发
①创建目标接口和目标类(内部有切点)
②创建切面类(内部有增强方法)
③将目标类和切面类的对象创建权交给spring
@Component
public class TargetImpl implements Target {
@Override
public void save() {
System.out.println("基于XML配置文件实现AOP");
int i = 6;
// if (i > 5){
// i = i /0;
// }
}
}
④在切面类中使用注解配置织入关系
@Component("aspect")
//标注当前类是一个通知类
@Aspect
public class MyAspect {
//前置增强
@Before("execution(* annoation.TargetImpl.*(..))")
public void before(){
System.out.println("前置增强");
}
//后置增强
@AfterReturning("execution(* annoation.TargetImpl.*(..))")
public void afterReturning(){
System.out.println("后置通知");
}
//ProceedingJoinPoint: 正在执行的连接点===切点
@Around("execution(* annoation.TargetImpl.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前");
Object object = joinPoint.proceed();
System.out.println("环绕后");
}
@AfterThrowing("execution(* annoation.TargetImpl.*(..))")
public void afterThrowing(){
System.out.println("抛出异常");
}
//最终通知
@After("execution(* annoation.TargetImpl.*(..))")
public void after(){
System.out.println("最终通知");
}
}
⑤在配置文件中开启组件扫描和AOP的自动代理
<!-- 组件扫描-->
<context:component-scan base-package="annoation"/>
<!-- AOP自动代理-->
<aop:aspectj-autoproxy/>
⑥测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class annoationTest {
@Autowired
private Target target;
@Test
public void test() {
target.save();
}
}
注解通知类型
注解切点表达式的抽取
同xml配置AOP一样,注解也可以将表达式进行抽取。
使用**@Pointcut注解进行抽取**
//定义切点表达
@Pointcut("execution(* annoation.TargetImpl.*(..))")
public void pointCut(){
}
//前置增强
@Before("pointCut()")
public void before(){
System.out.println("前置增强");
}
//后置增强
@AfterReturning("pointCut()")
public void afterReturning(){
System.out.println("后置通知");
}
//ProceedingJoinPoint: 正在执行的连接点===切点
@Around("pointCut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前");
Object object = joinPoint.proceed();
System.out.println("环绕后");
}
@AfterThrowing("pointCut()")
public void afterThrowing(){
System.out.println("抛出异常");
}
//最终通知
@After("pointCut()")
public void after(){
System.out.println("最终通知");
}
Spring事务控制
编程式事务控制相关对象
PlatformTransactionManager
PlatformTransactionManager接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法。
TransactionDefinition
TransactionDefinition是事务的定义信息对象,里面有如下方法:
事务隔离级别
设置隔离级别,可以解决事务并发产生的问题如 丢失更新、脏读、不可重复读和幻读。
TransactionStatus
TransactionStatus接口提供的是事务具体的运行状态,方法介绍如下。
基于XML的声明式事务控制
Spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在Spring配置文件中声明式的处理事务来代替代码式的处理事务。
声明式事务处理的作用
-
事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可
-
在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便
注意: Spring声明式事务控制底层就是AOP。
声明式事务控制实现
-
平台事务管理器配置
<!-- 配置平台事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
-
事务通知的配置
<!-- 事务增强 通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 设置事务属性信息--> <tx:attributes> <!-- 对应哪些切点(method)增强 "*":任意方法名称 --> <tx:method name="*"/> <!-- isolation:--> <!-- 隔离级别:读未提交/读已提交/可重复读/串行化--> <!-- propagation:传播行为--> <!-- timeout:超时时间--> <!-- read-only:是否可读--> <tx:method name="transfer" isolation="READ_COMMITTED" propagation="REQUIRED" read-only="false"/> </tx:attributes> </tx:advice>
-
事务aop织入的配置
<!-- 配置事务AOP织入--> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.service.impl.*.*(..))"></aop:advisor> </aop:config>
整个场景:银行转账
转账业务
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
//银行转账
public void transfer(String ouMan,String inMan,double money){
//开启事务
//通过事务保证转账成功
accountDao.out(ouMan,money);
accountDao.in(inMan,money);
//关闭事务
}
}
数据库更新操作
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void out(String outMan, double money) {
jdbcTemplate.update("update account set money = money - ? where name = ?",money,outMan);
}
public void in(String inMan, double money) {
jdbcTemplate.update("update account set money = money + ? where name = ?",money,inMan);
}
}
xml配置
<!-- 加载jdbc.properties-->
<context:property-placeholder location="jdbc.properties"/>
<!-- 数据源对象配置-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountDao" class="com.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 声明式事务配置-->
<!-- 目标对象 即找到切点-->
<bean id="accountService" class="com.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务增强 通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 设置事务属性信息-->
<tx:attributes>
<!-- 对应哪些切点(method)增强 "*":任意方法名称 -->
<tx:method name="*"/>
<!-- isolation:-->
<!-- 隔离级别:读未提交/读已提交/可重复读/串行化-->
<!-- propagation:传播行为-->
<!-- timeout:超时时间-->
<!-- read-only:是否可读-->
<tx:method name="transfer" isolation="READ_COMMITTED" propagation="REQUIRED" read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务AOP织入-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.service.impl.*.*(..))"></aop:advisor>
</aop:config>
基于注解的声明式事务控制
使用注解替代xml配置文件
- 使用@Transactional在需要进行事务控制的类或是方法上修饰,注解可用的属性同xml配置方式,例如隔离级别、传播行为等。
- 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
- 使用在方法上,不同的方法可以采用不同的事务参数配置。
- Xml配置文件中要开启事务的注解驱动<tx : annotation-driven / >
@Service("accountService")
//@Transactional(isolation = Isolation.REPEATABLE_READ)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
//注解式事务控制
//银行转账
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transfer(String ouMan,String inMan,double money){
//开启事务
//通过事务保证转账成功
accountDao.out(ouMan,money);
accountDao.in(inMan,money);
//关闭事务
}
}
<!-- 基于注解式事务控制-->
<!-- 事务的注解驱动 启动@Transactional-->
<tx:annotation-driven transaction-manager="transactionManager"/>