1 什么是AOP?
- Aspect Oriented Programming的缩写,面向切面编程,切面指定就是动态代理的方法,作用是在不改变业务层方法源代码的基础上对方法进行增强,底层使用的是动态代理技术,面向切面编程也可以理解成面向动态代理编程。
2 AOP相关概念
- Target(目标对象):被代理的对象就是目标对象
- Proxy(代理对象):被增强后的对象就是代理对象
- Joinpoint(连接点):就是目标对象中所有被拦截到的方法
- Pointcut(切入点):就是目标对象中被增强的方法
- Advice(通知):执行目标方法之前或者之后调用的方法就是通知
- Aspect(切面):通知方法和切入点方法结合所在的位置叫做切面
- Weaving(织入):通知方法和切入点方法结合的过程,织入之后的结果就是切面
总结一下:
连接点是所有被拦截到的方法,切入点是所有被增强的方法,连接点不一定是切入点,但是切入点一定是连接点。在执行目标对象方法之前或者之后要做的事叫做通知,通知中有增强的业务。将切入点和通知组织到一起叫织入,织入形成的结果就是切面。
3 AOP配置实现步骤
<1>【第一步】导入相关依赖:spring-context、aspectjweaver
<!--spring核心依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--切入点表达式依赖,作用:通过表达式找到哪些方法需要增强,也就是找到切入点-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<2>【第二步】定义通知类和目标对象
- AOP目标接口:
public interface StudentService {
//查询全部
public abstract List<Student> findAll() throws IOException;
public void transfer(Integer outId, Integer inId, double money);
}
- AOP目标实现类:
@Service("studentService")
public class StudentServiceImpl implements StudentService {
@Override
public List<Student> findAll() throws IOException {
System.out.println("查询所有学生信息findAll...");
return null;
}
@Override
public void transfer(Integer outId,Integer inId,double money){
//1 张三的账户-1000元
System.out.println("调用dao:张三("+outId+")的账户"+(-money)+"元");
//2 李四的账户+1000元
System.out.println("调用dao:李四("+inId+")的账户"+money+"元");
}
//接口中没有该方法,不会被拦截
public void show(){
System.out.println("----------------");
}
}
注:代理的为接口对象,接口中没有的方法,实现类自己的方法不会被增强
- 通知类:
import org.aspectj.lang.ProceedingJoinPoint;
//通知类,告诉spring在增强的前后需要做什么事
public class Advice {
public void before(){
//前置通知:开启事务
System.out.println("前置通知:开启事务");
}
public void afterReturn(){
//后置通知:提交事务
System.out.println("后置通知:提交事务");
}
public void afterThrowable(){
//异常通知:回滚事务
System.out.println("异常通知:回滚事务");
}
public void after(){
//最终通知:释放资源
System.out.println("最终通知:释放资源");
}
// 环绕通知:是Spring给我们提供的一种手动调用目标对象方法或者其他通知方法的方式
// spring在调用环绕通知方法时会传递一个封装了目标方法的对象,叫做ProceedingJoinPoint
public Object around(ProceedingJoinPoint pjp){
Object result =null;
try {
//前置通知
before();
//执行目标方法,相当于动态代理中的 result=method.invoke(...)
result = pjp.proceed();
//后置通知
afterReturn();
} catch (Throwable throwable) {
//异常通知
afterThrowable();
throwable.printStackTrace();
} finally {
//最终通知
after();
}
return result;
}
}
<3>xml文件配置AOP
1 配置目标对象,添加到spring容器中
2 配置通知对象,添加到spring容器中
3 配置切入点方法和通知方法织入过程,也就配置切面
- 纯XML配置:
<?xml version="1.0" encoding="UTF-8"?>
<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"
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">
<!--1.配置service-->
<bean id="studentService" class="com.itheima.service.impl.StudentServiceImpl"/>
<!--2.配置通知对象-->
<bean id="myAdvice" class="com.itheima.aop.Advice"/>
<!--3.配置AOP-->
<aop:config>
<!--3.1配置AOP切入点表达式[可放在任意位置]-->
<!--*空格代表void 方法名
.*:.StudentServiceImpl
.*:.方法名
(..)方法参数, ..代表参数任意
-->
<aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!--<aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.StudentServiceImpl.*(..))"/>-->
<!--3.2配置切面-->
<aop:aspect ref="myAdvice">
<!--前置通知-->
<aop:before method="before" pointcut-ref="pt"/>
<!--后置通知-->
<aop:after-returning method="afterReturn" pointcut-ref="pt"/>
<!--异常通知-->
<aop:after-throwing method="afterThrowable" pointcut-ref="pt"/>
<!--最终通知-->
<aop:after method="after" pointcut-ref="pt"/>
<!--使用环绕通知-->
<!--<aop:around method="around" pointcut-ref="pt"/>-->
</aop:aspect>
</aop:config>
</beans>
- 注解配置:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//通知类,告诉spring在增强的前后需要做什么事
@Component("advice")
@Aspect//告知是一个切面类,扫描时会扫描它的注解//代替:<aop:aspect ref="advice">
public class Advice {
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
//id为方法名[首字母小写]
public void pt() {
}
//==注意:使用注解配置AOP,后置通知和异常通知会在最终通知之后调用,
// 在spring-context的5.1.9版本中是这样的,在更高的版本中可能得到了解决,
// (5.2.6及以上版本解决了)。
// 但是我们可以使用环绕通知解决这个问题,推荐使用环绕通知。==**
/* @Before("pt()")
public void before(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
//前置通知:开启事务
System.out.println("前置通知:开启事务"+args[0]);
}*/
//两种得到传递参数的方法 如果目标方法没有传参,则不执行
@Before("execution(* com.itheima.service.impl.*.*(..))&&args(x)")
public void before(int x) {
//前置通知:开启事务
System.out.println("前置通知:开启事务" + x);
}
@AfterReturning("pt()")
public void afterReturn() {
//后置通知:提交事务
System.out.println("后置通知:提交事务");
}
@AfterThrowing("pt()")
public void afterThrowable() {
//异常通知:回滚事务
System.out.println("异常通知:回滚事务");
}
@After("pt()")
public void after() {
//最终通知:释放资源
System.out.println("最终通知:释放资源");
}
// 环绕通知:是Spring给我们提供的一种手动调用目标对象方法或者其他通知方法的方式
// spring在调用环绕通知方法时会传递一个封装了目标方法的对象,叫做ProceedingJoinPoint
//@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object result = null;
try {
//前置通知
//Object[] args = pjp.getArgs();
//before();
//执行目标方法,相当于动态代理中的 result=method.invoke(...)
result = pjp.proceed();
//后置通知
afterReturn();
} catch (Throwable throwable) {
//异常通知
afterThrowable();
throwable.printStackTrace();
} finally {
//最终通知
after();
}
return result;
}
}
注:
使用注解配置AOP,后置通知和异常通知会在最终通知之后调用,
在spring-context的5.1.9版本中是这样的,在更高的版本中可能得到了解决,
(5.2.6及以上版本解决了)。
但是我们可以使用环绕通知解决这个问题,推荐使用环绕通知
- 核心配置类代替XML
import org.springframework.context.annotation.*;
@Configuration//表示代表替换applicationContext.xml的标识[可以不写]
@ComponentScan("com.itheima")//开启Spring注解扫描
@EnableAspectJAutoProxy//开启Spring的AOP注解支持
public class SpringConfig {
}
4.底层动态代理类似原理[studentService动态代理工厂]
package com.itheima.proxy;
import com.itheima.aop.Advice;
import com.itheima.service.StudentService;
import com.itheima.service.impl.StudentServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class StudentServiceProxyFactory {
public static StudentService createStudentServiceProxy() {
Advice advice = new Advice();
//1.创建真实对象
StudentService studentService = new StudentServiceImpl();//可采用set注入
//2.创建代理对象
/**
ClassLoader loader,创建代理对象的class对象
Class<?>[] interfaces,告诉代理对象要和目标对象实现相同的接口,就具有相同的功能。
InvocationHandler h,处理增强的逻辑
*/
ClassLoader classLoader = studentService.getClass().getClassLoader();
Class<?>[] interfaces = studentService.getClass().getInterfaces();//获取所有的直接实现的接口
StudentService service = (StudentService) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
/**
* @param proxy 代理对象
* @param method 调用代理对象的方法,findAll、findById、transfer、update。。。
* @param args 调用代理对象方法传递进来的参数们
* @return 此处的返回值将返回给调用处
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
if (method.getName().equals("transfer") || method.getName().equals("delete")) {
try {
//1.开启事务
advice.before();
//2.执行操作,调用目标方法
result = method.invoke(studentService, args);
//3.提交事务
advice.afterReturn();
} catch (Exception e) {
e.printStackTrace();
//4.如果有异常则回滚事务
advice.afterThrowable();
} finally {
//5.释放资源
advice.after();
}
} else {
//执行操作,调用目标方法
result = method.invoke(studentService, args);
}
return result;
}
});
return service;
}
}
注:只可对单一实现类对象进行增强