什么是AOP(百度百科)
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
AOP实现原理
1.aop底层将采用代理机制进行实现。
2.提供接口 + 实现类:spring采用 jdk 的动态代理Proxy。
3.只提供实现类:spring 采用 cglib字节码增强。
4.Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入
AOP中的术语
1. target目标类:需要被代理的类
2. Joinpoint连接点:所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
3. PointCut切入点:已经被增强的方法。例如
4. advice通知/增强,相应方法里面的增强代码。
5. Weaving织入:是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
6. proxy:代理类
7. Aspect:切面,是切入点pointcut和通知advice的结合
他们之间的关系如下
第一个实例-JDK动态代理模拟(也可以参考一下这个)
先写一个接口(java要求实现代理必须提供一个接口)
提供它的实现类
写个切面类,里面提供增强方法
写个工厂类产生代理对象
写个测试类
打个断点查看这个代理方式的返回值
第二个实例-cglib字节码式增强
为什么使用cglib字节码增强方式
使用之前先导入cglib.jar以及它的依赖包(值得高兴的是,spring-core.jar已经帮我们集成了)
(还有很多其他的包因为我是直接用模板导入spring的,所以基本spring的包我的都导入了)
与JDK不同的就是没有了接口,那么我们可以把上面的例子中接口删除,就提供实现类
工厂类中的方法修改如下
public class Factory {
//写个静态方法用来返回一个代理对象
public static UserImpl createUser(){
//传入目标类实例对象(此时只有实现类)
UserImpl user = new UserImpl();
//传入切面
Aspact aspact = new Aspact();
//1.加载cglib核心类
//cglib的底层是通过创建目标类的子类来实现的
Enhancer enhancer = new Enhancer();
//2.设置父类(要被代理的类)
enhancer.setSuperclass(user.getClass());
//3.设置回调函数
//这里的MethodInterceptor方法等价于JDK中的
//InvocationHandler方法
//里面前三个参数也等价
//就是最后一个叫做方法代理
//作用是调用代理类的父类方法(就是目标类方法)
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
aspact.before();
Object obj = method.invoke(user,objects);
aspact.after();
return obj;
}
});
//4.创建代理对象并返回
UserImpl userProxy =(UserImpl)enhancer.create();
return userProxy;
}
}
然后测试
打个断点查看一下返回值
第三个实例-手动从spring容器中获得代理对象
使用这方式就要了解AOP联盟常用的4个通知类型
前置通知 :在目标方法执行前实施增强
后置通知 :在目标方法执行后实施增强
环绕通知 :在目标方法执行前后实施增强(一般用这个,因为在这里只放一个前置处理就叫做前置通知,只放一个后置就叫做后置,如果以上三个通知发生异常那么就可以用try和catch块捕获到异常通知,所以一般用这个就行了)
异常抛出通知:在方法抛出异常后实施增强
1.导入jar包
2.编写接口和实现类(目标类)
3.编写切面类(这里采用环绕通知就要实现一个MethodInterceptor接口并实现里面的方法)
可以去配置文件进行织入配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--其实就是把第一个或者第二个例子
的工厂类里面做的事情放到xml文件中配置
-->
<!--1.创建目标类-->
<bean id="userService" class="com.lfm.AOPalliance.UserServiceImpl"></bean>
<!--2.创建切面类-->
<bean id="aspect" class="com.lfm.AOPalliance.Aspect"></bean>
<!-- 3 创建代理类
* 使用工厂bean FactoryBean ,底层调用 getObject() 返回特殊bean
* ProxyFactoryBean 用于创建代理工厂bean,生成特殊代理对象
interfaces : 确定接口们
通过<array>可以设置多个值
只有一个值时,value=""
target : 确定目标类
interceptorNames : 通知 切面类的名称,类型String[],如果设置一个值 value=""
optimize :强制使用cglib
<property name="optimize" value="true"></property>
底层机制
如果目标类有接口,采用jdk动态代理
如果没有接口,采用cglib 字节码增强
如果声明 optimize = true ,无论是否有接口,都采用cglib
-->
<bean id="userProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="com.lfm.AOPalliance.UserService"></property>
<property name="target" ref="userService"></property>
<property name="interceptorNames" value="aspect"></property>
<!--<property name="optimize" value="true"></property>-->
</bean>
</beans>
测试类
此时没有声明式的强制使用cglib,打个断点测试一下返回值
采用强制声明
第四个实例-spring全自动管理
1.首先要导入两个包
2.然后要在配置文件中引入命名空间
然后和第二个例子不同的地方就是配置文件不一样了
3.目标实现类
4.切面类(环绕通知)
5.配置文件(这个全自动的精髓就在配置文件里面)
<?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">
<!--其实就是把第一个或者第二个例子
的工厂类里面做的事情放到xml文件中配置
-->
<!--1.创建目标类-->
<bean id="userService" class="com.lfm.SpringAOP.UserServiceImpl"></bean>
<!--2.创建切面类(通知)-->
<bean id="aspect" class="com.lfm.SpringAOP.Aspect"></bean>
<!-- 3 aop编程
3.1 导入命名空间
3.2 使用<aop:config>进行配置
proxy-target-class="true" 声明时使用cglib代理
-->
<aop:config proxy-target-class="true">
<!--定义切入点,从目标对象中获得
采用execution表达式
* (这里有个空格)目标类包名.类名(*为任意).方法名(*为任意)(参数(..代表任意))
-->
<aop:pointcut expression="execution(* com.lfm.SpringAOP.*.*(..))" id="myPointCut"/>
<!--
<aop:advisor> 特殊的切面,只有一个通知 和 一个切入点
advice-ref:对通知的引用
pointcut-ref:对切入点的引用
-->
<aop:advisor advice-ref="aspect" pointcut-ref="myPointCut"/>
</aop:config>
</beans>
6.测试类
结果
同样的打个断点测试返回值(有显示声明用cglib)
去配置文件删除那个声明
总结如下,AOP的核心就是用代理实现的,无论是JDK动态代理还是cglib字节码增强,都是运行时才增强相应的方法。
关于这个AspectJ它是一个很强大的AOP框架,spring只是对它进行了集成,不是说spring中有它,下次有时间也去学习一下AspectJ这个框架(555快要两点了,狗命重要,睡觉了。。)