开篇寄语
Spring有两大核心一个是控制反转IOC,一个是面向切面AOP
控制反转IOC也就相当于是一个Spring容器,我们将我们编写代码所需要的内容都存放在Spring的IOC容器中,让Spring来帮助我们管理,我们需要的时候直接调用就好,而不需要在出现new这些关键字来创建对象,他的底层使用了Java基础里面反射的原理。
而面向切面AOP也同样是Spring的核心之一,这篇笔记就是用来介绍Spring中面向切面AOP的思想和如何在Spring中使用到这种思想!
同时本篇会讲讲spring的事务管理,因为Spring的事务管理是基于AOP来实现的,可以放在一起讲一讲
自己的知识水平不是很高,写的不好大家看了多多提出,一起交流,一起学习,共同进步!
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
这一段百度百科里面都有的,都是一些概念,面向百度编程!!!
在我们学习了解AOP的时候,我们需要了解静态代理和动态代理,静态代理倒无所谓,但是动态代理一定要了解,在博客中Java基础分类中有提到动态代理,大家不明白的可以去看一看。
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
优势:减少重复代码,提高开发效率,并且便于维护
实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
常用的动态代理技术
基于接口的动态代理
需要有一个接口和接口实现类
AOP默认的动态代理技术为JDK代理技术
代码如下
package com.eason.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//1. 创建目标对象
final Target target = new Target();
//2. 创建增强对象
final Advice advice = new Advice();
//3. 返回值就是动态生成的代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
//目标对象类加载器
target.getClass().getClassLoader(),
//目标对象相同的接口字节码对象数组
target.getClass().getInterfaces(),
//接口
new InvocationHandler() {
//调用代理对象的任何方法,实质执行的都是invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置增强
advice.before();
method.invoke(target,args); //执行目标方法,这里的invoke方法和上面的不同,这里的invoke方法指的是反射
//后置增强
advice.afterReturning();
return null;
}
}
);
//调用代理对象的方法
proxy.save();
}
}
代码实现
package com.eason.proxy.cglib;
import com.eason.proxy.jdk.TargetInterface;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(final String[] args) {
//1. 创建目标对象
final Target target = new Target();
//2. 创建增强对象
final Advice advice = new Advice();
//3. 返回值就是动态生成的代理对象,基于cglib
//3.1 创建增强器
Enhancer enhancer = new Enhancer();
//3.2 设置父类(目标)
enhancer.setSuperclass(Target.class);
//3.3 设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//执行前置
advice.before();
//执行目标
Object invoke = method.invoke(target, args);
//执行后置
advice.afterReturning();
return null;
}
});
//3.4 创建代理对象,这个是父子关系,可以使用Target创建
Target proxy = (Target) enhancer.create();
//调用代理对象的方法
proxy.save();
}
}
Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语,常用的术语如下:
Spring框架监控切入点方法的执行。一旦监控到切入点方法被执行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的相应位置,将通知对应的功能织入,完成完整的代码逻辑运行(简而言之就是spring框架会监控我们可以被增强的方法,如果监控到这个方法执行,就会创建一个动态代理,然后会根据增强的类型在代理对象执行相应的方法时执行相应的增强对象的增强)
导入Maven依赖
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.4.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.13version>
dependency>
创建目标接口和目标类(内部有切点)
相当于动态代理里面的实现类和实现方法,我们要增强的就是这两个方法
package com.eason.aop;
/**
* 目标接口,切点方法
*/
public interface TargetInterface {
void save();
}
package com.eason.aop;
/**
* 目标类
*/
public class Target implements TargetInterface {
@Override
public void save() {
System.out.println("Target里的save方法打印");
}
}
创建切面类(内部有增强方法)
这个是增强方法,相当于动态代理里面的通知
package com.eason.aop;
/**
* 切面类
*/
public class MyAspect {
public void before() {
System.out.println("前置增强!");
}
public void after() {
System.out.println("后置增强!");
}
}
在applicationContext.xml文件中配置
<bean id="target" class="com.eason.aop.Target">bean>
<bean id="myAspect" class="com.eason.aop.MyAspect">bean>
<aop:config>
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut="execution(public void com.eason.aop.Target.save())">aop:before>
<aop:after method="after" pointcut="execution(public void com.eason.aop.Target.save())">aop:after>
aop:aspect>
aop:config>
表达式语法:
这个记住就好
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
例:execution(public void com.eason.aop.Target.save())
例:
通知的配置语法
aop:通知类型>
<aop:config>
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut="execution(public void com.eason..Target.save())">aop:before>
<aop:after method="after" pointcut="execution(public void com.eason.aop.*.*(..))">aop:after>
<aop:around method="around" pointcut="execution(public void com.eason.aop.*.*(..))">aop:around>
<aop:after-throwing method="afterThrowing" pointcut="execution(public void com.eason.aop.*.*(..))">aop:after-throwing>
aop:aspect>
aop:config>
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。
<aop:config>
<aop:aspect ref="myAspect">
<aop:pointcut id="myPointcut" expression="execution(public void com.eason.aop.*.*(..))"/>
<aop:before method="before" pointcut-ref="myPointcut">aop:before>
<aop:after method="after" pointcut-ref="myPointcut">aop:after>
<aop:around method="around" pointcut-ref="myPointcut">aop:around>
<aop:after-throwing method="afterThrowing" pointcut="execution(public void com.eason.aop.*.*(..))">aop:after-throwing>
aop:aspect>
aop:config>
基于注解就比XML配置文件都要简单了,但是我们在使用注解之前我们必须要理解XML文件的配置,不然的话会很迷的
还是和XML的配置方法一样
创建目标接口和目标类(内部有切点)
package com.eason.aop_anno;
public interface TargetInterface {
void save();
}
package com.eason.aop_anno;
/**
* 目标类
*/
public class Target implements TargetInterface {
@Override
public void save() {
System.out.println("Target里的save方法打印");
}
}
创建切面类(内部有增强方法)
package com.eason.aop_anno;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 切面类
*/
//在类里面使用的注解@Component
@Component("myAspect")
@Aspect //直接就可以标注当前类MyAspect为一个切面类,不用在XML文件下配置了,很方便
public class MyAspect {
/**
* 执行前增强
*/
public void before() {
System.out.println("前置增强...........");
}
/**
* 最终增强
*/
public void after() {
System.out.println("最终增强...........");
}
/**
* 后置增强
*/
public void after_returning() {
System.out.println("后置增强...........");
}
/**
* 环绕增强
*/
//Proceeding JoinPoint:正在执行的连接点 === 切点
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强.......");
Object proceed = pjp.proceed();//切点方法
System.out.println("环绕后增强.......");
return proceed;
}
/**
* 异常抛出增强
*/
public void afterThrowing() {
System.out.println("异常抛出增强........");
}
}
在切面类中使用注解配置织入关系
使用注解配置织入关系就不用在xml文件里面写那么冗长的配置了
使用了@Before(切入点),@After(切入点),@AfterReturning(切入点),@Around(切入点),@AfterThrowing(切入点)这几个注解在方法上进行注入
其中在@Around(切入点)环绕增强里面需要添加一个切点方法的执行点
Object proceed = pjp.proceed();//切点方法
package com.eason.aop_anno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 切面类
*/
@Component("myAspect")
@Aspect //标注当前类MyAspect为一个切面类
public class MyAspect {
/**
* 执行前增强,配置前置增强
*/
@Before("execution(public void com.eason.aop_anno.*.*(..))")
public void before() {
System.out.println("前置增强...........");
}
/**
* 最终增强
*/
@After("execution(public void com.eason.aop_anno.*.*(..))")
public void after() {
System.out.println("最终增强...........");
}
/**
* 后置增强
*/
@AfterReturning("execution(public void com.eason.aop_anno.*.*(..))")
public void after_returning() {
System.out.println("后置增强...........");
}
/**
* 环绕增强
*/
//Proceeding JoinPoint:正在执行的连接点 === 切点
@Around("execution(public void com.eason.aop_anno.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强.......");
Object proceed = pjp.proceed();//切点方法
System.out.println("环绕后增强.......");
return proceed;
}
/**
* 异常抛出增强
*/
@AfterThrowing("execution(public void com.eason.aop_anno.*.*(..))")
public void afterThrowing() {
System.out.println("异常抛出增强........");
}
}
在配置文件中开启组件扫描和 AOP 的自动代理
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.eason.aop_anno">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>
在切面类中添加切点方法
@Pointcut("execution(public void com.eason.aop_anno.*.*(..))")
public void myPointCut() {}
使用注解,然后增强方法进行调用即可
在注解里面加入切点(“MyAspect.myPointCut()”)
/**
* 执行前增强,配置前置增强
*/
@Before("MyAspect.myPointCut()")
public void before() {
System.out.println("前置增强...........");
}
/**
* 最终增强
*/
@After("MyAspect.myPointCut()")
public void after() {
System.out.println("最终增强...........");
}
/**
* 后置增强
*/
@AfterReturning("MyAspect.myPointCut()")
public void after_returning() {
System.out.println("后置增强...........");
}
/**
* 环绕增强
*/
//Proceeding JoinPoint:正在执行的连接点 === 切点
@Around("MyAspect.myPointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强.......");
Object proceed = pjp.proceed();//切点方法
System.out.println("环绕后增强.......");
return proceed;
}
/**
* 异常抛出增强
*/
@AfterThrowing("MyAspect.myPointCut()")
public void afterThrowing() {
System.out.println("异常抛出增强........");
}
事务的特性
事物的ACID原则
简而言之就是上面所说的要么都成功,要么都失败
Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="accountDao" class="com.eason.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate" />
bean>
<bean id="accountService" class="com.eason.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao" />
bean>
<bean id="transactionManger" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<tx:advice id="txAdvice" transaction-manager="transactionManger">
<tx:attributes>
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
<tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
<tx:method name="*" />
tx:attributes>
tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.eason.service.impl.*.*(..))" />
aop:config>
beans>
这里面涉及到了一些JdbcTemplate操作数据库的一些方法,这里就不提JdbcTemplate了,大家想了解的话可以上网查一查,里面有些方法还是很值得我们去学习的。
tx:method 代表切点方法的事务参数的配置
切点参数详解
声明事务不建议使用注解(个人认为使用XML文件会更好一点),当然仁者见仁,智者见智
编写AccountDao层
package com.eason.dao.impl;
import com.eason.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private 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);
}
}
编写AccountService层
package com.eason.service.impl;
import com.eason.dao.AccountDao;
import com.eason.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("accountService")
@Transactional(isolation = Isolation.DEFAULT) //若在这个地方配的话那么这个类里的所有方法都遵循这个事务控制
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public void transfer(String outMan, String inMan, double money) {
//开启事务
accountDao.out(outMan,money);
// int i = 1/0;
accountDao.in(inMan,money);
//提交事务
}
@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
public void xxx() {}
}
XML配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.eason" />
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
<bean id="transactionManger" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<tx:annotation-driven transaction-manager="transactionManger"/>
beans>
注解配置要点
Spring中面向切面编程AOP很重要,我们在掌握AOP之前需要将Java基础的静态代理技术,动态代理技术,类反射机制给理解,吃透,不然对于理解AOP的原理可能会有些困难,单单记AOP里面的各种切点,切面等等参数可能会有些生涩,学习动态代理之后就会清晰很多。
同时本篇涉及到了SpringJDBCTemplate的知识,但是没有讲,大家想了解的也可以到Spring官网了解了解或者百度了解了解,里面几个方法还是值得我们去学习的。
对于Spring声明式事务的底层就是AOP思想,要记住事务的ACID原则,事务在我们以后的程序编写中对于数据的保护还是有很大的作用的。
以上就是我对于Spring核心AOP和Spring声明式事务的一些理解,自己的知识水平不是很高,博客里面有些内容可能会有些错误,一些知识点可能也理解的不是很清晰,大家查看了有什么不对的可以多多指出,一起交流,共同学习,共同进步!