赞
踩
AOP 指面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
AOP 是 OOP(Object Oriented Programming)的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
AOP 底层通过动态代理实现
代理是一种常用的设计模式,通过一个对象 A 持有另一个对象 B ,可以使得 A 具有与 B 相同的行为,代替甚至增强 B 的工作
常用的动态代理技术有 JDK 代理和 cglib 代理,Spring 框架根据目标类是否实现接口决定采用哪种动态代理技术
JDK 代理基于接口实现,代理的目标对象必须实现某一接口
目标对象接口
- public interface TargetInterface {
- public void say();
- }
目标对象类
- public class Target implements TargetInterface {
- public void say() {
- System.out.println("喵");
- }
- }
增强类
- public class Advice {
- public void before() {
- System.out.println("前置增强...");
- }
-
- public void afterReturning() {
- System.out.println("后置增强...");
- }
- }
代理测试
- public class ProxyTest {
- public static void main(String[] args) {
- Target target = new Target();
- Advice advice = new Advice();
- TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- advice.before();
- Object invoke = method.invoke(target, args);
- advice.afterReturning();
- return invoke;
- }
- });
- proxy.say();
- }
- }
使用 Proxy.newProxyInstance() 方法创建一个动态代理对象,需要三个参数
invoke() 方法中有三个参数
在 invoke() 方法中,通过 method.invoke() 方法可以执行目标对象的相应方法,传入目标对象和方法参数,本质是反射,在 method.invoke() 前后还可以通过 advice 增强对象来对目标方法进行前置和后置增强
创建好的动态代理对象使用目标对象的接口接受
cglib 代理基于父类实现,该功能并非 Java 原生内容,但已经集成到 Spring 框架中,所以无需导入相关依赖
目标对象类
- public class Target {
- public void say() {
- System.out.println("喵");
- }
- }
增强类
- public class Advice {
- public void before() {
- System.out.println("前置增强...");
- }
-
- public void afterReturning() {
- System.out.println("后置增强...");
- }
- }
代理测试
- public class ProxyTest {
- public static void main(String[] args) {
- Target target = new Target();
- Advice advice = new Advice();
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(Target.class);
- enhancer.setCallback(new MethodInterceptor() {
- @Override
- public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
- advice.before();
- Object invoke = method.invoke(target, args);
- advice.afterReturning();
- return invoke;
- }
- });
- Target proxy = (Target) enhancer.create();
- proxy.say();
- }
- }
首先创建一个增强器 Enhancer
通过 setSuperclass 设置其父类,通过 setCallback 设置回调,传入方法拦截器 MethodInterceptor 并重写 intercept() 方法,在其中进行目标对象方法的执行和相应的增强,最后通过 create() 方法创建代理对象
Spring 的 AOP 底层通过上述两种动态代理的方式完成目标对象方法的增强
Target:目标对象
Proxy:代理对象
JoinPoint:连接点,指可以被增强的方法
Pointcut:切入点,指某个具体被增强的方法
Advice:通知(增强),对目标对象方法的具体增强内容
Aspect:切面,指 Pointcut 和 Advice 的结合
Weaving:织入,指把目标对象与通知结合创建代理对象的过程
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>5.1.5.RELEASE</version>
- </dependency>
-
- <dependency>
- <groupId>org.aspectj</groupId>
- <artifactId>aspectjweaver</artifactId>
- <version>1.9.7</version>
- </dependency>
- public class Target {
- public void say() {
- System.out.println("喵");
- }
- }
- public class MyAspect {
- public void before() {
- System.out.println("前置增强...");
- }
-
- public void afterReturning() {
- System.out.println("后置增强...");
- }
- }
- <bean id="target" class="Proxy.Target"/>
-
- <bean id="myAspect" class="Proxy.MyAspect"/>
命名空间中加入
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation 中加入
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
配置织入
- <aop:config>
- <aop:aspect ref="myAspect">
- <aop:before method="before" pointcut="execution(public void Proxy.Target.say())"/>
- <aop:after-returning method="afterReturning" pointcut="execution(public void Proxy.Target.say())"/>
- </aop:aspect>
- </aop:config>
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(locations = "classpath:applicationContext.xml")
- public class AOPTest {
- @Autowired
- private Target target;
-
- @Test
- public void test() {
- target.say();
- }
- }
- @Component("target")
- public class Target {
- public void say() {
- System.out.println("喵");
- }
- }
- @Component("myAspect")
- public class MyAspect {
- public void before() {
- System.out.println("前置增强...");
- }
-
- public void afterReturning() {
- System.out.println("后置增强...");
- }
- }
- @Component("myAspect")
- @Aspect
- public class MyAspect {
- @Before("execution(public void Proxy.Target.say())")
- public void before() {
- System.out.println("前置增强...");
- }
-
- @AfterReturning("execution(public void Proxy.Target.say())")
- public void afterReturning() {
- System.out.println("后置增强...");
- }
- }
- <context:component-scan base-package="Proxy"/>
-
- <aop:aspectj-autoproxy/>
通知类型 | 标签 | 注解 | 说明 |
---|---|---|---|
前置通知 | <aop:before> | @Before | 指定增强方法在切入点之前执行 |
后置通知 | <aop:after-returning> | @AfterReturning | 指定增强方法在切入点之后执行 |
环绕通知 | <aop:around> | @Around | 指定增强方法在切入点之前和之后执行 |
异常抛出通知 | <aop:throwing> | @AfterThrowing | 指定增强方法在出现异常时执行 |
最终通知 | <aop:after> | @After | 指定增强方法在最终执行 |
对于环绕通知较为特殊,对于增强方法的编写,需如下格式
- public Object around(ProceedingJoinPoint pjp) throws Throwable {
- System.out.println("环绕前增强");
- Object proceed = pjp.proceed();
- System.out.println("环绕后增强");
- return proceed;
- }
ProceedingJoinPoint 可以理解为切入点,进行相应的织入后,执行目标方法相当于执行该方法,该方法中通过 ProceedingJoinPoint 的 preceed() 方法,执行目标方法,但在目标方法前后进行环绕增强,最终将目标方法的返回值,再次返回
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- <aop:config>
- <aop:aspect ref="aspect">
- <aop:pointcut id="myPointcut" expression="execution(public void Proxy.Target.say())"/>
- <aop:before method="before" pointcut-ref="myPointcut"/>
- <aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
- </aop:aspect>
- </aop:config>
通过 <aop:pointcut> 抽取切点表达式,在需要使用的地方通过 pointcut-ref 引用即可,该标签需放在 <aop:config> 标签内部
- @Component("myAspect")
- @Aspect
- public class MyAspect {
- @Before("MyAspect.pointcut()")
- public void before() {
- System.out.println("前置增强...");
- }
-
- @AfterReturning("pointcut()")
- public void afterReturning() {
- System.out.println("后置增强...");
- }
-
- @Pointcut("execution(public void Proxy.Target.say())")
- public void pointcut() {}
-
- }
通过 @pointcut 注解抽取切点表达式,在需要使用的地方通过方法名或类名.方法名引用即可
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。