在日常开发中,我们经常用到切面,比如在springboot搭建的服务里,经常使用日志切面来记录客户端的访问日志。
我用一个小的demo演示
项目结构如下
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yyu</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- Inherit defaults from Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
</parent>
<!-- Add typical dependencies for a web application -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
OperationLog
package com.yyu.aop;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
String desc() default "";
}
OperationLogAop
package com.yyu.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class OperationLogAop {
@Pointcut("@annotation(com.yyu.aop.OperationLog)")
public void pointCut() {
}
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("before===" + getDesc(joinPoint));
}
@After("pointCut()")
public void after(JoinPoint joinPoint) {
System.out.println("after===" + getDesc(joinPoint));
}
@Around("pointCut()")
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("around before===" + getDesc(point));
point.proceed();
System.out.println("around after===" + getDesc(point));
}
@AfterReturning("pointCut()")
public void afterReturning(JoinPoint joinPoint) {
System.out.println("afterReturning===" + getDesc(joinPoint));
}
@AfterThrowing("pointCut()")
public void afterThrow(JoinPoint joinPoint) {
System.out.println("afterThrow===" + getDesc(joinPoint));
}
private String getDesc(JoinPoint joinPoint) {
MethodSignature sign = (MethodSignature) joinPoint.getSignature();
Method method = sign.getMethod();
Class<?> declareClass = joinPoint.getTarget().getClass();
OperationLog annotation = method.getAnnotation(OperationLog.class);
return annotation.desc();
}
}
启动类
package com.yyu;
import com.yyu.aop.OperationLog;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class YyuApp {
@GetMapping(value = "/aop")
@OperationLog(desc = "测试AOP")
public String testAop() {
System.out.println("执行testAop方法");
return "ok";
}
public static void main(String[] args) {
SpringApplication.run(YyuApp.class, args);
}
}
运行后访问http://localhost:8080/aop
控制台输出如下
在这个示例中,通过aop我们可以实现对日志的集中管理,提高了工作效率和代码的整洁性,这也正是我们经常提到的面向切面编程,可以说面向切面加强了OOP。
AspectJ与Spring AOP
AspectJ和Spring AOP都实现了AOP技术,但是很多人经常混淆Spring AOP和AspectJ,AspectJ其实并不属于Spring,它是Eclipse发起的一个项目,AspectJ是一套关于AOP的完整体系,是一门独立的语言,他有自己关于AOP独立的实现。
Aspectj提供了非常完善的AOP能力,几乎能在java class的任何时刻使用织入功能。
-
编译时织入
利用ajc编译器替代javac编译器,直接将源文件(java或者aspect文件)编译成class文件并将切面织入进代码。 -
编译后织入
利用ajc编译器向javac编译期编译后的class文件或jar文件织入切面代码。 -
加载时织入
不使用ajc编译器,利用aspectjweaver.jar工具,使用java agent代理在类加载期将切面织入进代码。
(aspectjweaver.jar中,存在着@Before,@After等注解,并且有他们织入的实现(ASM动态代理))
ajc编译器,是一种能够识别aspect语法的编译器,它是采用java语言编写的,由于javac并不能识别aspect语法,便有了ajc编译器,注意ajc编译器也可编译java文件。
而Spring AOP却完全只会使用Cglib或者JDK动态代理,在类加载时通过动态代理织入。
为了避免依赖于特定的像acj这样的编译器,Spring AOP也可以说放弃了静态织入,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入)。
Spring AOP借用了AspectJ的概念,可以拿aspectjweaver中的一些功能,来为Spring AOP服务。例如切点、连接点、切点表达式、前置通知、后置通知等等,并且仿照AspectJ实现了自己的AOP功能。
综上,我们可以说Spring AOP只能在运行(加载)时织入,而AspectJ在编译时和运行(加载)时均可。
JDK动态代理与Cglib
二者主要区别是JDK动态代理只能对实现了接口的类生成代理。Cglib则对类有没有实现接口都可实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final。
Spring如何选择两种代理模式的?Spring AOP会在实际使用中,根据所代理的类灵活切换为JDK动态代理或Cglib。
1、如果目标对象实现了接口,则默认采用JDK动态代理;
2、如果目标对象没有实现接口,则使用Cglib代理;
3、如果目标对象实现了接口,但强制使用了Cglib,则使用Cglib进行代理
以下用示例来说明
实现了接口的UserServiceImpl和没有实现接口的DeptService
public interface UserService {
void addUser(String user);
}
public class UserServiceImpl implements UserService {
@Override
public void addUser(String user) {
System.out.println("添加了用户:" + user);
}
}
public class DeptService {
public void addDept(String dept) {
System.out.println("添加了部门:" + dept);
}
}
- JDK动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyFactory implements InvocationHandler {
private Object target;
public JdkProxyFactory(Object target) {
super();
this.target = target;
}
public Object createProxy() {
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this);
return newProxyInstance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行代理方法前......");
Object invoke = method.invoke(target, args);
System.out.println("这是代理方法后......");
return invoke;
}
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
JdkProxyFactory factory = new JdkProxyFactory(userService);
UserService proxy = (UserService) factory.createProxy();
proxy.addUser("yyu");
}
}
执行结果
- Cglib
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxyFactory implements MethodInterceptor {
private Object target;
public CglibProxyFactory(Object target) {
super();
this.target = target;
}
public Object createProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("执行代理方法前......");
Object invoke = method.invoke(target, objects);
System.out.println("这是代理方法后......");
return invoke;
}
public static void main(String[] args) {
DeptService deptService = new DeptService();
CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(deptService);
DeptService proxy = (DeptService) cglibProxyFactory.createProxy();
proxy.addDept("hik");
}
}
执行结果