Spring 官方解释:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop
也可以去看看:百度百科
可以对业务逻辑得到各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
通俗描述就是,在不修改源代码的条件下,在主干功能中添加新功能,就是 AOP 的过程。
切面(Aspect): 用于跨多个类来实现切点模块化,在 Spring AOP 中,切面是通过使用常规类或者带有 @Aspect
注解的常规类来实现的。【把增强处理应用到切入点的过程】
连接点(Join point): 程序执行过程中明确的点,如方法的调用,或者异常的抛出。在Spring AOP中,连接点总是指方法的调用。【类里面哪些方法可以被增强,这些方法称为连接点。】
通知 / 增强(Advice): 切面在特定的连接点处采取的操作,增强/通知的类型有有 around
、before
和 after
等类型。【实际增强的逻辑部分称为通知(增强处理),通知有多种类型:前置通知、后置通知、环绕通知、异常通知、最终通知】
切入点(Pointcut): 切入点是与连接点匹配的描述。通知 (advice) 与 切入点表达式关联,并在与该切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。切入点表达式匹配连接点的概念是 AOP 的核心,Spring 默认使用 AspectJ 切入点表达式。【切入点 就是可以插入增强处理的连接点。简而言之,当某个连接点被添加增强处理后就变成了切入点。】
引入(Introduction): 引用允许你添加新方法或属性到现有的类中。
目标对象(Target object):被一个或者多个方面所通知的对象,这个对象永远是一个被代理对象。也称为被通知对象。
AOP 代理(AOP proxy) : 为了实现切面约定(通知方法的执行等),使用AOP框架创建对象。在Spring Framework中,AOP 使用 JDK 动态代理 或 CGLIB 代理。
编织(Weaving):Weaving 把方面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时,类加载时和运行时完成。
AOP 底层使用到了动态代理模式:
菜鸟教程-代理模式
简单理解代理模式:用户购买电脑不是到生产厂家处购买,而是到经销商处购买,经销商又从生产厂家取货。倘若用户电脑出问题了,找的是经销商的售后,经销商售后要么自己处理,要么返还给生产厂家处理。这个过程就叫做代理。
这是基于接口的动态代理方式,使用 proxy 类中的 static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
方法创建代理对象。
可参考:Proxy API介绍
参数解读:
loader
:类加载器,写的是被代理对象的类加载器,是固定写法interfaces
:用于让代理对象和被代理对象拥有相同的方法,是固定写法。InvocationHandler
: 用于提供增强的代码,让我们写如何代理,一般都是写一个该接口的实现类,通常情况下使用匿名内部类。代码演示:
1 创建接口,定义相关方法
public interface UserDao {
public int add(int a,int b);
public String update(String name);
public void run(String name);
}
2 创建接口实现类并实现方法
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
System.out.println("add方法执行了。。。");
return a + b;
}
@Override
public String update(String name) {
System.out.println("update方法执行了。。。");
return name;
}
@Override
public void run(String name) {
System.out.println("run 方法执行了。。。=>>" + name + "跑起来了。");
}
}
3 使用 Proxy 类创建接口代理对象。
public static void main(String[] args) {
// 创建被代理的对象
final UserDaoImpl udi = new UserDaoImpl();
//匿名内部类的方式,在内部实现 InvocationHandler 接口
UserDao ud = (UserDao)Proxy.newProxyInstance(
JDKProxy.class.getClassLoader(),
UserDaoImpl.class.getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法,具有拦截过滤功能
* @param proxy 在其上调用方法的代理实例
* @param method 当前执行的 方法
* @param args 当前执行 方法 所需的参数,可以通过索引来操作
* @return 和被代理对象 方法 有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强代码,拦截对应的方法作增强
if ("add".equals(method.getName())){
Object obj = (Integer)method.invoke(udi,args)*10;
return obj;
}
if ("update".equals(method.getName())){
Object obj = method.invoke(udi,"欢迎 " + args[0]);
return obj;
}
//不影响未增强的方法
return method.invoke(udi,args);
});
System.out.println(ud.add(1,2)); //增强了方法,将两数相加的结果乘以10倍
System.out.println(ud.update("Roan")); //在 name 前面加了欢迎。
ud.run("Jacks"); //未增强的方法
}
这是基于子类的动态代理方式,是由第三方 cglib
库提供,使用 Enhancer
类中的 public static Object create(Class type, Callback callback)
方法创建代理对象。
可参考以下博客:CGLIB原理及实现机制
参数解读:
type
:用于指定被代理对象的字节码。callback
:用于提供增强的代码,与 JDK 动态代理中的 InvocationHandler
大同小异,这里实现的是 MethodInterceptor
接口。代码演示:
1 创建类
public class Producer {
public void saleProduct(float money) {
System.out.println("销售产品,拿到钱~" + money);
}
public void afterService(float money) {
System.out.println("提供售后服务,并拿到钱~" + money);
}
}
2 创建代理对象
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param o 被代理实例
* @param method 方法
* @param objects 参数
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的。
* @param methodProxy 当前执行方法的代理对象
* @return 与被代理对象 方法 的返回值一致
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object rs = null;
Float money = (Float) objects[0];
if ("saleProduct".equals(method.getName())) {
rs = method.invoke(producer, money * 0.8f);
}
return rs;
}
});
cglibProducer.saleProduct(1000);//使用代理对象调用方法
}
}
作用:知道对哪个类的哪个方法进行增强
语法结构:execution([权限修饰符][返回值类型][类的全限定类名][方法名称](参数列表))
实例:
execution(* com.jk.dao.UserDao.add(..)); //对 com.jk.dao.UserDao 中的 add 方法进行增强
execution(* com.jk.dao.UserDao.*(..)); //对 com.jk.dao.UserDao 中的所有方法进行增强
execution(* com.jk.dao.*UserDao*.*(..)); //对 com.jk.dao 中的所有类的所有方法进行增强
*
代表任意。(任意权限修饰,任意方法,任意类,任意方法等)..
代表方法执行所需的参数。1 创建基本类,使用注解创建 User 和 UserProxy 对象。
@Component //创建对象
public class User {
public void add(){
System.out.println("add 方法执行了");
}
}
2 编写增强类,在增强类上面添加注解 @Aspect
,配置不同类型的通知。
@Component
@Aspect //生成代理对象
public class UserProxy {
//相同切入点抽取
@Pointcut(value = "execution(* com.jk.aop.annotation.User.add(..))")
public void pointDemo(){}
//前置通知
// @Before(value = "execution(* com.jk.aop.annotation.User.add(..))")
@Before("pointDemo()") //抽取公共切入点
public void before(){
System.out.println("before...");
}
//最终通知:方法执行之后,无论有没有异常都会通知
@After(value = "execution(* com.jk.aop.annotation.User.add(..))")
public void after() {
System.out.println("after...");
}
//后置通知(返回通知):方法返回值之后执行,有异常则不会通知
@AfterReturning(value = "execution(* com.jk.aop.annotation.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning...");
}
//异常通知:当方法有异常时执行
@AfterThrowing(value = "execution(* com.jk.aop.annotation.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing...");
}
//环绕通知:在方法执行前后都会通知,如果有异常,则环绕之后不会通知,环绕之前依旧通知。
@Around(value = "execution(* com.jk.aop.annotation.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕之前。。。");
//被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后...");
}
}
3 进行通知/增强的配置
context
和 aop
名称空间。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/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">
<context:component-scan base-package="com.jk.aop.annotation"/>
<aop:aspectj-autoproxy/>
beans>
如果有多个增强类对同一个方法进行增强,可以在增强类上面添加注解 @Order(value)
,value
是一个整数,值越小,优先级越高。
@Component
@Aspect //生成代理对象
@Order(3)
public class UserProxy {...}
@Component
@Aspect
@Order(1)
public class personProxy {...}
1 创建两个类,增强类和被增强类
@Component
public class Student {
public void run(){
System.out.println("run 方法执行了");
}
}
@Component
@Aspect //生成代理对象
public class StudentProxy {
@Before(value = "execution(* com.jk.aop.aopxml.Student.run(..))")
public void before() {
System.out.println("before...");
}
}
2 在 Spring 配置文件中创建两个类的对象,配置切入点。
<bean id="student" class="com.jk.aop.aopxml.Student"/>
<bean id="userProxy" class="com.jk.aop.aopxml.StudentProxy"/>
<aop:config>
<aop:pointcut id="p" expression="execution(* com.jk.aop.aopxml.Student.run(..))"/>
<aop:aspect ref="userProxy">
<aop:before method="before" pointcut-ref="p"/>
aop:aspect>
aop:config>
1 创建被增强类和增强类
@Component
public class Student {
public void run(){
System.out.println("run 方法执行了");
}
}
@Component
@Aspect //生成代理对象
public class StudentProxy {
@Before(value = "execution(* com.jk.aop.aopxml.Student.run(..))")
public void before() {
System.out.println("before...");
}
}
2 创建配置类,不需要创建 xml 配置
@Configuration
@ComponentScan(value = {"com.jk.aop.aopxml"}) // 等价于
@EnableAspectJAutoProxy(proxyTargetClass = true) // 等价于
public class AopConfig {
}
3 测试方法
@Test
public void testNoXml(){
ApplicationContext context =
new AnnotationConfigApplicationContext(AopConfig.class);
Student student = context.getBean("student", Student.class);
student.run();
}
AOP 基础部分到此结束,但还是感觉有点云,不结合实际项目的确很难理解~