Spring系列之aop aop是什么?+xml方式实现aop+注解方式实现aop
什么是AOP?
AOP为Aspect Oriented Programming 的缩写,意识为面向切面的编程,是通过预编译和运行期动态代理实现程序功能的统一维护的一种技术
AOP是OOP(Object Oriented Programmin 面向对象编程)的延续,是软件开发中的一个热点,也是框架中的一个重要内容,是函数式编程的一种衍生范型,利用AOP可以对业务逻辑的各个部分进行隔离,也使业务逻辑各部分的耦合性降低,提高程序的可重用性,同时提高了开发的效率
我先来讲讲什么是切面
把一块蛋糕切成两块,这个切口就是切面,;炒饭的时候,锅和锅铲就是切面;web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
我们使用一个银行管理系统来说说为什么要使用面向切面编程。
如图银行的取款业务和查询余额业务有交叉的业务逻辑(所谓交叉业务逻辑是与主业务无关的代码,比如安全检查,事务,日志等等),这里指的是验证用户的业务。这会导致代码纠缠,交叉业务逻辑与主业务逻辑混合在一起,这会导致业务逻辑的混合不清,这时候就要用到AOP
使用AOP可以帮助我们简化代码,我们在写代码的时候可不写这个验证用户的业务,可以在另一个地方写好验证用户的代码,然后告诉Spring那几个地方需要这些代码,让Spring加过去即可,如果有多个控制流的话,会大大的减少时间,而AOP不会把代码加入到源文件中但是他会正确的影响最后的机器代码
上面那个 验证用户 的方框,我们可以把它当成一块板子,在这块板子上插入一些控制流程,这块板子就可以当成是 AOP 中的一个切面。所以 AOP 的本质是在一系列的纵向的控制流程中,把那些相同的子流程提取成一个横向的面,把纵向流程画成一条直线,而 AOP 相当于把相同的地方连起来了(这幅图是真的形象,好好体会一下应该不难),这个验证用户的子流程 就成了一条直线,也可以理解成一个切面,这里只插了三个流程,如果其他流程也需要这个子流程,也可以插到其他地方去。
AOP的优势与作用
作用:在不修改源码的情况下对方法进行增强
优势:提高代码的可复用性,提高开发效率,便于维护
AOP的底层实现
AOP的底层是通过Spring动态代理技术实现的,在运行期间通过动态代理,获取代理对象,代理方法执行时增强功能介入,在去调用目标对象的方法,从而完成功能增强。
AOP的动态代理技术
jdk代理
jdk代理:基于接口的动态代理技术
cglib代理:基于父类的动态代理技术
我们来逐一讲解这两个代理方式的差别
jdk代理
demo内容:user类实现一个userImp接口,对user类进行动态代理
user类代码
package com.pjh.user;
public interface user {
public void save();
}
userImp代码
package com.pjh.user.Imp;
import com.pjh.user.user;
public class userImp implements user {
public void save() {
System.out.println("save run....");
}
}
对save方法进行增强
这里使用两种方式
方式一匿名内部类:即InvocationHandler直接使用匿名内部类的方式来创建
package com.pjh.test;
import com.pjh.user.Imp.userImp;
import com.pjh.user.user;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class main {
public static void main(String[] args) {
//创建目标对象
final userImp userImp=new userImp();
//调用proxy类的静态方法来创建代理对象
//Proxy.newProxyInstance(类加载器,获取目标对象的接口,实现动态代理接口)
user userproxy = (user)Proxy.newProxyInstance(userImp.class.getClassLoader(),userImp.getClass().getInterfaces(), new InvocationHandler() {
//invoke(代理类,被代理的方法,参数)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强代码");
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object invoke = method.invoke(userImp);
System.out.println("后置增强代码");
return invoke;
}
});
userproxy.save();
}
}
运行结果
成功对方法进行了增强
方法二使用一个类继承自InvocationHandler来实现
编写InvocationHandler实现类
package com.pjh.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class InvocationHandlerImp implements InvocationHandler {
//所有类均继承自object类
private Object object;
//写一个带参构造的方法,来引入目标对象
public InvocationHandlerImp(Object object) {
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增强前");
Object invoke = method.invoke(object, args);
System.out.println("执行后的方法");
return invoke;
}
}
编写测试类
package com.pjh.test;
import com.pjh.proxy.InvocationHandlerImp;
import com.pjh.user.Imp.userImp;
import com.pjh.user.user;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) {
//创建目标对象,即代理的真实对象
userImp person = new userImp();
//获取处理器实现类InvocationHandlerImp
InvocationHandlerImp invocationHandlerImp = new InvocationHandlerImp(person);
//获取代理对象
user o = (user)Proxy.newProxyInstance(userImp.class.getClassLoader(),
person.getClass().getInterfaces(),
invocationHandlerImp);
//调用方法
o.save();
}
}
运行结果
放这张表情包的目的是想提醒大家休息一下想必大家都看了很久的电脑了,可以开窗看看外面,休息休息
Cglib的动态代理
这里就简单的讲一下流程
目标类
这里仅仅是一个类没有实现任何接口
package com.pjh.user;
public class person {
public void save(){
System.out.println("save");
}
}
主函数
package com.pjh.test;
import com.pjh.user.person;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class test2 {
public static void main(String[] args) {
//设置目标对象
final person one = new person();
//创建增强器
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(person.class);
//设置回调
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前置增强代码");
Object invoke = method.invoke(one, objects);
System.out.println("后置增强");
return invoke;
}
});
//获取代理对象
person oneproxy = (person)enhancer.create();
//调用增强后的方法
oneproxy.save();
}
}
AOP相关概念
String 的AOP实现底层就是对上面的动态代理进行了封装,封装后我们只需要对关注的部分进行代码进行编写,并通过配置的方式完成对指定目标的方法增强
AOP的部分术语
Target(目标对象):代理的目标对象
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
Joinpoint(连接点):所谓连接点指那些被拦截到的点,在spring中这些点指的是方法,因为spring是只支持方法类型的连接点
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义,即被增强的方法
Jointpoint不一定是Pointcut但是Pointcut一定是Joinpoint
Advice(通知/增强):拦截到jointpoint之后要做的事情就是通知,封装增强业务逻辑的方法
Aspect(切面):是切入点和通知的结合
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程,spring采用动态织入代理,而Aspect采用编译织入和类装载期织入,切点与通知结合的过程
AOP的实现内容
Spring框架监控切入点方法的执行,只要检测到切入点被执行,就会使用代理机制,创建代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行
AOP底层使用什么代理机制
在spring中,框架会根据是否实现了接口来选择使用那种动态代理方式
基于XML的AOP开发
快速入门
1.导入AOP的相关配置坐标
2.创建目标接口和目标类(内有切入点)
3.创建切面类(内部有增强方法)
4.将目标类和切面类的对象创建权交给spring
5.在applicationContext.xml中配置织入关系
6.测试代码
1.导入AOP的相关坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
</dependencies>
创建接口与实现类
接口
package com.pjh.user;
public interface userInterface {
public void save();
}
实现类
package com.pjh.user;
public class user implements userInterface{
public void save() {
System.out.println("save run...");
}
}
创建切面类
package com.pjh.enhance;
public class enhance {
public void enhance(){
System.out.println("这是增强代码!!!!");
}
}
将目标类和切面类的对象创建权交给spring
<bean id="daoImp" class="com.pjh.dao.Imp.daoImp"/>
<bean id="aspect" class="com.pjh.aspect.aspect"/>
引入命名空间与约束路径
<?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">
</beans>
配置切点表达式和前置增强之间的关系
切点表达式的配置语法
excution(【修饰符】返回值类型 包名.类名.方法名(参数))
通知的配置语法
<aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>
这里先写个简单的格式后面再给大家深入讲
<aop:config >
<!--要切入的类-->
<aop:aspect ref="enhance">
<!--切入后的增强方法-->
<!--这是一个前置增强-->
<!--method切入后的增强方法-->
<!--pointcut对什么类方法执行的时候进行增强-->
<aop:before
method="enhance" pointcut="execution(public void com.pjh.user.user.save())"></aop:before>
</aop:aspect>
</aop:config>
测试代码类
import com.pjh.user.userInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class test {
/*如果是继承自接口的一定要使用接口进行定义否则会报错*/
@Autowired
private userInterface user;
@Test
public void test1(){
user.save();
}
}
结果
**
切点表达式的花样写法
**
表达式语法
excution(【修饰符】 返回值类型 包名.类名.方法名(参数))
返回值的修饰符可省略
返回值的类名,包名,方法名可以使用“ * ”星号代表任意
包名与类名之间的一个点" . "代表当前包下的所有类,两个点“ .. ”代表当前包及其子包下的所有类
参数列表可以使用两个点 " . . " 表示任意个数,任意类型的参数列表
//user类下的save方法增强
execution(public void com.pjh.one.user.save())
//对user类下的所有放回值为void的方法进行增强
execution(public void com.pjh.one.user.*(..))
//one包下所有类的所有方法进行增强
execution(* com.pjh.*.*.*(..))
//one包包括其子包下所有类的所有方法进行增强
execution(* com.pjh..*.*.*(..))
//任何包任何类任何方法
execution(* *.*..*.*. * (..))
切点表达式的抽取
当多个增强切点表达式相同时,可以将切点表达式进行抽取。在增强中使用pointcut-ref属性替代pointcut属性来引用切点表达式
<aop:config >
<aop:aspect ref="enhance">
<aop:pointcut id="myPointcut" expression="execution(public void com.pjh.user.user.save())"/>
<aop:before
method="enhance" pointcut-ref="myPointcut"></aop:before>
</aop:aspect>
</aop:config>
**
通知的类型
**
通知的配置语法
<aop:通知的类型 method=“切面中的方法名” pointcut=“切点表达式”/>
简单的小总结
<aop:config >
<aop:aspect ref="切入类的名称>
<aop:before
method="切入方法的名称e" pointcut="切点表达式"></aop:before>
</aop:aspect>
</aop:config>
通知的类型:前置通知、后置通知、环绕通知、异常抛出通知、最终通知
切点表达式的写法:
excution(【修饰符】返回值类型 包名.类名.方法名(参数))
下面我们再来讲讲更加简单的方法,即使用注解的方式
基于注解的AOP开发
注解aop的开发步骤
1.使用@Aspect标注切面类
2.使用@通知注解标注通知方法
3.在配置文件中配置aop自动代理<aop:aspectj-autoproxy>
标注为一个切面类@Aspect
@Aspect
public class enhance {
}
使用注解来抽取切点表达式
@Pointcut(”注解表达式“)
/切点表达式方法的抽取,抽取方法是在切点内定义方法,
在方法内使用 @Pointcut注解切点表达式,然后在增强注解中进行引用/
@Pointcut("execution(public void com.pjh.user.user.save())")
public void mypoint(){}