仔细想想SpringAOP也不难嘛,面试没有必要慌
文章已托管到GitHub,大家可以去GitHub查看阅读,欢迎老板们前来Star!
搜索关注微信公众号 码出Offer 领取各种学习资料!
SpringAOP
一、什么是AOP
AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
应用场景: 如日志记录、审计、声明式事务、安全性和缓存等。
二、场景分析
为了更好的理解AOP,渗透面向切面编程的思想。我这里举一个开发中很常见的例子。打印日志
首先,我们要先理解什么是日志。
日志: 日志是一种可以追踪某些软件运行时所发生事件的方法,软件开发人员可以向他们的代码中调用日志记录相关的方法来表明发生了某些事情。一个事件可以用一个可包含可选变量数据的消息来描述,此外,事件也有重要性的概念,这个重要性也可以被称为严重性级别(level)。开发者可以通过区分严重性级别来分析出想要的信息。
了解了什么是日志,那就要知道怎么打印日志,在哪里打印日志。打印日志,是引入依赖,使用日志工具来实现日志严重性级别和日志信息的打印。至于在哪里打印日志,当然是在我们项目代码中的关键位置了。
这里我们举一个例子在某一段代码的前后使用,有A、B、C三个方法,但是要在调用每一个方法之前,要求打印一行日志“某方法被调用了!”,在调用每个方法之后,也要求打印日志“某方法被调用完毕!”。
一般人会在每一个方法的开始和结尾部分都会添加一句日志打印吧,这样做如果方法多了,就会有很多重复的代码,显得很麻烦,这时候有人会想到,为什么不把打印日志这个功能封装一下,然后让它能在指定的地方(比如执行方法前,或者执行方法后)自动的去调用呢?如果可以的话,业务功能代码中就不会掺杂这一下其他的代码,所以AOP就是做了这一类的工作的。
其工作原理为JDK动态代理和CGLIB动态代理,这里就先不展开动态代理的知识了!还是先看AOP吧!
三、AOP术语
AOP作用: Spring的AOP编程即是通过动态代理类为原始类的方法添加辅助功能。
AOP术语 | 描述 |
---|---|
连接点(Joinpoint) | 连接点是程序类中客观存在的方法,可被Spring拦截并切入内容 |
切入点(Pointcut) | 被Spring切入连接点 |
通知、增强(Advice) | 可以为切入点添加额外功能,分为:前置通知、后置通知、异常通知、环绕通知等。 |
目标对象(Target) | 代理的目标对象 |
引介(Introduction) | 一种特殊的增强,可在运行期为类动态添加Field和Method |
织入(Weaving) | 把通知应用到具体的类,进而创建新的代理类的过程 |
代理(Proxy) | 被AOP织入通知后,产生的结果类 |
切面(Aspect) | 由切点和通知组成,将横切逻辑织入切面所指定的连接点中 |
四、AOP术语解析
3.1 连接点
简单来说,就是允许你使用通知、增强的地方。就比如在方法前后打印日志一样,我们可以在一段代码的前后做操作,可以在一段代码前做操作,可以在一段代码后做操作,可以在一段代码抛异常之后做操作。所以,在这里这些可以操作的一行行代码(方法等等)都是一个个的连接点。
3.2 切入点
把一个个方法等代码看作连接点,那我们从哪个位置打印日志(增强操作)呢,而我们挑选出需要打印日志的位置(也就是连接点的周围),就被称为切入点。
3.3 增强、通知
关于增强,在上面我已经说到过了,通过在切入点做的操作叫做增强,比如我们要打印日志的话,日志就是一个增强功能操作。
3.4 目标对象
目标对象,简单来说是要被增强的对象。
3.5 引介
允许我们向现有的类添加新方法属性。这不就是把切面(也就是增强定义的新方法属性)用到目标对象中
3.6 织入
把增强应用到具体的目标对象中,进而创建新的代理类的过程
3.7 代理
代理就像我们买房子的中介一样,也就是被AOP织入后产生的代理对象(中介对象),通过代理对象可以实现对我们的目标对象增强
3.8 切面
切面是通知(增强)和切入点的结合。通知说明了干什么和什么时候干,而切入点说明了在哪干,这就是一个完整的切面定义。
五、SpringAOP开发步骤
5.1 pom.xml文件引入依赖
引入Spring核心依赖(spring-context)和SpringAOP依赖(spring-aspects)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
5.2 创建spring-context.xml文件并添加schema
我们需要在核心配置文件的头文件中添加aop和context的Schema
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
</beans>
5.3 定义原始类
模拟创建一个原始类
public interface UserService {
public void save();
}
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("save method executed...");
}
}
5.4 定义通过类
定义通知类(添加额外功能做增强)
public class MyAdvice implements MethodBeforeAdvice { //实现前置通知接口
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("before advice executed...");
}
}
5.5 定义bean
配置bean对象
<!--原始对象-->
<bean id="us" class="com.mylifes1110.service.impl.UserServiceImpl" />
<!--辅助(增强)对象-->
<bean id="myAdvice" class="com.mylifes1110.advice.MyAdvice" />
5.6 定义切入点形成切面
定义切入点(PointCut)并形成切面(Aspect)
<aop:config>
<!--切点-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--组装切面-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut" />
</aop:config>
5.7 增强结果
使用的前置通知,结果增强的打印语句
before advice executed...
会在save()方法的前面打印save method executed...
。
六、通知
定义通知类,达到通知(增强)效果。实现不同的接口并覆盖方法来达到不同的通知效果
通知名称 | 接口 | 描述 |
---|---|---|
前置通知 | MethodBeforeAdvice接口 | 在目标对象的前面做增强 |
后置通知 | AfterAdvice接口 | 注意:此接口内方法为空,后置默认使用第三种即可 |
后置通知 | AfterReturningAdvice接口 | 在目标对象的后面做增强 |
异常通知 | ThrowsAdvice | 在目标对象发生异常后做增强 |
环绕通知 | MethodInterceptor | 在目标对象的前后做增强 |
七、通配切入点
根据表达式通配切入点
通配表达式顺序: 返回值类型 全类名.方法名(形参)
注意: 可以用
..
来实现通配形参列表,可以使用*
来通配方法名或返回值类型
<!-- public int com.mylifes1110.service.UserServiceImpl.queryUser(int,String,com.entity.User) -->
<!--匹配参数-->
<aop:pointcut id="myPointCut" expression="execution(* *(com.mylifes1110.bean.User))" />
<!--匹配方法名(无参)-->
<aop:pointcut id="myPointCut" expression="execution(* save())" />
<!--匹配方法名(任意参数)-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))" />
<!--匹配返回值类型-->
<aop:pointcut id="myPointCut" expression="execution(com.mylifes1110.bean.User *(..))" />
<!--匹配类名-->
<aop:pointcut id="myPointCut" expression="execution(* com.mylifes1110.bean.UserServiceImpl.*(..))" />
<!--匹配包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.mylifes1110.bean.*.*(..))" />
<!--匹配包名、以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* com.mylifes1110..*.*(..))" />
八、代理模式
8.1 代理模式
将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用。
功能分离 |
---|
8.2 代理模式应用场景模拟
通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。
场景模拟: 我们在租赁房子需要走如下流程:
- 发布租房信息
- 带租户看房
- 签合同
- 收房租
可如果你是房东,生活中还有其他的琐事,怎么办呢?那你是不是可以把不重要不核心的环节交给中介(代理)去做呢?比如:发布租房信息和带租户看房。这两件事情交给中介去做就好了,我们自己处理自己的事情,而且中间联系好租户我们走比较重要的流程就可以,比如签合同、收房租。
8.3 创建Service接口和实现类
创建Service接口和实现类来模拟动态代理的应用场景
package com.mylifes1110.service;
public interface LandlordService {
void rent();
}
package com.mylifes1110.service.impl;
import com.mylifes1110.service.LandlordService;
public class LandlordServiceImpl implements LandlordService {
@Override
public void rent() {
System.out.println("签合同");
System.out.println("收款");
}
}
8.4 静态代理
如下是静态代理设计模式解决代理问题
- 静态代理流程,创建一个代理类并实现相同接口,创建实现类对象,在代理类中添加辅助功能并调用实现类对象核心方法,使得辅助功能和核心方法一起触发,完成代理
- 静态代理的问题
- 随着辅助功能的数量增加,代理类也会增加,导致代理类数量过多,不利于项目的管理。
- 多个代理类的辅助功能代码冗余,修改时,维护性差。
静态代理 |
---|
创建静态代理类
package com.mylifes1110.advice1;
import com.mylifes1110.service.LandlordService;
import com.mylifes1110.service.impl.LandlordServiceImpl;
/**
* @ClassName Proxy
* @Description 静态代理类
* @Author Ziph
* @Date 2020/7/19
* @Since 1.8
* @Version 1.0
*/
public class Proxy implements LandlordService {
private LandlordService landlordService = new LandlordServiceImpl();
@Override
public void rent() {
// 代理事件
System.out.println("发布消息");
System.out.println("看房子");
// 核心事件
landlordService.rent();
}
}
静态代理实现
package com.mylifes1110.advice1;
import org.junit.Test;
public class ProxyTest {
/**
* @MethodName proxyTest
* @Param []
* @Description 静态代理实现
* @Author Ziph
* @Date 2020/7/10
*/
@Test
public void proxyTest() {
new Proxy().rent();
}
/**
* 结果:
*
* 发布消息
* 看房子
* 签合同
* 收款
*/
}
8.5 JDK和CGLIB的选择
spring底层,包含了jdk代理和cglib代理两种动态代理生成机制。
基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理。如果配置true:,则用CGLIB代理
class DefaultAopProxyFactory{
// 该方法中明确定义了 JDK代理和CGLib代理的选取规则
// 基本规则是:目标业务类如果有接口则用JDK代理,没有接口则用CGLib代理
public AopProxy createAopProxy(){...}
}
8.6 JDK动态代理
JDK动态代理是JDK底层基于接口实现的,也就是说我们必须通过实现JDK动态代理的接口并覆盖方法来完成
package com.mylifes1110.advice2;
import com.mylifes1110.service.LandlordService;
import com.mylifes1110.service.impl.LandlordServiceImpl;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
/**
* @MethodName proxyTest
* @Param []
* @Description JDK动态代理实现
* @Author Ziph
* @Date 2020/7/10
*/
@Test
public void proxyTest() {
// 需要使用代理的目标
LandlordService landlordService = new LandlordServiceImpl();
// 匿名内部类
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理事件
System.out.println("发布消息");
System.out.println("看房子");
return method.invoke(landlordService, args);
}
};
// 动态构建代理类
LandlordService proxy = (LandlordService) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),
landlordService.getClass().getInterfaces(),
handler);
proxy.rent();
/**
* 结果:
*
* 发布消息
* 看房子
* 签合同
* 收款
*/
}
}
8.7 CGLIB动态代理
CGLIB动态代理是Spring底层基于继承父类实现的,也就是说我们必须通过继承所指定的父类并覆盖其方法来完成
package com.mylifes1110.advice3;
import com.mylifes1110.service.LandlordService;
import com.mylifes1110.service.impl.LandlordServiceImpl;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;
import java.lang.reflect.Method;
/**
* @ClassName ProxyTest
* @Description CGLIB动态代理实现
* @Author Ziph
* @Date 2020/7/19
* @Since 1.8
* @Version 1.0
*/
public class ProxyTest {
public static void main(String[] args) {
final LandlordService landlordService = new LandlordServiceImpl();
// 创建字节码增强对象
Enhancer enhancer = new Enhancer();
// 设置父类(等价于实现原始类接口)
enhancer.setSuperclass(landlordService.getClass());
// 设置回调函数(额外功能代码)
enhancer.setCallback(new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// 代理事件
System.out.println("发布消息");
System.out.println("看房子");
Object ret = method.invoke(landlordService, args);
return ret;
}
});
// 创建动态代理类
LandlordService proxy = (LandlordService) enhancer.create();
proxy.rent();
/**
* 结果:
*
* 发布消息
* 看房子
* 签合同
* 收款
*/
}
}
九、后处理器
9.1 后处理器的了解
- spring中定义了很多后处理器;
- 每个bean在创建完成之前 ,都会有一个后处理过程,即再加工,对bean做出相关改变和调整;
- spring-AOP中,就有一个专门的后处理器,负责通过原始业务组件(Service),再加工得到一个代理组件。
常用后处理器 |
---|
9.2 定义后处理器
/**
* 定义bean后处理器
* 作用:在bean的创建之后,进行再加工
*/
public class MyBeanPostProcessor implements BeanPostProcessor{
/**
* 在bean的init方法之前执行
* @param bean 原始的bean对象
* @param beanName
* @return
* @throws BeansException
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后处理器 在init之前执行~~~" + bean.getClass());
return bean;
}
/**
* 在bean的init方法之后执行
* @param bean postProcessBeforeInitialization返回的bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后处理器 在init之后执行~~~" + bean.getClass());
return bean;// 此处的返回是 getBean() 最终的返回值
}
}
9.3 配置后处理器
<!-- 配置后处理器,将对工厂中所有的bean声明周期进行干预 -->
<bean class="com.mylifes1110.beanpostprocessor.MyBeanPostProcessor"></bean>
9.4 Bean的生命周期
创建Bean对象 -> 构造方法 -> Setter方法注入属性、满足依赖 -> 后处理器前置过程 -> init初始化 -> 后处理器后置过程 -> 构建完成 -> 销毁
9.5 动态代理源码(了解)
// AbstractAutoProxyCreator是 AspectJAwareAdvisorAutoProxyCreator的父类
// 该后处理器类中的 wrapIfNecessary方法即动态代理生成过程
AbstractAutoProxyCreator#postProcessAfterInitialization(Object bean, String beanName){
if (!this.earlyProxyReferences.contains(cacheKey)) {
// 开始动态定制代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
记得关注我哦!拜拜,下期见!
仔细想想SpringAOP也不难嘛,面试没有必要慌的更多相关文章
- 丰富图文详解B-树原理,从此面试再也不慌
本文始发于个人公众号:TechFlow,原创不易,求个关注 本篇原计划在上周五发布,由于太过硬核所以才拖到了这周五.我相信大家应该能从标题当中体会到这个硬核. 周五的专题是大数据和分布式,我最初的打算 ...
- Codeforces Round #383 (Div. 2) D. Arpa's weak amphitheater and Mehrdad's valuable Hoses(分组背包+dsu)
D. Arpa's weak amphitheater and Mehrdad's valuable Hoses Problem Description: Mehrdad wants to invit ...
- codeforces 425E
题意:对于[l1, r1], [l2, r2]...[lm, rm]线段组成的一个集合S,我们定义f(S)为最大的不相交(没有任何公共点)线段数,现在给定n及k,n表示线段范围,即任何[li, ri] ...
- 【csp模拟赛4】基站建设 (station.cpp)
[题目描述] 小 Z 的爸爸是一位通信工程师,他所在的通信公司最近接到了一个新的通 信工程建设任务,他们需要在 C 城建设一批新的基站. C 城的城市规划做得非常好,整个城市被规整地划分为 8 行 8 ...
- 2017上海C++面试
今天参加了一次面试,觉得比较有意思,收获蛮多,简单的在这里总结下. 开始做了一道算法题,也就是算术运算表达式中的左括号和右括号的匹配,用c++写.我大概10分钟就写完了.其实以前一直想实现这个功能的, ...
- 2015年校园招聘12家IT公司面试体验
背景 2015年注定是一个不平凡的年头,作为一个应届毕业生,我也算是经历了工作上的大起大落.下面我先简单讲述一下自己的遭遇,然后根据自己亲身的面试经历,从一个学生的角度去谈谈自己对面试过的公司的一些看 ...
- java面试入门总结
最近正好有时间空下来,前一段时间本来打算呢,写一写阶段的总结,今天就来谈谈吧.作为一个java入门小白,之前就职于浙江大华,是通过大华10月份秋季招聘通过大华的面试. 浙江大华校招采用模式是先笔试.再 ...
- 2015年网易考拉海淘android面试
经朋友推荐,昨天下午去网易杭州公司参加了考拉海淘android客户端的面试.今天回忆一下面试题目,做个整理进行备案. 1.说说JVM垃圾回收机制. 1.1.画了JVM分代回收的图,大致说了下垃圾分代回 ...
- 深圳--博雅互动 Android面试打酱油归来
公司在TCL工业园E4,坐地到西丽站,那边在修路,不好走.B796公交站台在A出口的反方向,还要顺着施工的屏障打个弯,在西丽法院1上车.公司那边比较偏了,附近只有两趟公交.办公地点在10楼,出电梯就可 ...
随机推荐
- Charles 功能详解
Charles的功能有? 1 抓取http和https 网络封包(抓包) 2 Charles 的断点请求 通过断点修改参数 在指定接口打上断点 右键点击接口选择 breakpoints 然后 导航栏 ...
- WeChair项目Beta冲刺(4/10)
团队项目进行情况 1.昨日进展 Beta冲刺第四天 昨日进展: 前后端并行开发,项目按照计划有条不絮进行 2.今日安排 前端:扫码占座功能和预约功能并行开发 后端:扫码占座后端逻辑和预约功能逻辑 ...
- 如何解决jeecgBoot前端运行项目之后无法获取验证码的问题
我也是第一次接触这个开源项目,拿到项目之后,安装完环境和依赖,当我启动项目的时候,验证码却刷新不出来. 然后公司后端告诉我需要改两个接口,一个是public目录下的index.html和vue.con ...
- SpringMVC拦截器使用
源码地址 拦截器interceptor 拦截器是URL请求的第一道门,所有请求会先经过拦截器interceptor,然后再进入controller: 下面,记录一种通过注解方法拦截所有需要登录才能发起 ...
- Python3-hashlib模块-加密算法之安全哈希
Python3中的hashlib模块提供了多个不同的安全哈希算法的通用接口 hashlib模块代替了Python2中的md5和sham模块,使用这个模块一般分为3步 1.创建一个哈希对象,使用哈希算法 ...
- CentOS 7 下安装 MySQL 8.0
前言 本篇文章主要介绍在 CentOS 7 环境下安装 MySQL 8.0. 正文 1. 配置yum源 首先在 https://dev.mysql.com/downloads/repo/yum/ 找到 ...
- Python 简明教程 --- 11,Python 元组
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 软件工程的目标是控制复杂度,而不是增加复杂性. -- Dr. Pamela Zave 目录 我们在上 ...
- 硬件对同步的支持-TAS和CAS指令
目录 Test and Set Compare and Swap 使用CAS实现线程安全的数据结构. 现在主流的多处理器架构都在硬件水平上提供了对并发同步的支持. 今天我们讨论两个很重要的硬件同步指令 ...
- 2020年IDEA破解激活码永久
我想很多做开发的小伙伴和小编一样,和往常一样开机搬砖. 打开idea的时候,会收到一个个提示,也是idea许可证过期啦,需要重新激活! 那怎么办呢?我最近发现了一个相对稳定的激活码 . 亲测可用.现在 ...
- 暑假集训Day0
啊这 跟学长学的要写日记 希望到时候能写省选集训的总结 咳咳 今天上午做了一上午苦力好像让老苏夸了难以接受(年纪两百考到年级两千他居然没有干我) 上午搞卫生搞到了十点半………… 替女生拉包提东西了!! ...