目录
1. AOP概述
1.1 AOP是什么
1.2 AOP术语
1.3.Spring AOP原理(代理模式)
静态代理
jdk静态代理:
动态代理
Jdk动态代理:
CGLIB动态代理
2. Spring AOP的使用
2.1 依赖
2.2 切面 Aspect(切面):是切入点和通知的结合。
2.3 切点表达式
2.4 JDK动态代理和CGLib
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。
cglib
cglib
3.1
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实现。
org.springframework.boot
spring-boot-starter-aop
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、*(..) :第三个*表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
"execution(public * *(..))"
"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