什么是AOP
AOP作用及其优势
举例说明:
比如现在开发当中我们的业务层有三个业务方法:
save(user)、update(user)、delete(id),而我们现在想对这三个方法进行功能增强,就是让这三个方法增加一个记录日志的功能,传统方式就是在对应的业务方法上加上日志的逻辑代码就可以了,如下图:
但是现在问题出现了,如果后期我们要对日志功能的部分代码进行修改,我们要怎么改?我们要对这三个方法都进行修改才行,这样维护就显得很困难。因为这三个方法的日志功能是相同的,所以我们很自然的想到将日志功能的代码抽取出来,业务方法要使用的时候引用就行了。
但是不难看出,引用日志控制的代码与业务方法以及耦合死了,业务方法的代码与引用日志控制的代码是合在一起的,我们要解耦合,我们希望日志功能是单独的一块,而业务方法又是独立的单独一块,那如何让他们产生关系呢?其实只需要在他们运行的时候进行绑定就可以了,所以产生了AOP的思想。怎么结合呢?通过配置文件进行配置就行了。
实际上aop就是将目标方法与功能增强方法结合到一起的一种在配置文件当中进行绑定的操作。
AOP的底层实现
实际上,AOP的底层是通过Spring提供的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
AOP的动态代理技术
常用两种:
1、JDK代理:基于接口的动态代理技术(JDK自带的)
2、cglib代理:基于父类的动态代理技术(第三方提供的)
JDK动态代理有个缺点,目标对象必须得有接口,因为JDK的动态代理对象是基于目标对象的接口生成的。而作为框架则必须具有通用性,所以针对这个缺点,Spring集成了市面上的一个第三方的小工具叫cglib,如果目标对象没有接口,则会调用cglib的动态代理技术。
jdk动态代理详解
代码演示:
我们建立一个TargetInterface接口,其中写上一个save方法
package com.itheima.proxy.jdk;
public interface TargetInterface {
public void save();
}
然后再写上一个它的实现类,Target
package com.itheima.proxy.jdk;
public class Target implements TargetInterface{
public void save() {
System.out.println("save running");
}
}
最后再准备一个功能增强类Advice
package com.itheima.proxy.jdk;
public class Advice {
public void before(){
System.out.println("前置增强");
}
public void afterReturning(){
System.out.println("后置增强");
}
}
现在建立一个测试类来演示动态代理:
package com.itheima.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
final Target target = new Target();
//返回值 就是动态生成的代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(), //目标对象类加载器
target.getClass().getInterfaces(), //目标对象相同的接口字节码对象数组
new InvocationHandler() {
//调用代理对象的任何方法 实质执行的都是invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(target,args);//执行目标方法
return null;
}
}
);
//调用代理对象的方法
proxy.save();
}
}
控制台输出save running,说明生成的代理对象已经成功,现在我们对这个代理对象进行功能增强。
现在我们进行功能增强之后:
package com.itheima.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//目标对象
final Target target = new Target();
//增强对象
final Advice advice = new Advice();
//返回值 就是动态生成的代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(), //目标对象类加载器
target.getClass().getInterfaces(), //目标对象相同的接口字节码对象数组
new InvocationHandler() {
//调用代理对象的任何方法 实质执行的都是invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置增强
advice.before();
method.invoke(target,args);//执行目标方法
//后置增强
advice.afterReturning();
return null;
}
}
);
//调用代理对象的方法
proxy.save();
}
}
运行之后可以看到JDK生成的代理对象已经在原来的目标对象基础上增强了前置增强方法和后置增强方法。
这就是底层原理,不过Spring框架已经替我们封装好了这个过程,我们能看得懂就行。
cglib的动态代理
要说明的一点是,cglib的依赖在早期是要我们手动导入的,而现在不需要了因为Spirng已经帮我们集成在了core核心里面。
package com.itheima.proxy.jdk;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//目标对象
final Target target = new Target();
//增强对象
final Advice advice = new Advice();
//返回值 就是动态生成的代理对象 基于cglib
//不是很重要,了解一下就行
//1、创建增强器
Enhancer enhancer = new Enhancer();
//2、设置父类(目标)
enhancer.setSuperclass(Target.class);
//3、设置回调
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//执行前置
advice.before();
method.invoke(target,args);
//执行后置
advice.afterReturning();
return null;
}
});
//4、创建代理对象
Target proxy = (Target) enhancer.create();
proxy.save();
}
}
这个代码的编写了解了解就行,如果以后自己的公司要开发自己的框架的话,可能才会用到这个底层知识。
AOP相关概念
AOP开发明确的事项
知识要点总结
基于XML的AOP开发
快速入门:
第一步:导入AOP相关坐标
其实spring-context中本身就有aop的实现,但是spring自己的这个组件差了点儿意思,相比之下aspectj的功能更加完美,Spring也这么认为,所以Spring内部也融入了aspectj,一般更主张大家使用aspectj来实现spring的aop操作。
<!--引入aspectj-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
第二步创建目标接口和目标类
目标接口:
package com.itheima.aop;
public interface TargetInterface {
public void save();
}
目标类
package com.itheima.aop;
public class Target{
public void save() {
System.out.println("save running");
}
}
第三步:创建切面类(内部有增强方法)
package com.itheima.aop;
public class MyAspect {
public void before(){
System.out.println("前置增强");
}
}
第四步:将目标类和切面类的对象创建权交给Spirng,还有第五步就是配置织入关系,这个自己应该看得懂叭…
<?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 id="target" class="com.itheima.aop.Target">
</bean>
<!--切面对象-->
<bean id="myAspect" class="com.itheima.aop.MyAspect">
</bean>
<!--配置织入:就是我们要告诉Spring哪些方法需要被进行哪些增强(比如什么前置增强啊后置增强之类的)-->
<aop:config>
<!--声明切面:告诉Spring框架这个切面是谁-->
<aop:aspect ref="myAspect">
<!--切面:切点+通知-->
<aop:before method="before" pointcut="execution(public void com.itheima.aop.Target.save())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
第六步测试即可。
XML配置AOP详解
其中的环绕通知不太一样,认识一下:
public Object arround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强");
Object proceed = pjp.proceed(); //切点方法
System.out.println("环绕后增强");
return proceed;
}
小结:
基于注解的AOP开发
快速入门:
第一步创建目标对象和目标类还有第二步的切面类都使用之前的Java文件演示即可。
现在第三步是将目标类和切面类的对象创建权交给Spring,用注解的方式
目标类:
package com.itheima.anno;
import org.springframework.stereotype.Component;
@Component("target")
public class Target implements TargetInterface{
public void save() {
System.out.println("save running");
}
}
切面类:
package com.itheima.anno;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component("myAspect")
@Aspect //告诉Spring这个类是个切面类
public class MyAspect {
public void before(){
System.out.println("前置增强");
}
}
第四步,在切面类中使用注解配置织入关系
package com.itheima.anno;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("myAspect")
@Aspect //告诉Spring这个类是个切面类
public class MyAspect {
//配置前置通知
@Before("execution(* com.itheima.anno.*.*(..))")
public void before(){
System.out.println("前置增强");
}
}
记得使用注解配置之后需要在Spring的配置文件当中开启组件扫描以告知Spring创建注解对应的bean实例,而不是直接写了注解就完事儿。
<?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"
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 https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--组件扫描:扫描注解-->
<context:component-scan base-package="com.itheima.anno"></context:component-scan>
<!--还需要配置aop的自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
测试就完事儿了。
注解配置AOP详解
小结