目录
AOP(Aspect Oriented Programming,面向切面编程),首先面向切面是一种思想,它看似与面向对象相对,但实则为面向对象的延续。
面向对象自问世以来,因其贴合现实生活,对编程人员极为友好,广受业内喜爱。 但单纯的面向对象编程,在一些场景下,好像不如现实生活中简单自然。 在OOP(面向对象编程)中,类之间的关系如下图:
这其中清晰地展示了类与类之间的父子关系,却没办法表示如下图所示的同级关系。
AOP的出现便是为了弥补此类需求。
AOP作为一种编程思想,其应用场景和具体的技术实现并不是固定的,其实Spring MVC中的拦截器就是AOP的一个具体实现。
关于Spring AOP,需要先掌握一组相关术语,以方便后面的学习和理解。
Joinpoint
(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。
Pointcut
(切入点):所谓切入点是指我们要对哪些Joinpoint
进行拦截的定义。
ABCD 四个方法分别就是连接点,我们要对ABC进行拦截定义,那么ABC整体就是切入点
Advice
(通知/增强):所谓通知是指拦截到Joinpoint
之后所要做的事情就是通知。通知分为前置通知,后置通知,异常通知,
最终通知,环绕通知。
你拦截到方法需要校验,校验这个动作就是增强
比如在ABC每个方法前都加一段代码,那么把这段代码我们就加在过滤器或者拦截器中,写一段即可, 这也属于方法的增强,
Target
(目标对象):代理的目标对象
ABCD所属于类型的对象就是Target
Weaving
(织入):是指把增强应用到目标对象来创建新的代理对象的过程。
A方法所在对象去创建一个代理对象A1,并把增强代码织入到A1中,这个过程称之为织入。
Proxy
(代理):一个类被AOP织入增强后,就产生一个结果代理类。
A1就是代理类,A方法就是目标
Aspect
(切面):是切入点和通知的结合。
A方法所在对象去创建一个代理对象A1,
B方法所在对象去创建一个代理对象B1,
C方法所在对象去创建一个代理对象C1,
那么A1 B1 C1三个代理类整体就是个切面。
参考图解
Spring AOP的原理是动态代理,通过对目标类的代理,完成功能代码的织入。
生活中的代理:律师,医生
代码中的代理:设计一个算法,验证它的耗时。
代理可分为静态代理和动态代理
静态代理,可在编译阶段对目标类织入增强代码;代码实现
典型有JDK静态代理和AspectJ
动态代理,在程序运行过程中,动态地为目标类织入增强代码。反射实现
典型有JDK动态代理和CGLib
Spring AOP采用JDK动态代理+CGLib的方式,使用二者结合体,原因是两种技术都有一点缺陷:
JDK动态代理: 目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
CGLib动态代理: 继承目标类来进行代理,因此目标类可以不实现接口,但要求目标类不是能是final。
代理类在编译期间就已经确定了,代理类是需要开发者自己定义,可能会存在多个代理类
// 第一种 继承
// 目标类
public class Service1 {
? ?public String test(String name) {
? ? ? ?System.out.println("Hello ? " + name);
? ? ? ?return "Hello ? " + name;
? }
}
?
// 代理类
public class Service1Proxy extends Service1 {
? ?@Override
? ?public String test(String name) {
? ? ? ?System.out.println("Proxy....");
? ? ? ?return super.test(name);
? }
}
?
// 测试类
public class App {
? ?public static void main(String[] args) {
// ? ? Service1 service1 = new Service1();
// ? ? service1.test("AOP");
? ? ? ?Service1Proxy service1Proxy = new Service1Proxy();
? ? ? ?service1Proxy.test("AOP");
?
? }
}
?
// 第二种,接口实现
// 接口
public interface Service2 {
? ?String test(String name);
}
?
// 目标类
public class Service2Impl implements Service2 {
?
? ?@Override
? ?public String test(String name) {
? ? ? ?System.out.println("Hello " + name);
? ? ? ?return "Hello" + name;
? }
}
?
// 代理类
public class Service2Proxy implements Service2 {
? ?Service2 service2 = new Service2Impl();
?
? ?@Override
? ?public String test(String name) {
? ? ? ?System.out.println("Proxy....");
? ? ? ?return service2.test(name);
? }
}
?
// 测试类
// ? ? Service2 service2 = new Service2Impl();
// ? ? service2.test("AOP");
? ? ? ?Service2Proxy service2Proxy = new Service2Proxy();
? ? ? ?service2Proxy.test("AOP");
具体代理类型需要在运行期间确定,开发者不需要自己实现代理类
?
interface UserService {
? ?void addUser(String username, String password);
}
?
interface DeptService {
? ?void addDept(String deptName);
}
?
class UserServiceImpl implements UserService {
?
? ?@Override
? ?public void addUser(String username, String password) {
? ? ? ?System.out.println(username + "用户添加成功");
? }
}
?
class DeptServiceImpl implements DeptService {
?
? ?@Override
? ?public void addDept(String username) {
? ? ? ?System.out.println(username + "部门添加成功");
? }
?
}
?
class JdkDynamicProxy implements InvocationHandler {
?
? ?private Object object;
?
? ?@Override
? ?public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
? ? ? ?System.out.println("日志1");
? ? ? ?Object invoke = method.invoke(object, args);
? ? ? ?System.out.println("日志2");
? ? ? ?return invoke;
? }
?
? ?public Object newProxyInstance(Object obj) {
? ? ? ?this.object = obj;
? ? ? ?Object o =
? ? ? ?Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
? ? ? ?return o;
? }
}
?
class Main2 {
? ?public static void main(String[] args) {
? ? ? ?JdkDynamicProxy u = new JdkDynamicProxy();
?
? ? ? ?UserService o = (UserService) u.newProxyInstance(new UserServiceImpl());
? ? ? ?o.addUser("ls", "1234");
?
? ? ? ?DeptService o1 = (DeptService) u.newProxyInstance(new DeptServiceImpl());
? ? ? ?o1.addDept("销售部");
? }
}
//缺点:
//JDK动态代理: 目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
//CGLib动态代理:继承目标类来进行代理,因此目标类可以不实现接口,但要求目标类不是能是final。
<dependency>
? ?<groupId>cglib</groupId>
? ?<artifactId>cglib</artifactId>
? ?<version>3.1</version>
</dependency>
package com.whitecamellia.dymicproxy.cglibdynamicProxy;
?
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
?
import java.lang.reflect.Method;
?
public interface UseService {
? ?void addUser(String username, String password);
}
?
class UseServiceImpl implements UseService {
?
? ?@Override
? ?public void addUser(String username, String password) {
? ? ? ?System.out.println(username + "用户添加成功");
? }
}
?
class CglibProxy implements MethodInterceptor {
?
? ?public Enhancer enhancer = new Enhancer();
?
? ?public Object getDaoBean(Class cls) {
? ? ? ?enhancer.setSuperclass(cls);
? ? ? ?enhancer.setCallback(this);
? ? ? ?return enhancer.create();
? }
?
? ?@Override
? ?public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) {
? ? ? ? System.out.println("日志1");
? ? ? ? Object o1 = methodProxy.invokeSuper(o, objects);
? ? ? ? System.out.println("日志2");
? ? ? ? return o1;
? }
}
?
class Main3 {
? ?public static void main(String[] args) {
? ? ? ?CglibProxy u = new CglibProxy();
? ? ? ?UseService daoBean = (UseService) u.getDaoBean(UseServiceImpl.class);
? ? ? ?daoBean.addUser("qqq", "1212");
? }
}
//缺点:
//JDK动态代理: 目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
//CGLib动态代理:继承目标类来进行代理,因此目标类可以不实现接口,但要求目标类不是能是final。
Spring AOP采用JDK动态代理+CGLib的方式,原因是两种技术都有一点缺陷:
JDK动态代理是通过实现目标类的接口来进行代理,因此只能代理实现了接口的类;
CGLib通过继承目标类来进行代理,因此目标类可以不实现接口,但要求目标类不是能是final的。
鉴于JDK动态代理不需要额外jar包,是JDK内部技术,稳定性要比第三方强,所以Spring的策略是默认采用JDK动态代理,如果目标类没有实现接口,则采用CGLib。
Spring还引入了AspectJ对于AOP的配置方式,注意,仅仅是引入了配置方式,并未采用AspectJ的AOP实现。
创建springboot项目
Spring引入了AspectJ对于AOP的配置方式,注意,仅仅是引入了配置方式,并未采用AspectJ的AOP实现。
<dependency>
? ? ? ? ? ?<groupId>org.springframework.boot</groupId>
? ? ? ? ? ?<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Aspect
(切面):是切入点和通知的结合。1.切入点
2.通知
package com.whitecamellia.demoaop.aop;
?
/**
* @author Petrel
* @create 2022-11-04 10:46 AM
*/
public interface DemoService {
? ?void hello ();
}
?
?
?
package com.whitecamellia.demoaop.aop;
import org.springframework.stereotype.Component;
?
/**
* @author Petrel
* @create 2022-11-04 10:46 AM
*/
@Component
public class DemoServiceImpl implements DemoService{
? ?@Override
? ?public void hello() {
? ? ? ?System.out.println("hello...");
? ? ? ?// int i = 10 / 0
? }
}
?
?
package com.whitecamellia.demoaop.aop;
?
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
?
/**
* @author Petrel
* @create 2022-11-04 10:48 AM
*/
// 切面类
@Component
@Aspect
public class DemoAspect {
? ?// Before增强方式
? ?@Before("execution(* com.whitecamellia.demoaop.aop.*.*(..))")
? ?// 增强代码
? ?public void ?testBefore () {
? ? ? ?System.out.println("before.....");
? }
}
?
// test 测试
package com.whitecamellia.demoaop;
?
import com.whitecamellia.demoaop.aop.DemoService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
?
@SpringBootTest
class DemoaopApplicationTests {
? ?@Autowired
? ?private DemoService demoService;
?
? ?@Test
? ?void contextLoads() {
? ? ? ?demoService.hello();
? ? ? ?System.out.println(demoService.getClass());
? }
?
}
?
运行结果:
before.....
hello...
class com.whitecamellia.demoaop.aop.DemoServiceImpl$$EnhancerBySpringCGLIB$$36f6325b
// 程序运行中通过代理动态生成的代理类
完整测试代码
package com.whitecamellia.demoaop.aop;
?
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
?
/**
* @author Petrel
* @create 2022-11-04 10:48 AM
*/
// 切面类
@Component
@Aspect
public class DemoAspect {
?
? ?@Pointcut("execution(* com.whitecamellia.demoaop.aop.*.*(..))")
? ?public ?void pointCut () {}
? ?// 前置通知
? ?// Before增强方式
? ?@Before("pointCut ()")
? ?// 增强代码
? ?public void ?testBefore () {
? ? ? ?System.out.println("before.....");
? }
? ?// 后置通知
? ?@After("pointCut ()")
? ?public void ?testAfter () {
? ? ? ?System.out.println("after.....");
? }
?
? ?// 环绕通知
? ?@Around("pointCut ()")
? ?public Object ?testAround (ProceedingJoinPoint joinPoint) {
? ? ? ?System.out.println("before.....");
? ? ? ?Object proceed = null;
? ? ? ?try {
? ? ? ? ? ?proceed = joinPoint.proceed();
? ? ? } catch (Throwable e) {
? ? ? ? ? ?e.printStackTrace();
? ? ? }
? ? ? ?System.out.println("after.....");
? ? ? ?return proceed;
? }
? ?// 异常通知
? ?@AfterThrowing("pointCut ()")
? ?public void testAfterThrowing () {
? ? ? ?System.out.println("afterThrowing.....");
? }
? ?// 最终通知
? ?@AfterReturning(("pointCut ()"))
? ?public void ?testAfterReturning () {
? ? ? ?System.out.println("afterReturning.....");
? }
}
?
可参考如下实例:
pointcut = "execution(* com.whitecamellia.controller.*.*(..))"
execution(* com.whitecamellia.controller.*.*(..))"
整个表达式可以分为五个部分
1、execution():表达式主体。
2、第一个*号:表示返回类型,*号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点分别表示当前包和当前包的所有子包,com.whitecamellia包、子孙包
4、第二个*号:表示类名,*号表示所有的类。
5、*(..) :第三个*表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
?
<!-- 【拦截所有public方法】 -->
"execution(public * *(..))"
?
<!-- 【拦截所有save开头的方法 】 -->
"execution(* save*(..))"
?
<!-- 【拦截指定类的指定方法】 -->
"execution(public * com.whitecamellia.pointcut.OrderDao.save(..))"
?
<!-- 【拦截指定类的所有方法】 -->
"execution(* com.whitecamellia.pointcut.UserDao.*(..))"
?
<!-- 【拦截指定包,以及其子包下所有类的所有方法】 -->
"execution(* com..*.*(..))"
?
<!-- 【多个表达式 与或非】 -->
"execution(* com.whitecamellia.pointcut.UserDao.save()) || execution(*com.whitecamellia.pointcut.OrderDao.save())"
"execution(* com.whitecamellia.pointcut.UserDao.save()) or execution(*com.whitecamellia.pointcut.OrderDao.save())"
"execution(* com.whitecamellia.pointcut.UserDao.save()) && execution(*com.whitecamellia.pointcut.OrderDao.save())"
"execution(* com.whitecamellia.pointcut.UserDao.save()) and execution(* com.whitecamellia.pointcut.OrderDao.save())"
"!execution(* com.whitecamellia.pointcut.OrderDao.save())"
"not execution(* com.whitecamellia.pointcut.OrderDao.save())"
上方的写法主要是针对aspectJ框架支持的切面的配置。当前AOP的配置基本上都用上面这种形式的配置。
Spring默认采用JDK动态代理,如果目标类中的切入点不是基于接口实现的,那么则采用CGLib进行代理。
Spring Boot默认采用CGLib进行代理,想要切换为JDK动态代理,需要配置。
spring.aop.proxy-target-class=false