TIP
AOP是Spring框架的两大特性之一,面向切面编程,实现原理是代理模式,有点不太理解代理模式,理一理。
# 什么是代理模式
代理模式,简单的来说就是中转站。例如省长要开会,不会把我们老百姓叫过去,而是市长过去,市长了解之后又给县长开;一个人要买房,然后找中介,卖方的老板并不直接与买家交流,而是通过中介传达意愿交易,最终把房卖掉;媒婆(并不一定女的),通常都是两家都比较熟悉的人,要结婚办事,一些不好说的话,就是通过媒婆去传达;vpn上网,本地连接网络代理服务器,不直接与互联网进行数据交换,而是通过代理。
# 情景:数学计算器
有两个要求
执行加减乘除运算
日志:在程序执行期间追踪正在发生的活动
需要实现加减乘除四个方法,并且在程序运行的过程需要打印日志(调用方法名,传递的参数,计算结果等)
接下来用以下各种各种方式实现。
# 普通实现
public class App
{
public static void main( String[] args )
{
MathI mathI = new MathImpl();
int result = mathI.sub(1,2);
System.out.println(result);
}
}
public interface MathI {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
public class MathImpl implements MathI{
@Override
public int add(int i, int j) {
System.out.println("method:add,arguments:"+i+","+j);
int result = i + j;
System.out.println("method:add,result:"+result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("method:sub,arguments:"+i+","+j);
int result = i - j;
System.out.println("method:sub,result:"+result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("method:mul,arguments:"+i+","+j);
int result = i * j;
System.out.println("method:mul,result:"+result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("method:div,arguments:"+i+","+j);
int result = i / j;
System.out.println("method:div,result:"+result);
return result;
}
}
一个计算接口中,定义了加减乘除四个方法,计算器实现计算类,并且在运行的时候打印了所调用的方法,传入的参数,计算的结果,每个方法体里面的结构都是差不多的,只是调用不同的方法,结果,标识有些不同。业务代码与非业务代码也混到了一起,显得混乱,我看着还挺整齐的。
# 静态代理实现
public class App
{
public static void main( String[] args )
{
ProxyUtil proxy = new ProxyUtil(new MathImpl());
int result = proxy.sub(1,2);
System.out.println(result);
}
}
public interface MathI {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
public class MathImpl implements MathI{
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
public class Proxy implements MathI{
public MathImpl mathImpl;
public Proxy(MathImpl mathImpl) {
this.mathImpl = mathImpl;
}
@Override
public int add(int i, int j) {
System.out.println("method:add,arguments:"+i+","+j);
int result = mathImpl.add(i,j);
System.out.println("method:add,result:"+result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("method:sub,arguments:"+i+","+j);
int result = mathImpl.sub(i,j);
System.out.println("method:sub,result:"+result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("method:mul,arguments:"+i+","+j);
int result = mathImpl.mul(i,j);
System.out.println("method:mul,result:"+result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("method:div,arguments:"+i+","+j);
int result = mathImpl.div(i,j);
System.out.println("method:div,result:"+result);
return result;
}
}
这种实现方式看起来并没有改进多少,相反代码更加多了,但是使MathImpl的业务代码和非业务代码隔离了,也算是进步了吧。
# JDK动态代理
public class App
{
public static void main( String[] args )
{
ProxyUtil proxy = new ProxyUtil(new MathImpl());
MathI math = (MathI) proxy.getProxy();
int result = math.add(1,2);
System.out.println(result);
}
}
public interface MathI {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
public class MathImpl implements MathI{
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ProxyUtil {
public MathImpl mathImpl;
public ProxyUtil(MathImpl mathImpl) {
this.mathImpl = mathImpl;
}
public Object getProxy() {
//第一个参数类加载器。用来加载代理对象所属类。
ClassLoader loader = this.getClass().getClassLoader();
//第二个参数interfaces,目标对象实现所有的接口的class对象。
Class[] interfaces = mathImpl.getClass().getInterfaces();
//第三个参数代理对象如何实现目标对象的方法。
return Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method:"+method.getName()+",args:"+ Arrays.toString(args));
Object obj = method.invoke(mathImpl,args);
System.out.println("method:"+method.getName()+",result:" + obj.toString());
return obj;
}
});
}
}
# cglib动态代理
CGlib动态代理和JDK动态代理相区别的地方就是,CGlib必须要包含继承,JDK必须包含接口实现。
# AspectJ实现
AspectJ并不是Spring自带的,是实现AOP的最好实现方式。
首先在pom.xml中添加以下依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
然后再创建applicationContext.xml,内容如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启aspectJ自动代理-->
<aop:aspectj-autoproxy />
<context:component-scan base-package="org.maven.spring">
</context:component-scan>
</beans>
public class App
{
public static void main( String[] args )
{
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
MathI mathI = ac.getBean("mathImpl",MathI.class);
int result = mathI.add(1,2);
System.out.println(result);
}
}
public interface MathI {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
@Component
public class MathImpl implements MathI{
@Override
public int add(int i, int j) {
int result = i + j;
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
return result;
}
}
//标识为一个切面
@Component
@Aspect
public class MyloggerAspect {
//切面里面的方法就叫做通知
//@Before将方法指定为前置通知,里面些切入表达式
//通知需要关注切入点
//当通知执行时就会把方法的一些信息添加到JoinPoint中
@Before(value = "execution(* org.maven.spring.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
//获取方法的参数
Object[] args = joinPoint.getArgs();
//getSignation将切面中的名字进行封装
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("method:"+methodName+",args:"+ Arrays.toString(args));
}
//返回通知
//可用returning设置接受方法的变量名
//在方法的参数中也应该改添加上
@AfterReturning(value = "execution(* org.maven.spring.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint , Object result) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println("method:" + methodName+",result:" + result);
}
}
# 切入点表达式
TIP
通常的形式
execution(public int org.maven.spring.MathImpl.add(int,int))
可以改写成
execution(* org.maven.spring.*.*(..))
第一个代表任意返回值任意类型 第二个代表此包下任意类 第三个*代表任意方法 两个点代表任意参数
# 切入点简化方式
TIP
@Pointcut(value = "execution(* org.maven.spring..(..))") public void proxy(){}
@Before(value="proxy()") public void auto(){}
# 五种通知类型
- @Before作用于方法执行之前
TIP
@Before(value = "execution(* org.maven.spring.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
//获取方法的参数
Object[] args = joinPoint.getArgs();
//getSignation将切面中的名字进行封装
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("method:"+methodName+",args:"+ Arrays.toString(args));
}
- @After作用于finally语句块
TIP
@After(value = "execution(* org.maven.spring.*.*(..))")
public void after() {
System.out.println("后置通知");
}
- @AfterReturning作用于方法执行之后
TIP
@AfterReturning(value = "execution(* org.maven.spring.*.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint , Object result) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println("method:" + methodName+",result:" + result);
}
- @AfterThrowing作用于catch块
TIP
//可捕捉是什么错误,用throwing
@AfterThrowing(value = "execution(* org.maven.spring.*.*(..))",throwing = "e")
public void afterThrowing(Exception e) {
System.out.println("有异常"+e);
}
- @Around可实现以上四种
TIP
@Around(value = "execution(* org.maven.spring.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
//前置通知
result = joinPoint.proceed();//执行方法
//返回通知
return result;
} catch (Throwable e) {
//异常通知
} finally {
//后置通知
}
return -1;
}
# spring框架之xml实现
applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.maven.spring"></context:component-scan>
<aop:config>
<aop:aspect ref="myloggerAspect">
<aop:before method="before" pointcut="execution(* org.maven.spring.*.*(..))"/>
<aop:after-returning method="afterReturning"
pointcut="execution(* org.maven.spring.*.*(..))"
returning="result"
/>
</aop:aspect>
</aop:config>
</beans>
除了切面代码有所改动,其余与AspectJ实现相同。
@Component
public class MyloggerAspect {
public void before(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("method:"+name+",args:"+Arrays.toString(args));
}
public void afterReturning(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName();
System.out.println("method:"+name+",result:"+result);
}
}