AOP(面向切面编程)是一种编程范式,它允许开发者在不修改现有代码的情况下,通过定义“切面”来横切多个模块,从而实现对这些模块的某些行为进行统一管理和控制。 AOP的核心概念包括切面(Aspect)、连接点(Join point)、通知(Advice)和切点(Pointcut)。
横切关注点:跨越应用程序多个模块的方法和功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。例如,事务管理、日志记录、权限控制、性能监控等都可以看作是横切关注点。
- 切面:横切关注点 被模块化 的特殊对象。即,它是一个类。
- 连接点:在程序执行过程中某个特定的点,如方法调用或异常处理时。在Spring AOP中,一个连接点总是代表一个方法的执行。
- 通知:在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”、“before”和“after”等,它们定义了在连接点上应该执行的具体操作。
- 切点:匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行。
AOP的主要优点包括:
- 代码模块化:将横切关注点从业务逻辑中分离,使代码结构更清晰,模块划分更合理。例如,将日志记录、权限控制等功能独立出来,业务代码更专注于核心业务。
- 提高代码复用性:横切关注点的代码可以在多个地方重复使用,无需在每个相关位置重复编写。比如,一个通用的事务处理逻辑可以应用于多个业务方法。
- 增强可维护性:当横切关注点的逻辑需要修改时,只需在切面中进行修改,而不必在众多的业务方法中逐一修改。
AOP 技术的缺点:
- 增加复杂性:对于初次接触和理解的开发者来说,AOP 的概念和实现机制可能较为复杂,增加了学习成本。
- 调试困难:由于横切逻辑的织入是在运行时动态进行的,可能会增加调试的难度,特别是在出现问题时定位和排查故障。
AOP
aop的实现原理
AOP的实现原理通常依赖于动态代理技术。动态代理技术可以分为两类:基于接口的动态代理和基于类的动态代理。
基于接口的动态代理:通过Java的java.lang.reflect.Proxy类和InvocationHandler接口实现。
基于类的动态代理:通常使用第三方库,如CGLIB,它通过继承的方式在运行时创建代理类。
AOP利用动态代理的设计模式。
AOP底层默认jdk动态代理,如果没有接口使用cglib动态代理。
在程序运行期间,不修改源码对已有方法进行增强。
减少重复代码 提高开发效率 维护方便
应用场景
常用于日志记录,性能统计,安全控制,事务处理,异常处理等等。
代理模式的分类
为什么要学习代理模式?因为这就是SpringAop的底层!
静态代理
角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象的人
package com.lx.pojo;
// 租房
public interface Rent {
// 出租
void rent();
}
package com.lx.pojo;
// 房东,往外租房子
public class Landlord implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
// 到这儿,我们就找到房子了
// 然而现实情况是很难直接找到房东,通常都是通过中介来租房子
package com.lx.pojo;
public class Client {
public static void main(String[] args) {
Landlord landlord = new Landlord();
// 房东要出租房子
landlord.rent();
}
}
package com.lx.pojo;
public class Client {
public static void main(String[] args) {
Landlord landlord = new Landlord();
Proxy proxy = new Proxy(landlord);
// 通过代理找到 房东要出租房子
// 代理,中介帮房东租房子,但是代理角色一般会有一些附属操作
proxy.rent();
}
}
// 通过代理不仅可以帮房东租房子,还可以做很多其他的附属操作
package com.lx.pojo;
public class Proxy implements Rent {
private Landlord landlord;
public Proxy() {
}
public Proxy(Landlord landlord) {
this.landlord = landlord;
}
@Override
public void rent() {
seeHouse();
landlord.rent();
fare();
signContract();
}
// 看房
// 这个只有中介能做,房东只有几套房子,而中介会有很多房子
public void seeHouse(){
System.out.println("中介带你看房");
}
// 收中介费
public void fare(){
System.out.println("收中介费");
}
// 签租赁合同
public void signContract(){
System.out.println("签合同");
}
}
代理模式的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共业务就交给代理角色!实现了业务的分工
- 公共业务发生拓展的时候,方便集中管理
缺点:
- 一个真实角色就会产生一个代理角色。代码量会翻倍,开发效率会变低
静态代理的第二个例子
package com.lx.service;
public interface UserService {
void add();
void delete();
void update();
void query();
}
package com.lx.service;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
package com.lx.service;
import java.time.LocalDateTime;
public class UserServiceProxy implements UserService{
private UserServiceImpl userServiceImpl;
public UserServiceProxy() {
}
public UserServiceProxy(UserServiceImpl userServiceImpl) {
this.userServiceImpl = userServiceImpl;
}
@Override
public void add() {
getLocalDateTime();
userServiceImpl.add();
}
@Override
public void delete() {
getLocalDateTime();
userServiceImpl.delete();
}
@Override
public void update() {
getLocalDateTime();
userServiceImpl.update();
}
@Override
public void query() {
getLocalDateTime();
userServiceImpl.query();
}
// 现在增加一个日志功能,检测方法是什么时间开始执行的
public void getLocalDateTime(){
System.out.println(LocalDateTime.now());
}
}
// 如果要增加日志功能可以在impl中直接添加代码
// 但是改动原有的业务代码,在公司中是大忌
// 我们就可以用代理来实现
package com.lx.pojo;
import com.lx.service.UserServiceImpl;
import com.lx.service.UserServiceProxy;
public class Client {
public static void main(String[] args) {
UserServiceImpl userServiceImpl = new UserServiceImpl();
UserServiceProxy userServiceProxy = new UserServiceProxy(userServiceImpl);
// 2023-12-09T15:41:54.587
// 增加了一个用户
userServiceProxy.add();
动态代理
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的
- 动态代理可以分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口 — jdk动态代理 【我们在这里使用】
- 基于类 — cglib
- java字节码实现 — javasist
需要了解两个类:
Proxy(代理),InvocationHandler(调用处理程序)
动态代理的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共就交给代理角色!实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
package com.lx.demo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
// private Rent rent;
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),
this);
}
// 处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 动态代理的本质,就是使用反射机制实现!
seeHouse();
Object result = method.invoke(target, args);
return result;
}
public void seeHouse(){
System.out.println("中介带看房子");
}
}
package com.lx.demo;
import com.lx.service.UserService;
import com.lx.service.UserServiceImpl;
public class Client {
public static void main(String[] args) {
// 真实角色
// Host host = new Host();
//
代理角色:现在没有
// ProxyInvocationHandler pih = new ProxyInvocationHandler();
//
// // 通过调用程序处理角色来处理我们要调用的接口对象!
// pih.setTarget(host);
//
// Rent proxy = (Rent) pih.getProxy(); // 这里的proxy就是动态生成的,我们并没有写
//
// // 中介带看房子
// //房东要出租房子!
// proxy.rent();
UserServiceImpl host = new UserServiceImpl();
// 代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
// 通过调用程序处理角色来处理我们要调用的接口对象!
pih.setTarget(host);
UserService proxy = (UserService) pih.getProxy(); // 这里的proxy就是动态生成的,我们并没有写
// 中介带看房子
//增加了一个用户
proxy.add();
}
}
aop的实现方式一 使用spring的api接口
主要spring的api接口实现
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean-->
<bean id="userService" class="com.lx.service.UserServiceImpl"/>
<bean id="log" class="com.lx.log.Log"/>
<!-- 配置aop-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.lx.service.*.*(..))"/>
<!-- 执行环绕增加-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
</aop:config>
</beans>
package com.lx.service;
public interface UserService {
void add();
void insert();
void delete();
void select();
}
package com.lx.service;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void insert() {
System.out.println("新增了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void select() {
System.out.println("查询了一个用户");
}
}
package com.lx.log;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
*
*/
public class Log implements AfterReturningAdvice, MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName() + "的" + method.getName() + "被执行了");
}
/**
*
* @param o 返回值
* @param method 要执行的目标对象的方法
* @param objects 参数
* @param o1 目标对象
* @throws Throwable
*/
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("执行了" + method.getName() + "方法,返回结果为:" + o);
}
}
import com.lx.service.UserService;
import com.lx.service.UserServiceImpl;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
// com.lx.service.UserServiceImpl的add被执行了
//增加了一个用户
//执行了add方法,返回结果为:null
userService.add();
}
}
aop的实现方式二 自定义类来实现aop
主要是切面定义
<!--开启注解扫描包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--创建日志的对象: 增强的对象-->
<bean id="log" class="com.itheima.log.TestLog"></bean>
<!--aop的配置-->
<aop:config>
<!--切入点配置,对连接点的定义
expression: 切入点表达式
execution: 固定单词
* com.itheima.service.impl.*.*(..) :拦截的是com.itheima.service.impl包中的所有类所有的方法,任意参数,任意返回值
第一个* : 任意返回值
com.itheima.service.impl: 固定的包名称
第二个*: 包中的任意类
第三个* : 类中的任意方法名称
(..):方法任意参数,任意类型,任意个数,任意顺序
* com.itheima.service..*.*(..) :
两个连续的 . : 当前包及其子包都包含
-->
<!--配置切面
切面= 切入点 + 增强
ref="log": 关联增强对象
-->
<aop:aspect ref="log">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--织入-->
<!--前置增强-->
<aop:before method="before" pointcut-ref="pointcut"></aop:before>
<!--后置增强-->
<!--<aop:after-returning method="afterReturning" pointcut-ref="pointcut"></aop:after-returning>-->
<!--最终增强-->
<!--<aop:after method="after" pointcut-ref="pointcut"></aop:after>-->
<!--异常增强-->
<!--<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"></aop:after-throwing>-->
<!--环绕增强-->
<!--<aop:around method="around" pointcut-ref="pointcut"></aop:around>-->
</aop:aspect>
</aop:config>
/**
*
* 记录日志 -- 增强功能
* aop的增强类型
* 前置增强
* 后置增强
* 异常增强
* 最终增强
*
* 环绕增强= 前置增强 + 后置增强 + 异常增强 + 最终增强
*
* 记录什么时间,访问了什么类中的什么方法
* @author 黑马程序员
* @Company http://www.ithiema.com
* @Version 1.0
*/
public class TestLog {
/**
* 前置增强
*/
public void before(JoinPoint joinPoint){
System.out.println(new Date());
// 获取你执行时哪个类
// 获取目标对象
Object o = joinPoint.getTarget();
// 获取对象的名称
System.out.println(o.getClass().getName());
// 执行的哪个方法
// 获取方法对象
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
System.out.println(methodName);
System.out.println("前置增强");
}
public void afterReturning(){
System.out.println("后置增强");
}
public void after(){
System.out.println("最终增强");
}
public void afterThrowing(){
System.out.println("异常增强");
}
/**
* JoinPoint: 连接点对象
* ProceedingJoinPoint: 连接点对象, 只在环绕增强中使用
* 可以执行真实对象的方法,可以返回值
* @param joinPoint
*/
public void around(ProceedingJoinPoint joinPoint){
try {
System.out.println("前置增强");
//执行真实的对象的方法
joinPoint.proceed();
System.out.println("后置增强");
} catch (Throwable throwable) {
System.out.println("异常增强");
throwable.printStackTrace();
} finally {
System.out.println("最终增强");
}
}
}
aop的实现方式三 注解实现
<context:component-scan base-package="com.itheima"/>
<!-- 开启注解支持! jdk默认 cglib -->
<aop:aspectj-autoproxy/>
/**
*
* 记录日志 -- 增强功能
* aop的增强类型
* 前置增强
* 后置增强
* 异常增强
* 最终增强
*
* 环绕增强= 前置增强 + 后置增强 + 异常增强 + 最终增强
*
* 记录什么时间,访问了什么类中的什么方法
*
* @Aspect: 该类为切面类
* 切面 = 切入点 + 增强
* @author 黑马程序员
* @Company http://www.ithiema.com
* @Version 1.0
*/
@Component
@Aspect
public class TestLog {
/**
* 切入点
*/
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
public void pointcut(){}
/**
* 前置增强
*/
// @Before() 中也可以直接使用 execution(* com.itheima.service.impl.*.*(..))
// @Before("pointcut()")
public void before(JoinPoint joinPoint){
System.out.println(new Date());
// 获取你执行时哪个类
// 获取目标对象
Object o = joinPoint.getTarget();
// 获取对象的名称
System.out.println(o.getClass().getName());
// 执行的哪个方法
// 获取方法对象
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
System.out.println(methodName);
System.out.println("前置增强");
}
// @AfterReturning("pointcut()")
public void afterReturning(){
System.out.println("后置增强");
}
// @After("pointcut()")
public void after(){
System.out.println("最终增强");
}
// @AfterThrowing("pointcut()")
public void afterThrowing(){
System.out.println("异常增强");
}
/**
* JoinPoint: 连接点对象
* ProceedingJoinPoint: 连接点对象, 只在环绕增强中使用
* 可以执行真实对象的方法,可以返回值
* @param joinPoint
*/
@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint){
try {
System.out.println("前置增强");
//执行真实的对象的方法
joinPoint.proceed();
System.out.println("后置增强");
} catch (Throwable throwable) {
System.out.println("异常增强");
throwable.printStackTrace();
} finally {
System.out.println("最终增强");
}
}
}
其实,集体评议往往会埋没人才。“歪瓜裂枣”很多,我们的专家要识别他特殊能力的一面就行,也不用全面评价一个人,“不拘一格降人才”。比如,清华大学数学系主任熊庆来让只有初中学历的华罗庚破格进入清华大学,开启了华罗庚高水平数学的研究生涯;罗家伦当清华校长时,录取了数学成绩只有15分的钱钟书,成就了一位文学大师。初始职级,在校园招聘时可以定一次,在与优秀新员工喝咖啡时也可以再定一次,我们直接授权这批专家。
任正非