Spring超详细讲解AOP面向切面
说明:基于atguigu学习笔记。
简介
AOP(Aspect Oriented Programming)是一种面向切面的编程思想。不同于面向对象里的继承思想,当需要为多个不具有继承关系的对象引人同一个公共行为时,也就是把程序横向看,寻找切面,插入公共行为。
AOP目的是为了些把影响了多个类的公共行为抽取到一个可重用模块里,不通过修改源代码方式,在主干功能里面添加新功能,降低模块间的耦合度,增强代码的可操作性和可维护性。
例如,每次用户请求我们的服务接口,都要进行权限认证,看看是否登录,就可以在不改变原来接口代码的情况下,假如认证这个新功能。
Spring AOP底层使用了代理模式。下面具体了解一下。
AOP底层原理
代理概念
所谓代理,也就是让我们的代理对象持有原对象,在执行原对象目标方法的前后可以执行额外的增强代码。
代理对象需要是原对象接口的实现或原对象的子类,这样就可以在对象引用处直接替换原对象。
代理方式分静态代理和动态代理,区别在于代理对象生成方式不同
静态代理:在编译期增强,生成可见的代理class,使用代理类替换原有类进行调用。
动态代理:在运行期增强,内存中动态生成代理类,使用反射动态调用原对象方法。
在spring中使用的是JDK、CGLIB动态代理对象。
JDK动态代理:必须基于接口,即生成的代理对象是对原对象接口的实现,相当于替换了实现类,面向对象中接口可以替换实现类。
CGLIB动态代:理基于继承,即生成的代理对象是原对象的子类,面向对象中子类可以替换父类。
JDK动态代理实现
使用 JDK 动态代理,使用反射包里 java.lang.refelft.Proxy
类的 newProxyInstance
方法创建代理对象。
源码如下
@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) { Objects.requireNonNull(h); final Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass(); /* * Look up or generate the designated proxy class and its constructor. */ Constructor<?> cons = getProxyConstructor(caller, loader, interfaces); return newProxyInstance(caller, cons, h); }
方法有三个参数:
第一参数,类加载器
第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分
下面以JDK动态代理为例,具体步骤。
1.创建接口,定义方法
public interface UserDao { public int add(int a,int b); public String update(String id); }
2.创建接口实现类,实现方法
public class UserDaoImpl implements UserDao { @Override public int add(int a, int b) { return a+b; } @Override public String update(String id) { return id; } }
3.使用 Proxy 类创建接口代理对象
public class JDKProxy { public static void main(String[] args) { //创建接口实现类代理对象 Class[] interfaces = {UserDao.class}; // Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() { // @Override // public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // return null; // } // }); UserDaoImpl userDao = new UserDaoImpl(); UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); int result = dao.add(1, 2); System.out.println("result:"+result); } } //创建代理对象代码 class UserDaoProxy implements InvocationHandler { //1 把创建的是谁的代理对象,把谁传递过来 //有参数构造传递 private Object obj; public UserDaoProxy(Object obj) { this.obj = obj; } //增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法之前 System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args)); //被增强的方法执行 Object res = method.invoke(obj, args); //方法之后 System.out.println("方法之后执行...."+obj); return res; } }
Spring中的AOP
相关术语
1.连接点(Join point): 类里面可以被增强的方法。
2.切入点:真正被增强的方法。
3.通知:实际增强处理的逻辑。
AOP框架汇总通知分为以下几种:
- 前置通知@Before
- 后置通知@AfterReturning
- 环绕通知@Around
- 异常通知@AfterThrowing
- 最终通知@After
4.切面:把通知应用到切入点的过程,是一个动作。
AspectJ
AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,进行 AOP 操作。
基于 AspectJ 实现 AOP 操作可以有两种方式:基于xml配置文件、基于注解。
要使用AspectJ,首先要引入相关依赖:
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> </dependency>
使用AspectJ时,会寻找切入点,这时候会用到切入点表示,为了知道对哪个类里面的哪个方法进行增强。
语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
举例 1:对 com.example.dao.BookDao 类里面的 add 进行增强
execution(* com.example.dao.BookDao.add(..))举例 2:对 com.example.dao.BookDao 类里面的所有的方法进行增强
execution(* com.example.dao.BookDao.* (..))举例 3:对 com.example.dao 包里面所有类,类里面所有方法进行增强
execution(* com.example.dao.*.* (..))
实现AOP
1.创建项目,引入依赖
依赖如下:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>spring-demo02</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.2.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.2.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.6.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.6.RELEASE</version> </dependency> </dependencies> </project>
2.创建类
创建一个自己的类,写一个要增强的方法,并使用注解管理bean
package com.example; import org.springframework.stereotype.Component; @Component public class User { public void add () { System.out.println("user add method..."); } }
3.创建代理增强类
创建增强类,使用@Aspect注解。
在增强类里面,创建方法,让不同方法代表不同通知类型,此例创建前置通知使用@Before
package com.example; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Component @Aspect public class UserProxy { @Before(value = "execution(* com.example.User.add())") public void before () { System.out.println("proxy before..."); } }
4.xml配置
开启注解扫描和Aspect 生成代理对象
<?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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 开启注解扫描 --> <context:component-scan base-package="com.example"></context:component-scan> <!-- 开启 Aspect 生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
5.测试类
package com.example; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AopTest { public static void main(String[] args) { ClassPathXmlApplicationContext ap = new ClassPathXmlApplicationContext("bean1.xml"); User user = ap.getBean("user", User.class); user.add(); } }
结果先输出proxy before…,再输出user add method…。说明我们的前置通知确实再被增强方法之前执行成功。
不同通知类型实现
下面把五种通知都实现看一下顺序,修改我们的代理类如下:
package com.example; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component @Aspect public class UserProxy { /** * 前置通知 */ @Before(value = "execution(* com.example.User.add())") public void before () { System.out.println("proxy before..."); } /** * 后置通知 */ @AfterReturning(value = "execution(* com.example.User.add())") public void afterReturning() { System.out.println("proxy afterReturning..."); } /** * 最终通知 */ @After(value = "execution(* com.example.User.add())") public void after() { System.out.println("proxy after..."); } /** * 异常通知 */ @AfterThrowing(value = "execution(* com.example.User.add())") public void afterThrowing() { System.out.println("proxy afterThrowing..."); } /** * 环绕通知 */ @Around(value = "execution(* com.example.User.add())") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 环绕之前 System.out.println("proxy around before..."); proceedingJoinPoint.proceed(); // 环绕之后 System.out.println("proxy around after..."); } }
执行结果如下:
proxy around before...
proxy before...
user add method...
proxy around after...
proxy after...
proxy afterReturning...
相同的切入点抽取
上面代码可以看到我们的通知value是相同的,这时候可以抽取出来公用,改写代理类如下代码如下:
package com.example; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Component @Aspect public class UserProxy { @Pointcut(value = "execution(* com.example.User.add())") public void pointDemo() {} /** * 前置通知 */ @Before(value = "pointDemo()") public void before () { System.out.println("proxy before..."); } /** * 后置通知 */ @AfterReturning(value = "pointDemo()") public void afterReturning() { System.out.println("proxy afterReturning..."); } /** * 最终通知 */ @After(value = "pointDemo()") public void after() { System.out.println("proxy after..."); } /** * 异常通知 */ @AfterThrowing(value = "pointDemo()") public void afterThrowing() { System.out.println("proxy afterThrowing..."); } /** * 环绕通知 */ @Around(value = "pointDemo()") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 环绕之前 System.out.println("proxy around before..."); proceedingJoinPoint.proceed(); // 环绕之后 System.out.println("proxy around after..."); } }
增强类优先级
有多个增强类多同一个方法进行增强,设置增强类优先级。
在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高。
@Component @Aspect @Order(1) public class UserProxy
完全使用注解开发
创建配置类,不需要创建 xml 配置文件。
package com.example; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @ComponentScan(basePackages = {"com.example"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class AopConfig { }
测试类:
package com.example; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class AopTest { public static void main(String[] args) { ApplicationContext ap = new AnnotationConfigApplicationContext(AopConfig.class); User user = ap.getBean(User.class); user.add(); } }
到此这篇关于Spring超详细讲解AOP面向切面的文章就介绍到这了,更多相关Spring面向切面内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
最新评论