参考:https://mp.weixin.qq.com/s/vfPKWYcUa4yjJ4cnmIEaxQspring
AOP特色:
express
面向切面编程, 利用AOP对业务逻辑的各个部分进行抽取公共代码, 下降耦合度, 提升代码重用性, 同时提升开发效率.编程
采起横向抽取, 取代传统纵向继承体系重复性代码缓存
解决事务管理, 性能监视, 安全检查, 缓存, 日志等问题安全
Spring AOP在运行期, 经过反向代理的方式解决类加载, 属性注入bash
AspectJ是基于Java的AOP框架, 在Spring使用AspectJ实现AOPapp
AOP实现机制:
底层采用代理机制实现AOP.框架
2种代理机制:
2.0、采用JDK的的动态代理Proxy;
2.一、采用CGLIB字节码加强ide
AOP专业术语:
Target: 目标类 ( 须要被代理的类 )
Joinpoint: 链接点 ( 可能须要使用的目标类方法 )
Advice: 加强代码 ( 对链接点加强的代码 )
PointCut: 切入点 ( 可能须要 Advice 加强的链接点 )
Weaving: 织入 ( 建立代理对象 proxy 执行切入点的方法 )
Aspect: 切面 ( Advice 与 PointCust的结合 )函数
下面经过JDK动态代理和CGLIB字节码加强两种方式实现AOP操做:
当目标类没有实现接口或者须要更好的性能的时候就须要考虑使用CGLIB实现动态Proxy
JDK动态代理:
1.目标类: Service层
2.切面类: 使用JDK动态代理对Service层代码加强
3.工厂类: 得到proxy对象
目标类:
public interface UserService {
void addUser();
void updateUser();
void deleteUser();
}
public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("add User");
}
public void updateUser() {
System.out.println("update User");
}
public void deleteUser() {
System.out.println("delete User");
}
}
复制代码
切面, 加强链接点:
public class MyAspect {
public void before(){
System.out.println("before");
}
public void after(){
System.out.println("after");
}
}
复制代码
静态代理对象工厂:
public class MyProxyBeanFactory {
public static UserService createService(){
final UserService userService = new UserServiceImpl();
final MyAspect myAspect = new MyAspect();
//经过userService得到代理对象
UserService proxyService = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler(){
//Proxy代理对象, method代理类的目标方法, args目标方法参数
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
//织入横向代码
myAspect.before();
//执行代理类的方法
Object obj = method.invoke(userService, args);
myAspect.after();
//返回执行代理方法的返回值
return obj;
}
});
//返回代理对象
return proxyService;
}
}
复制代码
applicationContext.xml:
<bean id="userService" class="com.f_aop.jdkproxy.MyProxyBeanFactory" factory-method="createService"></bean>
复制代码
测试方法:
@Test
public void f1(){
String XMLPATH="com/f_aop/jdkproxy/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(XMLPATH);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}复制代码
CGLIB字节码加强动态代理:
原理: cglib动态生成一个代理类的子类, 子类重写代理类的全部不是final的方法, 在子类中采用方法拦截技术拦截全部父类的方法调用, 顺势织入切面逻辑, 实现AOP, 它比JDK动态代理要快。但其操做流程与JDK动态代理一致。
下面只给出静态代理工厂的代码:
public class MyProxyBeanFactory {
public static UserService createService(){
final UserService userService = new UserServiceImpl();
final MyAspect myAspect = new MyAspect();
//建立代理
Enhancer enhancer = new Enhancer();
//肯定父类
enhancer.setSuperclass(userService.getClass());
//向代理对象的方法中织入切面代码
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
myAspect.before();
//执行目标方法
//Object obj = method.invoke(userService, args);
//执行目标方法, 效果与method.invoke(userService, args);
//通常执行这个方法, 速度要快一些
Object obj = methodProxy.invoke(proxy, args);
myAspect.after();
//返回目标方法返回值
return obj;
}
});
//使用enhancer建立代理对象
return (UserService) enhancer.create();
}
}
复制代码
cglib的整个流程与JDK的动态代理都是同样的, 就在底层处理接口和加载字节码文件有区别。
AOP联盟定义Advice规范, 编写Advice代码需实现Advice接口。
Spring按照Advice在目标类中方法的链接点的位置, 分为5类:
前置通知:
实现接口: org.springframework.aop.MethodBeforeAdvice
只在目标方法前进行代码加强;
后置通知:
实现接口: org.springframework.aop.AfterReturningAdvice
只在目标方法后进行代码加强;
环绕通知( 必须手动执行目标方法 ):
实现接口: org.springframework.aop.MethodInterceptor
只在目标方法先后进行代码加强; 效果等同于JDK的Proxy/cglib
异常通知:
实现接口: org.springframework.aop.ThrowsAdvice
在抛出异常的时候进行代码加强;
引介通知:
实现接口: org.springframework.aop.IntroductionInterceptor
只在目标类中增添一些新的方法和属性;
复制代码
使用Spring提供的ProxyFactoryBean模拟代理过程, 实现Spring的AOP:
使用环绕型通知进行演示(目标类与前面的同样):
1.导入aop, aopalliance jar包
2.切面类(MyAspect)实现MethodInterceptor接口
3.实现MethodInterceptor中invoke方法, 手动织入横向代码
4.在applicationContext.xml中配置, 使用Spring提供的ProxyFactoryBean对目标类实现代理
复制代码
演示代码:
切面类:
public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("前");
//手动执行目标方法
Object obj = mi.proceed();
System.out.println("后");
//返回目标方法执行的返回值
return obj;
}
}
复制代码
配置applicationContext.xml:
<!-- 得到目标类对象 -->
<bean id="userService" class="com.f_aop.methodInterceptor.UserServiceImpl"></bean>
<!-- 建立切面类 -->
<bean id="myAspect" class="com.f_aop.methodInterceptor.MyAspect"></bean>
<!-- 建立代理类, 使用Spring配备的代理工厂 -->
<bean id="proxyService" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 指定接口 -->
<property name="interfaces" value="com.f_aop.methodInterceptor.UserService"></property>
<!-- 肯定目标类对象 -->
<property name="target" ref="userService"></property>
<!-- 肯定Aspect, 因为interceptorNames的形参值是String[], 因此使用value, 而非ref -->
<property name="interceptorNames" value="myAspect"></property>
<property name="optimize" value="true"></property>
</bean>
复制代码
测试方法:
@Test
public void f1(){
String XMLPATH="com/f_aop/methodInterceptor/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(XMLPATH);
//使用proxyService, 而非userService
//经过代理对象执行Advice
UserService userService = (UserService) applicationContext.getBean("proxyService");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}复制代码
运行结果:
前
add User
后
前
update User
后
前
delete User
后
复制代码
applicationContext.xml中建立代理类标签详解:
ProxyFactoryBean: Spring的代理工厂,生成代理对象
interfaces: 目标类实现的接口, 多个值使用<array><value>肯定每一个值
单个值的时候直接使用value
target: 肯定目标类
interceptorNames: 肯定切面类的名称, 类型为String[], 使用value, 切记不使用ref
optimize: 强制底层使用cglib
当没有设置optimize的值时:
Spring自动判断, 没有接口使用cglib, 有接口使用jdk
显式设置optimize, 若是声明optimize=true,不管是否有接口,都采用cglib
复制代码
上面这种代理实现, 是在applicationContext.xml配置文件中模拟代理工厂产生代理对象, 在测试函数中得到是容器产生的代理对象proxyService.
利用AspectJ简化Spring中ProxyFactoryBean的配置:
使用环绕型通知进行演示, 编写流程:
1.导入aspectj.weaver jar包.
2.在applicationContext.xml配置文件中添加aop的xmlns和xsi限制
3.在配置文件中配置切面类(MyAspect)的切入点(PointCut), 特殊切面(包含advice与PointCut).
首先使用expression表达式配置切入点(PointCut), 即目标类中哪些方法须要加强.
而后配置特殊切面, 对配置好的切入点, 使用加强点advice进行加强.
复制代码
下面使用代码演示, 由于只需修改配置文件与测试类, 只给出配置文件代码:
<?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="userService" class="com.f_aop.aspectJ.UserServiceImpl"></bean>
<!-- 建立切面类 -->
<bean id="myAspect" class="com.f_aop.aspectJ.MyAspect"></bean>
<!-- 配置特殊切面 -->
<!-- proxy-target-class配置是否使用cglib -->
<aop:config proxy-target-class="true">
<aop:pointcut id="myPointCut" expression="execution(* com.f_aop.aspectJ.*.*(..))"/>
<aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut"/>
</aop:config>
</beans>
<!--
aop:config: 配置AOP
proxy-target-class: 配置是否强行使用cglib, 效果与前面的optimize同样
pointcut: 配置切入点.
expression: 配置切入点表达式,用于得到目标类中须要加强的目标方法.
advisor: 配置切入点与切面类, 指明哪些方法须要加强.
advice-ref: 切入类对象引用.
pointcut-ref: 切入点引用.
-->
复制代码
相比于Spring提供的ProxyFactoryBean, AspectJ更加便捷。
AspectJ是基于Java的AOP框架, 用于自定义AOP开发.
切入点表达式
用于描述目标类中的目标方法, 指定哪些方法可做为切入点.
下面说明切入点表达式写法:
语法: expression = " execution( 修饰符 返回值 包.类.方法名(参数) throws 异常 ) "
切入表达式针对每一个部分的编写规则以下
修饰符(通常省略):
public 公共方法
* 任意方法
返回值(不能省略):
void 没有返回值
String 返回值为字符串
* 返回值任意
包(可省略):
com.demo 固定包
com.demo.* demo下任意子包,例如:com.demo.aop
com.demo.. demo下全部包(包含本身,也包含多级子包)
com.demo.*.service.. demo下任意子包, 子包中包含固定包service,service下全部包
类(可省略):
UserServiceImpl 指定类
*Impl 以Impl结尾的类
User* 以User开头的类
* 任意类
方法名(不能省略):
addUser 指定方法
add* 以add开头的方法
*User 以User结尾的方法
* 任意方法
参数:
() 无参
(int) 一个int型参数
(int, int) 两个int型参数
(..) 任意参数
throws(可省略, 通常不写)
复制代码
下面给出一个例子:
1.execution(* com.demo.*.service..*.*(..))
指定com.demo下具备固定service包的任意子包中任意类中的任意方法,方法返回值任意.
其余种类的expression表达式:
1.within: 匹配包或子包中的方法.
within(com.demo..*) demo下全部包中任意类中任意方法
2.this: 匹配实现接口的类的代理对象中方法:
this(com.demo.aop.user.UserDAO) 匹配UserDAO中实现类代理对象任意方法.
3.target: 匹配实现接口的类的目标对象中方法:
target(com.demo.aop.user.UserDAO) 匹配UserDAO中实现类目标对象任意方法.
4.args: 匹配参数格式符合标准的方法
args(int, int) 匹配形参值类型为int, int的任意方法.
5.bean(id): 匹配指定bean全部方法
bean('userService') 匹配userService中全部方法
复制代码
AspectJ通知类型
与AOP联盟同样, AspectJ也定义了多种通知类型.
AspectJ总共6中通知类型:
1.before: 前置通知,用于校验数据
在目标方法以前执行, 若抛出异常, 组织目标方法运行.
2.afterReturning: 后置通知,常规数据处理
目标方法执行后执行, 可得到目标方法的返回值.目标方法出现异常, 方法不执行.
3.around: 环绕通知
目标方法先后, 可阻止目标方法执行, 必须手动执行目标方法.
4.afterThrowing: 抛出异常通知
目标方法出现异常后执行, 没有出现异常就不执行.
5.after: 最终通知, 资源回收, 相似finally方法
方法执行完, 不管方法中是否出现异常, 都将执行.
复制代码
环绕通知与其余通知之间的联系:
try{
//前置: before
//手动执行目标方法
//后置: afterReturning
} catch(){
//捕获异常: afterThrowing
} finally{
//最终通知: after
}
复制代码
从上面看出, 彻底可使用环绕通知模拟前置通知, 后置通知, 环绕通知结合AfterThrowing, After实现AOP.
aop标签对应的通知类型种类:
使用AOP联盟进行切面类编写, 须要定义通知类型, 切面类必须实现特定接口(MethodInterceptor), 而后为目标方法添加加强代码, 相比于AOP联盟, AspectJ只要定义切面类, 加强代码的使用彻底交给配置文件, 避免代码污染, 简化操做。
基于xml配置通知类型的开发流程:
1.导入AOP联盟, AspectJ, AOP依赖, Aspect规范 jar包.
2.编写目标类: 接口与实现类.
3.编写切面类: 编写AspectJ的通知类型方法, 方法名任意, 无需实现什么接口.
4.配置xml: 配置通知类型.
5.测试.
下面给出演示代码, 代码中已经给出注释加以说明(如有不懂请在评论区留言):
目标类 ( 接口与实现类 ):
public interface UserService {
void addUser();
void updateUser();
void deleteUser();
}
public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("add User");
}
public void updateUser() {
System.out.println("update User");
}
public void deleteUser() {
System.out.println("delete User");
}
}
复制代码
切面类:
package com.f_aop.aspectJFinal;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect{
// 测试前置通知与后置通知
// public void myBefore(JoinPoint jPoint){
// System.out.println("前置通知"+jPoint.getSignature().getName());
// }
//
// public void myAfterReturning(JoinPoint jPoint, Object ret){
// System.out.println("后置通知"+jPoint.getSignature().getName()+"--"+ret);
// }
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("前置通知");
//手动执行目标方法
Object obj = joinPoint.proceed();
// 环绕通知与抛出异常通知的测试结果:
// int i = 1/0;
// 前置通知
// add User
// 抛出异常通知/ by zero
// 最终通知
System.out.println("后置通知");
return obj;
}
public void myAfterThrowing(JoinPoint jPoint, Throwable e){
System.out.println("抛出异常通知"+e.getMessage());
}
public void myAfter(JoinPoint jPoint){
System.out.println("最终通知");
}
}
复制代码
applicationContext.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 id="userService" class="com.f_aop.aspectJFinal.UserServiceImpl"></bean>
<!-- 建立切面类 -->
<bean id="myAspect" class="com.f_aop.aspectJFinal.MyAspect"></bean>
<!-- 使用 config 配置AspectJ的AOP -->
<aop:config>
<!-- 声明切入面 -->
<aop:aspect ref="myAspect">
<!-- 配置目标方法的切入点 -->
<aop:pointcut id="myPointCut" expression="execution(* com.f_aop.aspectJFinal.UserServiceImpl.*(..))"/>
<!--
配置通知类型的时候, method表明切入类方法, pointcut-ref表明目标类切入点.
两者结合的意思就是目标类中哪些切入点须要切入方法进行加强.
-->
<!-- 前置通知
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
后置通知, returning用于接收目标方法执行完后的返回值
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret"/>
-->
<!-- 抛出异常通知要配合环绕通知使用, 环绕通知抛出的异常使用抛出异常通知接收 -->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!-- 抛出异常, throwing="e" 表明执行目标方法后,可能会抛出的异常经过 e 进行接收 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!-- 最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
复制代码
测试方法:
@Test
public void f1(){
String XMLPATH="com/f_aop/aspectJFinal/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(XMLPATH);
UserService userService = (UserService) applicationContext.getBean("userService");
//测试AOP
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
复制代码
基于注解的通知类型开发流程:
1.在刚开始配置注解的时候, 能够按照 xml 中bean, aop的配置信息来给类/属性添加注解, 这样不容易把逻辑搞混.
2.测试, 其实整个开发过程与 xml 配置没什么区别, 都是同样的, 只是形式上有区别。
在给各类类添加注解之间, 必定要牢记:
1.在 xml 配置文件中添加扫描, 扫描注解类:
<context:component-scan base-package="com.demo.aspectJAnnotation"></context:component-scan>
复制代码
2.肯定AOP注解生效:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
复制代码
AspectJ中通知类型的注解种类:
1.@Aspect
声明切面类, 不须要指定切面类名称.
等同于<aop:aspect ref="myAspect">, 通常与 @Component 结合使用, Component表明myAspect对象
2.@Pointcut("execution(* com.f_aop.aspectJFinalAnnotation.UserServiceImpl.*(..))")
声明公共切入点, 经过"方法名"得到切入点引用.
等同于<aop:pointcut id="myPointCut" expression="execution(* com.f_aop.aspectJFinalAnnotation.UserServiceImpl.*(..))"/>
2.@Before(value="execution(* com.demo..service.*.*(..))")
前置通知, 直接添加在切面类方法前.
等同于<aop:before method="myBefore" pointcut-ref="myPointCut"/>
或者上面 @Before 也可写作 @Before(value="myPointCut()") myPointCut是方法名
此时要先在切面类中声明公共切入点方法:
@Pointcut("execution(* com.f_aop.aspectJFinalAnnotation.UserServiceImpl.*(..))")
private void myPointCut(){}
这样写的做用就是为了少写代码, 避免在多个切面类通知方法前都要加execution=(...).
而且若是切入点表达式写错了, 也很难排查问题.(不懂请看下面的演示代码)
3.@AfterReturning(value="myPointCut()", returning="ret")
后置通知, 直接添加在后置通知方法前.
等同于<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret"/>
ret表示接收的返回值名称, 含有与标签中的ret同样.
4.@Around("myPointCut()")
环绕通知, 添加在环绕方法前面.
等同于<aop:around method="myAround" pointcut-ref="myPointCut"/>
5.@AfterThrowing(value="myPointCut()", throwing="e")
抛出异常通知, 添加在抛出异常通知方法前.
等同于<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
6.@After("myPointCut()")
最终通知, 添加在最终通知以前.
等同于<aop:after method="myAfter" pointcut-ref="myPointCut"/>
复制代码
接下来给出演示代码:
目标类:
package com.f_aop.aspectJFinalAnnotation;
import org.springframework.stereotype.Service;
//生成UserService的bean: userService
@Service("userService")
public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("add User");
}
public void updateUser() {
System.out.println("update User");
}
public void deleteUser() {
System.out.println("delete User");
}
}
复制代码
xml 配置文件 applicationContext.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"
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 http://www.springframework.org/schema/context/spring-context.xsd ">
<!-- 扫描注解类 -->
<context:component-scan base-package="com.f_aop.aspectJFinalAnnotation"></context:component-scan>
<!-- 肯定AOP注解生效 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
复制代码
切面类:
package com.f_aop.aspectJFinalAnnotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//得到切面类Bean
@Component
//声明切面类
@Aspect
//因为两者都修饰同一个类, 因此不加id
public class MyAspect{
//直接设置切入点, 不使用自定义的公共切入点
// @Before("execution(* com.f_aop.aspectJFinalAnnotation.UserServiceImpl.*(..))")
// public void myBefore(JoinPoint jPoint){
// System.out.println("前置通知"+jPoint.getSignature().getName());
// }
// 设置切入点, 经过returning得到返回值
// @AfterReturning(value="myPointCut()", returning="ret) // public void myAfterReturning(JoinPoint jPoint, Object ret){ // System.out.println("后置通知"+jPoint.getSignature().getName()+"--"+ret); // } @Pointcut("execution(* com.f_aop.aspectJFinalAnnotation.UserServiceImpl.*(..))") private void myPointCut(){ //配置空方法,用于声明公共切入点 } @Around("myPointCut()") public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("前置通知"); //手动执行目标方法 Object obj = joinPoint.proceed(); int i = 1/0; // 前置通知 // add User // 抛出异常通知/ by zero // 最终通知 System.out.println("后置通知"); return obj; } @AfterThrowing(value="myPointCut()", throwing="e") public void myAfterThrowing(JoinPoint jPoint, Throwable e){ System.out.println("抛出异常通知"+e.getMessage()); } @After("myPointCut()") public void myAfter(JoinPoint jPoint){ System.out.println("最终通知"); } } 复制代码