前言
本文不仅介绍Spring中AOP的几种实现方式。还整体介绍一下:静态代理、JDK API动态代理和CGLIB动态代理
文中示例的代码地址:
GitHub | https://github.com/Web-Learn-GSF/Java_Learn_Examples |
---|---|
父工程 | Java_Framework_Spring |
静态代理、动态代理*2
他人文章参考
这三个示例可以直接看文章:https://segmentfault.com/a/1190000011291179#item-4
自己编写示例
下边示例是自己写的,有空再补充吧:
示例 | 在上述工程的具体Module里面 |
---|---|
静态代理 | AOP_0_Static_Proxy |
JDK API动态代理 | 暂无 |
CGLIB动态代理 | 暂无 |
三种方式优缺点对比
静态代理:
特点:实现简单,只需要代理对象对目标对象封装,即可实现功能增强。
缺点:静态代理只能为一个目标对象服务,目标对象过多,就会产生很多的代理类
这个缺点存疑。如果将目标对象实例化在代理类中,即一个静态代理类代理一个目标对象,那肯定是目标对象越多,代理类就越多。但如果通过在代理对象中定义set方法,就可以实现:一个代理类代理同一个接口下的多个目标对象,就没有这个缺点了。
动态代理 | JDK API:
- 特点:动态代理必须实现InvocationHandler接口,且要求目标对象有接口实现。通过反射代理方法,实现对目标对象的增强
- 缺点:目标对象必须要有接口,不然无法应用
动态代理 | CGLIB:
- 特点:无需目标对象有接口实现,通过生成类字节码实现代理,比反射稍快,不存在性能问题。
- 缺点:代理类需要继承目标对象,即目标对象不能被final修饰
Spirng中实现AOP
Spring中实现AOP有如下几种方式:
实现方式 | 在上述工程中的Module位置 |
---|---|
xml + 实现Spring的API | AOP_1_Xml_SpringAPI |
xml + 自定义类 | AOP_2_Xml_CustomClass |
xml + 注解 + 自定义类 | AOP_3_Xml_Annotation |
注解 + 自定义类 | 暂空 |
下边展示每种实现方式里面的关键部分代码。
xml + 实现Spring的API
public class LogAfterMethod implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("AOP后置通知:" + "在" + target.getClass().getName() + "的" + method.getName() + "方法调用后执行。目标对象-方法-参数,都可以获取到");
}
}
public class LogBeforeMethod implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("AOP前置通知:" + "在" + target.getClass().getName() + "的" + method.getName() + "方法调用前执行。目标对象-方法-参数,都可以获取到");
}
}
<?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
http://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="GSF.Example.Service.UserServiceImpl" />
<bean id="logBefore" class="GSF.Example.Log.LogBeforeMethod" />
<bean id="logAfter" class="GSF.Example.Log.LogAfterMethod"/>
<!-- Aop的设置-->
<aop:config>
<!-- 切入点-->
<aop:pointcut id="pointcut" expression="execution(* GSF.Example.Service.UserServiceImpl.*(..))"/>
<!-- 执行前置通知-->
<aop:advisor advice-ref="logBefore" pointcut-ref="pointcut" />
<aop:advisor advice-ref="logAfter" pointcut-ref="pointcut" />
</aop:config>
</beans>
xml + 自定义类
package GSF.Example.Log;
public class CustomLogClass {
public void before(){
System.out.println("---------基于XML自定义类方式实现,前置通知:方法执行前---------");
}
public void after(){
System.out.println("---------基于XML自定义类方式实现,后置通知:方法执行前---------");
}
}
<?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
http://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="GSF.Example.Service.UserServiceImpl" />
<bean id="customLogClass" class="GSF.Example.Log.CustomLogClass" />
<!--Aop的设置-->
<aop:config>
<!-- 切入点-->
<aop:aspect ref="customLogClass">
<aop:pointcut id="pointcut" expression="execution(* GSF.Example.Service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut" />
</aop:aspect>
</aop:config>
</beans>
xml + 注解 + 自定义类
package GSF.Example.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationClass {
@Before("execution(* GSF.Example.Service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------基于注解方式实现,前置通知:方法执行前---------");
}
@After("execution(* GSF.Example.Service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------基于注解方式实现,后置通知:方法执行后---------");
}
@Around("execution(* GSF.Example.Service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable{
System.out.println("环绕通知:环绕前");
System.out.println(jp.getSignature());
// 执行目标方法
Object proceed = jp.proceed();
System.out.println(proceed);
System.out.println("环绕通知:环绕后");
}
}
<?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
http://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="GSF.Example.Service.UserServiceImpl" />
<bean id="annotationPointcut" class="GSF.Example.Log.AnnotationClass" />
<!-- 自动代理 -->
<aop:aspectj-autoproxy/>
</beans>
注解 + 自定义类
在上述:xml+注解+自定义类的实现方式中,xml文件的作用已经弱化为只有:注册bean + 开启允许注解实现AOP
只需要通过注解取代上述两种方法,就可以抛弃xml文件,完成仅:注解+自定义类实现AOP。
有空再写。
理解AOP中的概念
通过该参考文章,理解AOP中的相关概念:https://www.cnblogs.com/aduner/p/14656427.html
接下来相关概念的解释,也都用上述文章中的示例来讲。
概念 | 连接点
- Spring是方法级的AOP,一般对某个方法进行增强。选择的这个方法就是连接点的意思
- 这里选择的切入点就是:Landlord类的service()方法
@Component
public class Landlord {
public void service() {
System.out.println("签合同");
System.out.println("收钱");
}
}
概念 | 切面
- 切面可以理解一个拦截器,在切面上,我们对
连接点
的前边、后边进行方法增强。 - 切面对应一个类,也是Spring中的一个Bean
- 这里选择的切面就是Broker类,通过@Aspect注解定义该类为一个切面
@Component
@Aspect
class Broker {
@Before("execution(* com.aduner.demo03.pojo.Landlord.service())")
public void before(){
System.out.println("带租客看房");
System.out.println("谈钱");
}
@After("execution(* com.aduner.demo03.pojo.Landlord.service())")
public void after(){
System.out.println("给钥匙");
}
}
概念 | 切入点
切面中的方法都对一个连接点进行增强,有些重复的代码。可以定义一个切入点。
切面中的方法想要对某个切入点进行增强,就使用该切入点
上述代码可以修改为:
@Component
@Aspect
class Broker {
// 切入点
@Pointcut("execution(* com.aduner.demo03.pojo.Landlord.service())")
public void pointcut() {
}
// 使用上述切入点
@Before("pointcut()")
public void before() {
System.out.println("带租客看房");
System.out.println("谈钱");
}
@After("pointcut()")
public void after() {
System.out.println("给钥匙");
}
}
概念 | 通知、目标、代理、横切关注点
- 通知:上述代码中已经体现。before()方法就是一个前置通知(通过注解@Before赋予该方法前置通知的功能)
- 目标:Spring的AOP是对方法进行增强,被增强的方法叫做:连接点。那连接点所属的类就是目标
- 代理:Spring AOP的实现是动态代理,会有一个代理对象,代码中没有体现,但这个概念好理解
- 横切关注点:又是一个宏观的概念。日志、安全、权限都可以认为是横切关注点。示例中的横切关注点可以理解为:租房的准备工作
其余问题
多个切面:如果不同切面的切点相同,那就有多个切面,需要规定每个切面的执行顺序
通知Advice的类别(5种):
通知类型 | 连接点 | 实现接口 |
---|---|---|
前置通知 | 方法前 | org.springframework.aop.MethodBeforeAdvice |
后置通知 | 方法后 | org.springframework.aop.AfterReturningAdvice |
环绕通知 | 方法前后 | org.springframework.aop.MethodInterceptor |
异常抛出通知 | 方法抛出异常 | org.springframework.aop.ThrowsAdvice |
引介通知 | 类中增加新的方法属性 | org.springframework.aop.IntroductionInterceptor |