一、AOP简介
AOP:面向切面编程,基于动态代理实现,
切点:需要添加额外代码(事务)的方法叫做切点
前置通知:在切点方法之前执行的代码叫做前置通知(例如:开始事务)
后置通知:在切点方法执行完毕之后执行的代码叫做后置通知(例如:提交事务)
切面:切点+通知称之为切面
通知分类:
前置通知:方法执行之前执行
后置通知:方法体执行完毕但是还没rerun之前执行
返回后通知:在rerun执行完毕之后执行(提交)
异常通知:方法出现异常之后执行(回滚)
环绕通知:在方法执行前后执行,通常用来做日志处理
二、AOP的使用
spring在实现AOP的时候,优先以JDK代理生成代理类及代理类对象,只有被代理类没有实现任何接口是才会CGLIB代理
2.1 schema-based方案(使用较少)
这种方案每种通知就是一类。不利于维护
前置通知类:
//前置通知类
public class BankServiceBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object object) throws Throwable {
System.out.println("====开启事务====");
}
}
返回后通知类:
//返回后通知
public class BankServiceReturnAfterAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object object, Method method, Object[] args, Object object2) throws Throwable {
System.out.println("====提交事务====");
}
}
在spring的配置文件中配置通知类bean、切面及被代理对象
三步:
- 配置通知对象
- 配置切面:指定哪些方法与通知进行关联
配置被代理类对象(得到的是代理类对象,通过JDK代理生成的)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- AOP相关配置 --> <!-- 1.配置通知对象 --> <bean id="beforeAdvice" class="com.woniuxy.advice.BankServiceBeforeAdvice"></bean> <bean id="afterReturnAdvice" class="com.woniuxy.advice.BankServiceReturnAfterAdvice"></bean> <!-- 2.配置切面:指定哪些方法与通知进行关联 --> <aop:config> <!-- 2.1 配置切点:找到“哪些”方法需要添加通知 --> <!-- 其中execution(* )第一个*代表四种访问修饰符都包含(private等) *.*(..)表示在*类下的所有方法,..表示有参数,无参数的方法都包含 --> <aop:pointcut expression="execution(* com.woniuxy.service.Impl.*.*(..))" id="pc"/> <!-- 2.2 配置通知 --> <aop:advisor advice-ref="beforeAdvice" pointcut-ref="pc"/> <aop:advisor advice-ref="afterReturnAdvice" pointcut-ref="pc"/> </aop:config> <!-- 3.配置被代理类对象(得到的是代理类对象,通过JDK代理生成的) --> <bean id="bankService" class="com.woniuxy.service.Impl.BankServiceImpl"></bean> </beans>
测试类:
public class SchemaTest { @Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); // 从容器中获取对象(代理类对象) Object object = context.getBean("bankService"); if(object instanceof BankService) { BankService bankService = (BankService)object; bankService.transfer(); bankService.save(); } } }
2.2 aspectj方案(使用较多)
将一个类中的不同方法作为不同的通知,多个通知可以写在一个类里面
通知类:
// 通知类:包含多种通知
public class Advice {
// 前置通知
public void before() {
System.out.println("前置通知:开启事务");
}
// 后置通知
public void after() {
System.out.println("后置通知:不管方法是否出现异常,都会执行");
}
// 返回后通知
public void afterReturning() {
System.out.println("返回后通知:提交事务");
}
// 异常通知
public void except() {
System.out.println("异常通知:回滚");
}
// 环绕通知:日志处理
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕通知:在方法执行之前执行....");
try {
jp.proceed();// 调用被代理类的方法
} catch (Throwable e) {
// 用于拼接的字符串
String info = "";
// 日志处理:
// 时间+类+方法+参数+发生了XXX事
// 签名对象
MethodSignature methodsignature = (MethodSignature) jp.getSignature();
// 发生异常的方法
Method method = methodsignature.getMethod();
// 获取发生异常的时间+类+方法名
info = new Date().toString() + jp.getTarget().getClass() + "." + method.getName();
// 参数
Object[] args = jp.getArgs();
// 遍历拼接
for (Object object : args) {
info+=".";
info+=object;
}
System.out.println(info + "-" + e.getMessage());
// 最后将异常信息写入日志文件、日志表格
// 继续抛异常
throw e;
}
System.out.println("环绕通知:在方法执行之后执行....");
}
}
在spring的配置文件中配置通知类bean、切面及被代理对象
三步:
- 配置通知类bean
- 配置切面
配置被代理类对象(得到的是代理类对象,通过JDK代理生成的)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 1.通知类bean --> <bean id="advice" class="com.woniuxy.aspectj.Advice"></bean> <!-- 2.配置切面 --> <aop:config> <!-- 2.1 配置切点 --> <aop:pointcut expression="execution(* com.woniuxy.service.Impl.*.*(..))" id="pc"/> <!-- 2.2 配置通知 --> <aop:aspect ref="advice"> <aop:before method="before" pointcut-ref="pc"/> <aop:after-returning method="afterReturning" pointcut-ref="pc"/> <aop:after-throwing method="except" pointcut-ref="pc"/> <aop:after method="after" pointcut-ref="pc"/> <aop:around method="around" pointcut-ref="pc"/> </aop:aspect> </aop:config> <!-- 3.配置被代理类对象(得到的是代理类对象,通过JDK代理生成的) --> <bean id="bankService" class="com.woniuxy.service.Impl.BankServiceImpl"></bean> </beans>
测试类:
public class AspectjTest { @Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-context-aspectj.xml"); // 从容器中获取对象(代理类对象) Object object = context.getBean("bankService"); if(object instanceof BankService) { BankService bankService = (BankService)object; // bankService.transfer(); // bankService.save(); bankService.get(0); } } }
三、注解实现AOP(aspectj方案)
3.1 通知类
// 通知类:包含多种通知
@Component //加入到IOC容器中
@Aspect //告知IOC容器这是一个切面类
public class Advice {
//配置切点
@Pointcut("execution(* com.woniuxy.service.Impl.*.*(..))")
public void _pc() {}
// 前置通知
@Before("_pc()")
public void before() {
System.out.println("前置通知:开启事务");
}
// 后置通知
@After("_pc()")
public void after() {
System.out.println("后置通知:不管方法是否出现异常,都会执行");
}
// 返回后通知
@AfterReturning("_pc()")
public void afterReturning() {
System.out.println("返回后通知:提交事务");
}
// 异常通知
@AfterThrowing("_pc()")
public void except() {
System.out.println("异常通知:回滚");
}
// 环绕通知:日志处理
@Around("_pc()")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕通知:在方法执行之前执行....");
try {
jp.proceed();// 调用被代理类的方法
} catch (Throwable e) {
// 用于拼接的字符串
String info = "";
// 日志处理:
// 时间+类+方法+参数+发生了XXX事
// 签名对象
MethodSignature methodsignature = (MethodSignature) jp.getSignature();
// 发生异常的方法
Method method = methodsignature.getMethod();
// 获取发生异常的时间+类+方法名
info = new Date().toString() + jp.getTarget().getClass() + "." + method.getName();
// 参数
Object[] args = jp.getArgs();
// 遍历拼接
for (Object object : args) {
info+=".";
info+=object;
}
System.out.println(info + "-" + e.getMessage());
// 最后将异常信息写入日志文件、日志表格
// 继续抛异常
throw e;
}
System.out.println("环绕通知:在方法执行之后执行....");
}
}
3.2 Service实现类
@Service
public class BankServiceImpl implements BankService{
@Override
public void transfer() {
System.out.println("调用mapper执行转账sql");
}
@Override
public void save() {
System.out.println("调用mapper执行存钱sql");
}
@Override
public void get(int money) {
System.out.println("调用mapper执行取钱sql");
// 手动制造异常,触发异常通知,环绕通知记录异常
// System.out.println(1/money);
}
}
3.3 Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1.开启spring的注解扫描
context:component-scan扫描:component、controller、service、repository
-->
<context:component-scan base-package="com.woniuxy"></context:component-scan>
<!-- 2.开启AOP扫描
aop:aspectj-autoproxy扫描:aspect、before、after等等
-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
四、切入点表达式
//拦截com.woniuxy.service包下所有类的所有方法
execution(* com.woniuxy.service.*.*(..))
//拦截所有public方法
execution(public * *(..))
//save开头的方法
execution(* save*(..))
//拦截指定类的指定方法, 拦截时候一定要定位到方法
execution(public com.woniuxy.g_pointcut.OrderDao.save(..))
//拦截指定类的所有方法
execution(* com.woniuxy.g_pointcut.UserDao.*(..))
//拦截指定包,以及其子包下所有类的所有方法
execution(* com..*.*(..))
//多个表达式
// ||和or表示两种满足其一即可,取两个表达式的并集
execution(* com.woniuxy.g_pointcut.UserDao.save()) || execution(* com.woniuxy.g_pointcut.OrderDao.save())
execution(* com.woniuxy.g_pointcut.UserDao.save()) or execution(* com.woniuxy.g_pointcut.OrderDao.save())
// &&和and 表示两种都同时满足才行,取交集
//下面2个且关系的,没有意义
execution(* com.woniuxy.g_pointcut.UserDao.save()) && execution(* com.woniuxy.g_pointcut.OrderDao.save())
execution(* com.woniuxy.g_pointcut.UserDao.save()) and execution(* com.woniuxy.g_pointcut.OrderDao.save())
//取非值 !和not表示不在该范围内的作为切点
!execution(* com.woniuxy.g_pointcut.OrderDao.save())
not execution(* com.woniuxy.g_pointcut.OrderDao.save())