模拟计算功能
public interface Calculator {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
package com.bijing.spring.proxy.impl;
import com.bijing.spring.proxy.Calculator;
/**
* @author 毕晶
* @date 2022/11/13 17:28
*/
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部,result:" + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部,result:" + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部,result:" + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部,result:" + result);
return result;
}
}
package com.bijing.spring.proxy.impl;
import com.bijing.spring.proxy.Calculator;
/**
* @author 毕晶
* @date 2022/11/13 17:28
*/
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
System.out.println("日志,方法:add,参数:" + i + "," + j);
int result = i + j;
System.out.println("方法内部,result:" + result);
System.out.println("日志,方法:add,结果:" + result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("日志,方法:sub,参数:" + i + "," + j);
int result = i - j;
System.out.println("方法内部,result:" + result);
System.out.println("日志,方法:sub,结果:" + result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志,方法:mul,参数:" + i + "," + j);
int result = i * j;
System.out.println("方法内部,result:" + result);
System.out.println("日志,方法:mul,结果:" + result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("日志,方法:div,参数:" + i + "," + j);
int result = i / j;
System.out.println("方法内部,result:" + result);
System.out.println("日志,方法:div,结果:" + result);
return result;
}
}
package com.bijing.spring.proxy;
import com.bijing.spring.proxy.impl.CalculatorImpl;
/**
* @author 毕晶
* @date 2022/11/13 20:37
*/
//需要和目标类实现同一个接口
public class CalculatorStaticProxy implements Calculator {
//要代理的对象
private CalculatorImpl target;
public CalculatorStaticProxy(CalculatorImpl target) {
this.target = target;
}
public CalculatorImpl getTarget() {
return target;
}
public void setTarget(CalculatorImpl target) {
this.target = target;
}
@Override
public int add(int i, int j) {
//附加功能由代理类中的代理方法实现
System.out.println("日志,方法:add,参数:" + i + "," + j);
//通过目标对象来实现核心业务逻辑
int result = target.add(i, j);
System.out.println("日志,方法:add,结果:" + result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("日志,方法:sub,参数:" + i + "," + j);
int result = target.sub(i, j);
System.out.println("日志,方法:sub,结果:" + result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志,方法:mul,参数:" + i + "," + j);
int result = target.mul(i, j);
System.out.println("日志,方法:mul,结果:" + result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("日志,方法:div,参数:" + i + "," + j);
int result = target.div(i, j);
System.out.println("日志,方法:div,结果:" + result);
return result;
}
}
package com.bijing.proxy;
import com.bijing.spring.proxy.CalculatorStaticProxy;
import com.bijing.spring.proxy.impl.CalculatorImpl;
import org.junit.jupiter.api.Test;
/**
* @author 毕晶
* @date 2022/11/13 20:46
*/
public class ProxyTest {
@Test
public void testProxy() {
CalculatorStaticProxy proxy = new CalculatorStaticProxy(new CalculatorImpl());
proxy.add(199, 1);
}
}
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这—个代理类来实现。
这就需要使用动态代理技术了。
package com.bijing.spring.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* @author 毕晶
* @date 2022/11/13 21:19
*/
public class ProxyFactory {
//创建目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxy() {
/**
* ClassLoader loader:指定加载动态生成代理类的类加载器
* Class[] interfaces:获取目标对象实现的所有的接口的class对象数组
* InvocationHandler h:设置代理类中的抽象方法如何重写
*/
ClassLoader classLoader = this.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
//代理对象的所有方法都会经过下列操作
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志,方法:" + method.getName() + ",参数:" + Arrays.toString(args));
//proxy表示代理对象,method表示要执行的方法,args表示要执行的方法的参数列表
Object result = method.invoke(target, args);
System.out.println("日志,方法:" + method.getName() + ",结果:" + result);
return result;
}
};
return Proxy.newProxyInstance(classLoader, interfaces, h);
}
}
package com.bijing.proxy;
import com.bijing.spring.proxy.Calculator;
import com.bijing.spring.proxy.ProxyFactory;
import com.bijing.spring.proxy.impl.CalculatorImpl;
import org.junit.jupiter.api.Test;
/**
* @author 毕晶
* @date 2022/11/13 20:46
*/
public class ProxyTest {
/**
* 动态代理有两种:
* 1.jdk代理,要求必须有接口,最终生成的代理类和目标类实现相同的接口,在com.sun.proxy包下,类名为$proxy2
* 2.cglib代理,最终生成的代理类会继承目标类,并且和目标类在相同的包下
*/
@Test
public void testProxy() {
// CalculatorStaticProxy proxy = new CalculatorStaticProxy(new CalculatorImpl());
// proxy.add(199, 1);
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());//在proxyFactory中指定了要代理的对象是CalculatorImpl
Calculator proxy = (Calculator) proxyFactory.getProxy();
proxy.add(99, 1);
}
}
AOP (Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个
不同方面的增强。
这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
横切关注点是针对目标对象来说的,通知是针对切面来说的。
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
封装通知方法的类
被代理的目标对象
向目标对象应用通知之后创建的代理对象,就是为目标对象创建的代理对象
这也是一个纯逻辑概念,不是语法定义的
把方法排成一排,每一个横向位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点
就是抽取横切关注点的位置

从目标对象中把非核心业务代码即横切关注点抽取出来,放在切面中,在切面中每一个横切关注点都是一个方法,又叫通知,把切面中的通知,通过切入点表达式套到连接点上

<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.3.23version>
dependency>
dependencies>
public interface Calculator {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
package com.bijing.spring.aop.annotation;
import org.springframework.stereotype.Component;
/**
* @author 毕晶
* @date 2022/11/13 17:28
*/
@Component
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部,result:" + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部,result:" + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部,result:" + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部,result:" + result);
return result;
}
}
package com.bijing.spring.aop.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author 毕晶
* @date 2022/11/14 12:00
* **
* 1.在切面中,需要通过指定的注解将方法标识为通知方法
* @Before:前置通知,在目标对象方法执行之前执行 .
* @After:后置通知,在目标对象方法的finally子句中执行 .
* @AfterReturning:返回通知,在目标对象方法返回值之后执行 .
* @AfterThrowing:异常通知,在目标对象方法的catch子句中执行 .
* **
* 2.切入点表达式:设置在标识通知的注解的value属性中
* execution(public int com.bijing.spring.aop.annotation.CalculatorImpl.add(int,int))
* execution(* com.bijing.spring.aop.annotation.CalculatorImpl.*(..))
* 第一个*表示任意的访问修饰符和返回值类型
* 第二个*表示类中的任意方法
* ..表示任意的参数列表
* 类的地方也可以使用*表示包下所有的类
* **
* 3.重用切入点表达式
* //@Pointcut声明一个公共的切入点表达式
* . @Pointcut("execution(* com.bijing.spring.aop.annotation.CalculatorImpl.*(..))")
* . public void pointCut() {}
* 使用方式: @After("pointCut()")
* **
* 4.获取连接点信息
* 在通知方法的参数位置,设置joinPoint类型的参数,就可以获取连接点所对应方法的信息
* //signature获取连接点所对应的方法的签名信息
* Signature signature = joinPoint.getSignature();
* //获取连接点所对应方法的参数
* Object[] args = joinPoint.getArgs();
* **
* 5.切面的优先级可以通过@Order注解的value属性来设置优先级,默认值为Integer的最大值
* @Order注解的value属性值越小,优先级越高 .
*/
@Component
@Aspect//将当前组件标识为切面组件
public class LoggerAspect {
@Pointcut("execution(* com.bijing.spring.aop.annotation.CalculatorImpl.*(..))")
public void pointCut() {
}
// @Before("execution(public int com.bijing.spring.aop.annotation.CalculatorImpl.add(int,int))")
@Before("execution(* com.bijing.spring.aop.annotation.CalculatorImpl.*(..))")
public void beforeAdviceMethod(JoinPoint joinPoint) {//JoinPoint表示连接点
//signature获取连接点所对应的方法的签名信息
Signature signature = joinPoint.getSignature();
//获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect,方法:" + signature.getName() + ",参数:" + Arrays.toString(args));
}
@After("pointCut()")
public void afterAdviceMethod(JoinPoint joinPoint) {
//signature获取连接点所对应的方法的签名信息
Signature signature = joinPoint.getSignature();
//获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect,方法:" + signature.getName() + ",执行完毕");
}
/* 在返回通知中,若要获取目标对象方法的返回值,
只需要通过@AfterReturnning注解的returning属性,
就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数*/
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result) {
//signature获取连接点所对应的方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:" + signature.getName() + ",结果:" + result);
}
/* 在异常通知中,若要获取目标对象方法的异常,
只需要通过@AfterThrowing注解的throwing属性,
就可以将通知方法的某个参数指定为接收目标对象方法的出现异常的参数*/
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void afterThrowingAdviceMethod(JoinPoint joinPoint, Exception exception) {
//signature获取连接点所对应的方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:" + signature.getName() + ",异常通知:" + exception);
}
@Around("pointCut()")
//环绕通知的返回值一定要和目标对象方法的返回值一致
public Object aroundAdviceMethod(ProceedingJoinPoint proceedingJoinPoint) {
Object result = null;
try {
System.out.println("环绕通知-->前置通知位置");
//标识目标对象方法的执行
result = proceedingJoinPoint.proceed();
System.out.println("环绕通知-->返回通知位置");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("环绕通知-->异常通知位置");
} finally {
System.out.println("环绕通知-->后置通知位置");
}
return result;
}
}
aop-annotation.xml
<context:component-scan base-package="com.bijing.spring.aop.annotation">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
作用

语法细节

@Pointcut("execution(* com.bijing.aop.annotation.*.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
@Before("com.bijing.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs()); System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}
获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参
@Before("execution(* com.bijing.spring.aop.annotation.CalculatorImpl.*(..))")
public void beforeAdviceMethod(JoinPoint joinPoint) {//JoinPoint表示连接点
//signature获取连接点所对应的方法的签名信息
Signature signature = joinPoint.getSignature();
//获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect,方法:" + signature.getName() + ",参数:" + Arrays.toString(args));
}
@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result) {
//signature获取连接点所对应的方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:" + signature.getName() + ",结果:" + result);
}
@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void afterThrowingAdviceMethod(JoinPoint joinPoint, Exception exception) {
//signature获取连接点所对应的方法的签名信息
Signature signature = joinPoint.getSignature();
System.out.println("LoggerAspect,方法:" + signature.getName() + ",异常通知:" + exception);
}
@Around("pointCut()")
//环绕通知的返回值一定要和目标对象方法的返回值一致
public Object aroundAdviceMethod(ProceedingJoinPoint proceedingJoinPoint) {
Object result = null;
try {
System.out.println("环绕通知-->前置通知位置");
//标识目标对象方法的执行
result = proceedingJoinPoint.proceed();
System.out.println("环绕通知-->返回通知位置");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("环绕通知-->异常通知位置");
} finally {
System.out.println("环绕通知-->后置通知位置");
}
return result;
}
AOPByAnnotationTest
为什么在bean文件中注入的是实现类,但是通过getBean()取出的时候却必须强制转化为接口类?
package com.bijing.spring.test;
import com.bijing.spring.aop.annotation.Calculator;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 毕晶
* @date 2022/11/14 14:02
*/
public class AOPByAnnotationTest {
@Test
public void testAOPByAnnotation() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("aop-annotation.xml");
/* 为什么在bean文件中注入的是实现类,但是通过getBean()取出的时候却必须强制转化为接口类?
如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。
若该目标对象没有实现任何接口,则创建一个Cglib代理,以目标对象子类的方式类实现代理.
使用Cglib代理的时候,通过getBean()取出的注入对象既可以是普通对象,也可以是接口,通过JDK动态代理就只能使用接口。*/
Calculator calculator = ioc.getBean(Calculator.class);
calculator.div(10, 1);
}
}
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序
使用@Order注解可以控制切面的优先级:
参考基于注解的AOP环境
<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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.bijing.spring.aop.xml">context:component-scan>
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.bijing.spring.aop.xml.CalculatorImpl.* (..))"/>
<aop:aspect ref="loggerAspect">
<aop:before method="beforeAdviceMethod" pointcut-ref="pointCut">aop:before>
<aop:after method="afterAdviceMethod" pointcut-ref="pointCut">aop:after>
<aop:after-returning method="afterReturningAdviceMethod" pointcut-ref="pointCut"
returning="result">aop:after-returning>
<aop:after-throwing method="afterThrowingAdviceMethod" pointcut-ref="pointCut"
throwing="exception">aop:after-throwing>
<aop:around method="aroundAdviceMethod" pointcut-ref="pointCut">aop:around>
aop:aspect>
aop:config>
<aop:config>
<aop:aspect ref="validateAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="pointCut">aop:before>
aop:aspect>
aop:config>
beans>