java-AOP面向切面编程
什么是AOP
AOP是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善。
面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术,利用AOP可以对业务逻辑的各个部分进行隔离,降低业务逻辑的偶尔度,提高程序可重用性和开发的效率.
场景说明
当你存在一个类,用于更新数据库数据:
package org.example;
public interface UserService {
void add();
void delete();
void update();
void query();
}
复制
具体实现类:
package org.example;
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("add");//模拟新增
}
@Override
public void delete() {
System.out.println("delete");//模拟删除
}
@Override
public void update() {
System.out.println("update");//模拟更新
}
@Override
public void query() {
System.out.println("query");//模拟查询
}
}
复制
如果我们需要在每个数据库操作的时候,插入一条日志,或者判断是否有权限删除,该怎么写呢?
public void add() {
//判断权限
System.out.println("check add perm");
System.out.println("add");//模拟新增
//记录日志
System.out.println("log:add");
}
复制
这个时候就出现了一个问题:每个类方法都需要额外添加2行代码去进行其他操作,这样对于该类来说是一种耦合,增加了代码的复杂度,那么有没有方法能够更加方便的去实现其他逻辑呢?这个时候就需要用到AOP了 AOP提供了对代码无侵入式的写法,对类方法的执行增加前后置的操作,不需要变动原有的类,就可以实现其他额外的逻辑
AOP术语
- join point: 拦截点,比如某个业务方法
- point cut: join point 的表达式,表示要拦截的方法集合
- advice: 要切入的逻辑
- before advice:在方法前切入的逻辑
- after advice:在方法执行后切入,抛出异常也切入
- after returning advice:在方法执行后切入,抛出异常不切入
- after throwing advice:在方法抛出异常后切入
- around advice 在方法执行前后切入,可以中断或忽略原有流程的执行
织入器
通过在切面
定义point cut
来搜索被代理类
的join point
,然后把需要切入的advice逻辑
织入到目标对象,生成代理类
AOP实现原理
AOP可以通过几个层面来实现:
- 编译期
- 字节码加载前
- 字节码加载后
根据这几个层面,有着以下几种实现机制:
静态AOP
在编译期间,切面将以字节码的形式编译到目标字节码文件中.
静态AOP可使用 aspectjrt 实现
pim.xml新增依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.12</version>
</dependency>
复制
新增MyAspectDeme.aj文件:
package org.example;
public aspect MyAspectJDemo {
pointcut add():call(* MathService.add(..));
before():add(){
System.out.println("add方法执行后记录日志");
}
after():add(){
System.out.println("add方法执行前记录日志");
}
}
复制
调用类:
package org.example;
public class Main {
public static void main(String[] args) {
MathService mathService = new MathServiceImpl();
System.out.println(mathService.add(1, 2));
}
}
复制
写好之后,还需要通过 aspectjtools.jar 方式编译
在编译后,main.class将新增字节码织入点:
和正常编译的对比:
运行结果:
也可以通过注解类进行切入:
package org.example;
import org.aspectj.lang.annotation.*;
@Aspect
public class TestAspect {
@Pointcut("execution(* MathServiceImpl.add(..))")
public void pointCutMethod() {
}
@Before("pointCutMethod()")
public void testBeforeAdvice() {
System.out.println("test advice add before");
}
@After("pointCutMethod()")
public void testAfterAdvice() {
System.out.println("test advice add after");
}
}
复制
输出:
动态代理
通过jdk提供的Proxy 类,可实现原生的动态代理:
public static void main(String[] args) {
MathServiceImpl mathService = new MathServiceImpl();
ClassLoader loader = mathService.getClass().getClassLoader();
Class<?>[] interfaces = mathService.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//可以加上需要的非业务代码
//method.getName()获取方法名
// Arrays.asList(args)获取输入值
System.out.println("this is " + method.getName() + " method begin with" + Arrays.asList(args));
//method:表示代理对象要代理的方法
//invoke:回调该函数
//args:方法需要的参数
Object result = method.invoke(mathService, args);//代理对象回调该方法
return result;
}
};
//先写此处方法,才可找到上述三个方法填写方式
Object o = Proxy.newProxyInstance(loader, interfaces, h);
MathService mathServiceProxy = (MathService) o;
System.out.println(mathServiceProxy.add(1, 2));
}
复制
代码类封装:
package org.example;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ProxyFactory {
//被代理对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//获取代理对象
public Object getProxy() {
/**
* ClassLoader loader, 被代理对象的类加载器
* Class<?>[] interfaces, 被代理对象实现的接口
* InvocationHandler h: 当代理对象执行被代理的方法时,会触发该对象中的invoke功能
*/
ClassLoader loader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//可以加上需要的非业务代码
//method.getName()获取方法名
// Arrays.asList(args)获取输入值
System.out.println("this is " + method.getName() + " method begin with" + Arrays.asList(args));
//method:表示代理对象要代理的方法
//invoke:回调该函数
//args:方法需要的参数
Object result = method.invoke(target, args);//代理对象回调该方法
return result;
}
};
//先写此处方法,才可找到上述三个方法填写方式
Object o = Proxy.newProxyInstance(loader, interfaces, h);
return o;
}
}
复制
package org.example;
public class Main {
public static void main(String[] args) {
MathServiceImpl target=new MathServiceImpl();
ProxyFactory proxyFactory=new ProxyFactory(target);
MathService mathServiceProxy = (MathService) proxyFactory.getProxy();
System.out.println(mathServiceProxy.add(1, 2));
}
}
复制
cglib 动态代理工厂
pom.xml新增:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
复制
代理工厂类:
package org.example;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//获取代理对象
public Object getProxy(){
Enhancer enhancer=new Enhancer();
//指定被代理对象的父类
enhancer.setSuperclass(target.getClass());
//指定回调类
enhancer.setCallback(this);
//创建代理对象
return enhancer.create();
}
//当代理对象执行代理方法时触发的方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before++++++++++++++++++++");
//可以加上需要的非业务代码
//method.getName()获取方法名
// Arrays.asList(args)获取输入值
System.out.println("this is "+method.getName()+" method begin with"+ Arrays.asList(args));
//method:表示代理对象要代理的方法
//invoke:回调该函数
//args:方法需要的参数
Object result = method.invoke(target, args);//代理对象回调该方法
return result;
}
}
复制
调用和上面的一致 区别在于这个方案不需要service继承interface
spring AOP动态代理
通过spring封装的组件进行实现动态代理,新增依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
复制
在原来的MathServiceImpl 类上新增@Service注解,以便于spring解析
在resources下新增spring.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: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
https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--包扫描-->
<context:component-scan base-package="org.example"/>
<!--开启aop注解-->
<aop:aspectj-autoproxy/>
</beans>
复制
新增TestAspect切面类:
package org.example;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Service;
@Service
@Aspect
public class TestAspect {
//任意返回类型 aop包下的所有类都有切面日志 使用通配符
//第一个*:修饰符和返回值类型
//第二个*:所有类
//第三个*:所有方法
@Before("execution(* org.example.*.*(..))")
public void before() {
System.out.println("方法执行前的日志");
}
@After("execution(* org.example.*.*(..))") //总会被执行,不管有没有异常
public void after() {
System.out.println("方法执行后的日志");
}
}
复制
调用main:
package org.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
//从spring容器中获取
ApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
MathService mathService = (MathService) app.getBean("mathServiceImpl");
Double add = mathService.add(10, 5);
System.out.println(add);
}
}
复制
输出:
应用场景
- 性能测试
- 日志记录
- 事务
- 权限控制
- 等等
- 点赞
- 收藏
- 关注作者
评论(0)