【鸽天尊】琼明神女录:一段跨越时空的国风仙侠传奇

核心内容摘要

9点捷克街头:一场跨越边境的暖心邂逅,乌克兰女性的最新动态
汤姆叔叔温馨提示:这30秒,是送给疲惫灵魂的“头等舱自救指南”

男生和女生一起错错:打破界限,碰撞火花,解锁无限可能

介绍AOP

AOP的定义AOP是Aspect Oriented Programming意为面向切面编程。

那什么是AOP我们先回顾一下OOPObject Oriented Programming意为面向对象编程。

OOP的主要功能是封装、继承、多态。

而AOP是一种新的编程方式它和OOP不同OOP把系统看做多个对象的交互AOP把系统分解为不同的关注点或者称之为切面Aspect。

简单来说AOP是一种思想是对某一类事情的集中处理。

AOP采取横向抽取机制取代了传统纵向继承体系重复性代码。

如下图所示AOP的作用在程序运行期间在不修改程序源代码的基础上对已有的方法进行增强。

无侵入性解耦。

1 举例说明要理解AOP的概念我们先用OOP举例比如一个业务组件BookService它有几个业务方法createBook(): 添加新的BookupdateBook(): 修改已有的BookdeleteBook(): 删除Book对每个业务方法除了业务逻辑还需要安全检查、日志记录和事务处理。

它的代码像这样public class BookService { public void createBook(Book book) { securityCheck(); Transaction tx startTransaction(); try { // 核心业务逻辑 tx.commit(); } catch (RuntimeException e) { tx.rollback(); throw e; } log(created book: book); } public void updateBook(Book book) { securityCheck(); Transaction tx startTransaction(); try { // 核心业务逻辑 tx.commit(); } catch (RuntimeException e) { tx.rollback(); throw e; } log(updated book: book); } }对于安全检查、日志、事务等代码它们会重复出现在每个业务方法中。

使用OOP我们很难将这些四处分散的代码模块化。

BookService关心的是自身的核心逻辑但整个系统还要求关注安全检查、日志、事务等功能这些功能实际上“横跨”多个业务方法为了实现这些功能不得不在每个业务方法上重复编写代码。

一种可行的方式是使用Proxy模式将某个功能例如权限检查视作一种切面Aspect把日志、事务也视为切面然后以某种自动化的方式把切面织入到核心逻辑中实现Proxy模式。

如果我们以AOP的视角来编写上述业务可以依次实现核心逻辑即BookService切面逻辑即

2.

权限检查的Aspect

2.

日志的Aspect

2.

事务的Aspect。

然后以某种方式让框架来把上述3个Aspect以Proxy的方式“织入”到BookService中这样一来就不必编写复杂而冗长的Proxy模式。

AOP原理如何把切面织入到核心逻辑中这正是AOP需要解决的问题。

换句话说如果客户端获得了BookService的引用当调用bookService.createBook()时如何对调用方法进行拦截并在拦截前后进行安全检查、日志、事务等处理就相当于完成了所有业务功能。

在Java平台上对于AOP的织入有3种方式编译期在编译时由编译器把切面调用编译进字节码这种方式需要定义新的关键字并扩展编译器AspectJ是一种基于Java语言的AOP框架它扩展了Java编译器使用关键字aspect在编译时实现织入。

类加载器在目标类被装载到JVM时通过一个特殊的类加载器对目标类的字节码重新“增强”。

运行期目标对象和切面都是普通Java类通过JVM的动态代理功能或者第三方库实现运行期动态织入。

Spring AOP就是在运行期通过代理方式向目标类织入增强代码。

最简单的方式就是第三种。

什么是Spring AOPSpring AOP是AOP的一种实现方式它的实现就是基于JVM的动态代理。

由于JVM的动态代理要求必须实现接口如果一个普通类没有业务接口就需要通过CGLIB或者javassist这些三方库实现。

AOP的组成

1 几个核心概念PointCut: 切入点。

它的作用是提供一组规则使用切点表达式定义对应用程序的哪些方法进行增强。

JoinPoint: 连接点。

满足切点规则的具体方法就是连接点也就是可以插入切面的方法。

切点是一组连接点的集合。

advice: 通知。

代码增强指连接点上执行的动作。

Aspect切面。

即一个横跨多个核心逻辑的功能或者称之为系统关注点。

切面 切点 通知。

Introduction引介。

指为一个已有的Java对象动态的增加新的接口。

Weaving: 织入指将切面整合到应用程序的执行流程中。

Interceptor: 拦截器是一种实现增强的方式。

Target Object目标对象即真正执行业务的核心逻辑对象。

AOP Proxy: AOP代理是客户端持有的增强后的对象引用。

2 Advice通知的类型上面我们讲了什么是通知接下来学习通知的类型。

Spring Aop中通知类型有以下几种Around环绕通知。

此注解标注的通知方法在目标方法前后都被执行。

Before前置通知。

此注解标注的通知方法在目标方法前被执行。

AfterReturning: 返回后通知。

此注解标注的通知方法在目标方法正常返回后被执行有异常不会执行。

AfterThrowing: 异常后通知。

此注解标注的通知方法在目标方法发生异常后被执行。

After后置通知。

此注解标注的通知方法在目标方法后被执行无论是否有异常都会执行。

3.

1 没有异常时通知的运行顺序程序正常运行的情况下AfterThrowing标识的通知方法不会执行。

3.

2 有异常时通知的运行顺序AfterReturning标识的通知方法不会执行。

AfterThrowing标识的通知方法执行了。

Around环绕通知中原始方法调用有异常通知中的环绕后的代码逻辑也不会再执行了。

注意Around 环绕通知需要调用ProceedingJoinPoint.proceed() 来让原始方法执行其他通知不需要考虑目标方法执行。

Around 环绕通知方法的返回值必须指定为 Object来接收原始方法的返回值否则原始方法执行完毕是获取不到返回值的。

一个切面类可以有多个切点。

Spring AOP的使用示例我们以UserService和MailService为例这两个属于核心业务逻辑现在我们准备给UserService的每个业务方法执行前添加日志给MailService的每个业务方法执行前后添加日志在Spring中需要以下步骤

引入Spring对AOP的支持如果是SpringBoot项目就引入org.springframework.boot:spring-boot-starter-aop包。

如果是Spring项目但不是SpringBoot就引入org.springframework:spring-aspects包。

代码实现Spring AOP有两种实现方式基于Aspect、基于自定义注解

1 基于Aspect我们定义一个LoggingAspectAspect Component public class LoggingAspect { // 在执行UserService的每个方法前执行: Before(execution(public * com.itranswarp.learnjava.service.UserService.*(..))) public void doAccessCheck() { System.err.println([Before] do access check...); } // 在执行MailService的每个方法前后执行: Around(execution(public * com.itranswarp.learnjava.service.MailService.*(..))) public Object doLogging(ProceedingJoinPoint pjp) throws Throwable { System.err.println([Around] start pjp.getSignature()); // 执行真正的业务逻辑 Object retVal pjp.proceed(); System.err.println([Around] done pjp.getSignature()); return retVal; } }观察doAccessCheck()方法我们定义了一个Before注解里面的execution是切点表达式告诉AspectJ应该在何处执行该方法这里写的意思是执行UserService的每个public方法前执行doAccessCheck()代码。

再观察doLogging()方法我们定义了一个Around注解它和Before不同Around可以决定是否执行目标方法因此我们在doLogging()内部先打印日志再调用方法最后打印日志后返回结果。

在LoggingAspect类的声明处除了用Component表示它本身也是一个Bean外我们再加上Aspect注解表示它的Before标注的方法需要注入到UserService的每个public方法执行前Around标注的方法需要注入到MailService的每个public方法执行前后。

如果不是SpringBoot项目我们需要给Configuration类加上一个EnableAspectJAutoProxy注解Configuration ComponentScan EnableAspectJAutoProxy public class AppConfig { ... }Spring的IoC容器看到这个注解就会自动查找带有Aspect的Bean然后根据每个方法的Before、Around等注解把AOP注入到特定的Bean中。

在SpringBoot项目中不需要显示的添加EnableAspectJAutoProxy注解。

在我们的主类或配置类上有SpringBootApplication注解SpringBoot的自动配置就会生效包括AOP的自动配置。

这样切面就会被自动识别并应用。

执行代码我们可以看到以下输出[Before] do access check... [Around] start void com.itranswarp.learnjava.service.MailService.sendRegistrationMail(User) Welcome, test! [Around] done void com.itranswarp.learnjava.service.MailService.sendRegistrationMail(User) [Before] do access check... [Around] start void com.itranswarp.learnjava.service.MailService.sendLoginMail(User) Hi, Bob! You are logged in at

T23:13:

5

16799608:00[Asia/Shanghai] [Around] done void com.itranswarp.learnjava.service.MailService.sendLoginMail(User)这说明执行业务逻辑前后确实执行了我们定义的Aspect即LoggingAspect的方法。

使用切入点表达式execution(public * com.itranswarp.learnjava.service.UserService.*(..))这种方式基本能实现无差别全覆盖即某个包下面的所有Bean的所有方法都会被这个doAccessCheck()方法拦截非精准打击误伤面非常大。

我们在使用AOP时要注意到虽然Spring容器可以把指定的方法通过AOP规则装配到指定的Bean的指定方法前后但是如果自动装配时因为不恰当的范围容易导致意想不到的结果即很多不需要AOP代理的Bean也被自动代理了并且后续新增的Bean如果不清楚现有的AOP装配规则容易被强迫装配。

使用AOP时被装配的Bean最好自己能清清楚楚地知道自己被安排了。

例如Spring提供的Transactional就是一个非常好的例子。

如果我们自己写的Bean希望在一个数据库事务中被调用就标注上TransactionalComponent public class UserService { // 有事务: Transactional public User createUser(String name) { ... } // 无事务: public boolean isValidName(String name) { ... } }因此装配AOP的时候使用注解是最好的方式。

2 基于自定义注解我们以一个实际例子演示如何使用注解实现AOP装配。

为了监控应用程序的性能我们定义一个性能监控的注解Target(METHOD) Retention(RUNTIME) public interface MetricTime { String value(); }

在需要被监控的关键方法上标注该注解Component public class UserService { // 监控register()方法性能: MetricTime(register) public User register(String email, String password, String name) { ... } ... }

然后我们定义切面MetricAspectAspect Component public class MetricAspect { Around(annotation(metricTime)) public Object metric(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable { String name metricTime.value(); long start System.currentTimeMillis(); try { return joinPoint.proceed(); } finally { long t System.currentTimeMillis() - start; // 写入日志或发送至JMX: System.err.println([Metrics] name : t ms); } } }注意metric()方法标注了Around(annotation(metricTime))它的意思是符合条件的目标方法是带有MetricTime注解的方法因为metric()方法参数类型是MetricTime注意参数名是metricTime不是MetricTime我们通过它获取性能监控的名称。

有了MetricTime注解再配合MetricAspect任何Bean只要方法标注了MetricTime注解就可以自动实现性能监控。

运行代码输出结果如下Welcome, Bob! [Metrics] register: 16ms

Spring AOP的原理那么LoggingAspect定义的方法是如何注入到其他Bean的呢我们以LoggingAspect.doAccessCheck()为例要把它注入到UserService的每个public方法中最简单的方法是编写一个子类并持有原始实例的引用public UserServiceAopProxy extends UserService { private UserService target; private LoggingAspect aspect; public UserServiceAopProxy(UserService target, LoggingAspect aspect) { this.target target; this.aspect aspect; } public User login(String email, String password) { // 先执行Aspect的代码: aspect.doAccessCheck(); // 再执行UserService的逻辑: return target.login(email, password); } public User register(String email, String password, String name) { aspect.doAccessCheck(); return target.register(email, password, name); } ... }这些都是Spring容器启动时为我们自动创建的注入了Aspect的子类它取代了原始的UserService原始的UserService实例作为内部变量隐藏在UserServiceAopProxy中。

如果我们打印从Spring容器获取的UserService实例类型它类似UserService$$EnhancerBySpringCGLIB$$1f44e01c实际上是Spring使用CGLIB动态创建的子类但对于调用方来说感觉不到任何区别。

Spring AOP的原理Spring AOP对接口类型使用JDK动态代理对普通类使用CGLIB创建子类。

如果一个Bean的class是finalSpring将无法为其创建子类。

Spring AOP的使用场景日志记录在方法执行前后自动记录日志无需在每个方法中手动添加日志代码。

事务管理确保数据操作的一致性和完整性。

例如在数据库操作前后自动开启和提交事务。

安全检查在方法执行前进行权限验证。

例如检查用户是否有权访问某个资源。

性能监控测量方法的执行时间帮助识别性能瓶颈。

缓存处理在方法执行前后自动缓存结果减少重复计算。

错误处理捕获方法执行中抛出的异常并进行统一处理或记录。

数据校验在请求接口时先校验参数的合法性。

51视频污-51视频污应用

百度百家号客服电话人工服务

123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123