一、AOP核心思想:将业务代码和具体功能做分离。
实现方式:代理模式实现
二、代理模式
2.1.静态代理:
静态代理是设计模式中的一种代理模式,通过在代理类中封装被代理对象,间接控制对被代理对象的访问。以下是静态代理的简单示例:
2.1.1. 定义一个接口(被代理对象):
public interface Subject {
void doSomething();
}
2.1.2. 创建一个实现了接口的类(被代理对象):
public class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("RealSubject is doing something...");
}
}
2.1.3. 创建代理类,实现相同的接口:
public class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void doSomething() {
System.out.println("ProxySubject doSomething is intercepted...");
realSubject.doSomething();
System.out.println("ProxySubject doSomething is finished...");
}
}
2.1.4. 使用代理类:
public class Main {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject(); // 创建被代理对象
ProxySubject proxySubject = new ProxySubject(realSubject); // 创建代理对象
proxySubject.doSomething(); // 调用代理对象的方法,实际上会执行代理逻辑
}
}
在上述示例中,RealSubject 是被代理对象,它实现了 Subject 接口。ProxySubject 是代理类,它也实现了 Subject 接口,并在代理的 doSomething() 方法中添加了额外的逻辑。当我们调用代理对象的 doSomething() 方法时,实际上会先执行代理逻辑,再调用被代理对象的 doSomething() 方法,最后执行代理逻辑的后续操作。
静态代理的特点是代理类必须在编译时就已经存在,并且每一个被代理类都需要一个对应的代理类。虽然静态代理相对简单,但代理类的数量会随着被代理类的增加而增加,增加了代码的维护成本。另外,静态代理只能代理特定接口或类,无法应对接口或类的变化。为了更灵活地处理代理对象,可以使用动态代理。
2.2.动态代理:
动态代理是在运行时生成代理对象的一种代理方式,无需预先定义代理类。Java提供了java.lang.reflect包中的Proxy和InvocationHandler接口来实现动态代理。以下是使用动态代理的简单示例:
2.2.1. 定义一个接口(被代理对象):
public interface Subject {
void doSomething();
}
2.2.2. 创建一个实现了接口的类(被代理对象):
public class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("RealSubject is doing something...");
}
}
2.2.3. 创建一个实现了
InvocationHandler接口的动态代理类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxy implements InvocationHandler {
private Object realSubject;
public DynamicProxy(Object realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("DynamicProxy doSomething is intercepted...");
Object result = method.invoke(realSubject, args);
System.out.println("DynamicProxy doSomething is finished...");
return result;
}
}
2.2.4. 使用动态代理:
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject(); // 创建被代理对象
DynamicProxy dynamicProxy = new DynamicProxy(realSubject); // 创建动态代理对象
Subject proxyInstance = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
dynamicProxy //实现invocationHandle接口的类对象
);
proxyInstance.doSomething(); // 调用动态代理对象的方法,实际上会执行代理逻辑
}
}
在上述示例中,DynamicProxy 实现了 InvocationHandler 接口,并在 invoke() 方法中添加了额外的逻辑。当我们使用 Proxy.newProxyInstance() 方法创建代理对象时,需要传入被代理对象的类加载器、接口数组和代理对象(DynamicProxy)。
动态代理的特点是可以代理任意接口或类,并且在运行时生成代理对象,无需预先定义代理类。这样,我们可以根据实际需要来动态生成代理对象,更加灵活。动态代理常用于框架和库中,用于实现横切关注点(如事务管理、日志记录等)的插入。
三、AOP(底层使用了动态代理)
3.1.概述:
概念:面向切面编程
实现方式:通过预编译方式和运行期动态代理的方式实现
作用:在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。
3.2.专业术语:
>1.横切关注点:方法中抽取出来的非核心业务
>2.通知(增强):通俗的说,就是想要增强的功能,例如:安全,事务,日志
每一个横切关注点上要做的事情需要写一个方法来实现,这样的方法就叫通知方法。
- 前置通知:被代理目标方法执行前。
- 返回通知:被代理的目标方法成功结束后。
- 异常通知:被代理的目标方法异常结束后执行。
- 后置通知:被代理的目标方法最终结束后。
- 环绕通知:使用tyr...catch...finally结构围绕被代理的目标方法,包括上面四种通知的所有位置。
>3.切面:封装通知方法的类
>4.连接点:允许使用通知的地方
四、动态代理分类
-
JDK动态代理(代理对象和目标对象实现同一个接口)
-
CGlib动态代理(没借口,代理对象继承被代理对象)
区别:有接口的情况用的是JDK的动态代理;没有接口则用cglib动态代理
*注意:是不是实现类接口就一定是JDK动态代理吗?
不是的,涉及到实现了Bean生命周期的回调接口是不会使用JDK动态代理的,其次,接口中的方法数一定要大于0。
源码如下:
实现初始化、销毁、关闭接口都不会执行JDK动态代理。
总结:接口必须是有效的
五、AOP代码实现(零配置文件)
5.1. 引入依赖
<!--IOC基础包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
<!--第三方面向切面编程的组件包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
5.2. aop各种通知类型的顺序:
前置通知->目标方法->后置通知->(有异常则是异常通知)->返回通知
环绕通知包含了所有通知的位置
5.3. 代码实现
项目模块如下:
5.3.1. aopservie包下接口及实现类
public interface UserService {
void add();
String update(Long id, String name);
void delete(Long id);}
@Service
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("添加方法"); }
@Override
public String update(Long id, String name) {
System.out.println("修改方法");
return "success";
}
@Override
public void delete(Long id) {
System.out.println("删除方法"); }
}
5.3.2. config配置
/**
* 如果项目中使用的是Spring Boot,那么AOP默认是启用的,不需要使用@EnableAspectJAutoProxy注解 */
@Configuration
@ComponentScan(basePackages = "com.aopdemo")
@EnableAspectJAutoProxy
public class AopConfig {
}
5.3.3. 切面类
package com.aopdemo.config;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/** * 切面类 */
@Component
@Aspectpublic class AopFactory {
/**
* 前置通知
*
* @Before(value = "切入点表达式")
* 切入点表达式:execution(访问修饰符 返回值 包名.类/接口名.方法名(参数) throw 异常名)
* 访问修饰符和返回值:可以用一个*表示全部
* 参数:可以用..表示任意
*/
@Before("execution(* com.aopdemo.aopservice.UserServiceImpl.update(..))")
public void before() {
System.out.println("aop before");
}
/**
* 后置通知
*/
@After("execution(* com.aopdemo.aopservice.UserServiceImpl.update(..))")
public void after(JoinPoint joinPoint) {
// 获取方法名
String name = joinPoint.getSignature().getName();
System.out.println("aop after,方法名为:" + name); }
/**
* 返回通知
* @param joinPoint
* @param res 目标方法(被增强的方法)的返回值
*/
@AfterReturning(value = "execution(* com.aopdemo.aopservice.UserServiceImpl.update(..))", returning = "res")
public void afterReturning(JoinPoint joinPoint, Object res) {
// 获取方法名
Object[] args = joinPoint.getArgs();
String string = res.toString();
System.out.println("afterReturning,参数为:" + Arrays.toString(args));
System.out.println("afterReturning,返回值为:" + string);
}
/**
* 异常通知
* @param joinPoint
* @param e
*/
@AfterThrowing(value = "execution(* com.aopdemo.aopservice.UserServiceImpl.update(..))", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
// 获取方法名
String name = joinPoint.getSignature().getName();
System.out.println("aop afterThrowing,方法名为:" + name);
System.out.println("aop afterThrowing,异常为:" + e);
}
/**
* Around通知
*/
@Around(value = "execution(* com.aopdemo.aopservice.UserServiceImpl.delete(..))")
public Object afterThrowing(ProceedingJoinPoint proceedingJoinPoint) {
// 获取方法名
System.out.println("目标方法前执行");
Object result = new Object();
try {
result = proceedingJoinPoint.proceed();
System.out.println("目标方法之后执行");
} catch (Throwable e) {
System.out.println("目标方法抛异常后执行");
} finally {
System.out.println("目标方法执行完毕后执行");
}
return result;
}
/**
* 重用切入点表达式:可省略写切入点表达式
*/
@Pointcut("execution(* com.aopdemo.aopservice.UserServiceImpl.add(..))")
public void point() {}
@Before(value = "point()")
public void testAdd() {
System.out.println("aop before");
}
}
5.3.4 测试类
public class test {
@Test
public void test() {
ApplicationContext applicationContext
= new AnnotationConfigApplicationContext(AopConfig.class);
UserService bean = applicationContext.getBean(UserService.class);
// bean.add();
// System.out.println("====================================");
// bean.update(123L, "samY");
bean.delete(2L);
}
}
六、切面类的优先级
相同方法上存在多个切面,切面优先级控制切面的内外嵌套顺序
- 优先级高的切面:外面
- 优先级低的切面:里面
使用@Order注解可以控制切面的优先级
- @Order(较小的数):优先级高
- @Order(较大的数):优先级低