博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring 面向切面编程AOP
阅读量:4209 次
发布时间:2019-05-26

本文共 11715 字,大约阅读时间需要 39 分钟。

原文:

AOP基本概念

AOP:Aspect Oriented Programming 面向切面编程

通过预编译方式和运行期间动态代理实现程序统一维护的一种技术

主要功能:日志记录,性能统计,安全控制,事务处理,异常处理

相关概念:

1) Aspect :切面,切入系统的一个切面。比如事务管理是一个切面,权限管理也是一个切面;

2) Join point :连接点,也就是可以进行横向切入的位置;

3) Advice :通知,切面在某个连接点执行的操作(分为: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice );

4) Pointcut :切点,符合切点表达式的连接点,也就是真正被切入的地方;

实现方式:

1.静态AOP:预编译AspectJ 将切面代码直接编译到Java类文件中

2.动态AOP:Spring的AOP和JbossAop
SpringAop:
JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)

1)JDK动态代理

  1. 主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方法
  2. 将被代理对象注入到一个中间对象,而中间对象实现InvocationHandler接口,在实现该接口时,可以在 被代理对象调用它的方法时,在调用的前后插入一些代码
  3. Proxy.newProxyInstance() 能够利用中间对象来生产代理对象。插入的代码就是切面代码
package aop;/** * UserService * 被代理对象实现的接口,只有接口中的方法才能够被代理 * Spring AOP之JDK动态代理 * Created by heqianqian on 2017/4/26. */public interface UserService {
public void addUser(User user); public User getUser(int id);}
package aop;/** * UserServiceImpl * 被代理对象 * Spring AOP之JDK动态代理 * Created by heqianqian on 2017/4/26. */public class UserServiceImpl implements UserService {
@Override public void addUser(User user) { System.out.println("Add User"); } @Override public User getUser(int id) { System.out.println("Get User"); return new User(1, "heqianqian"); }}
package aop;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;/** * ProxyUtil * 代理中间类 * Spring AOP之JDK动态代理 * 

* JDK动态代理要求被代理实现一个接口, * 只有接口中的方法才能够被代理。 * 其方法是将被代理对象注入到一个中间对象, * 而中间对象实现InvocationHandler接口, * 在实现该接口时,可以在被代理对象调用它的方法时,在调用的前后插入一些代码 *

* Created by heqianqian on 2017/4/26. */public class ProxyUtil implements InvocationHandler {

private Object target; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("do someting before invoke method"); Object result = method.invoke(target, args); System.out.println("do someting after invoke method"); return result; } public ProxyUtil(Object target) { this.target = target; } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; }}

package aop;import java.lang.reflect.Proxy;/** * ProxyTest * 测试类 * Spring AOP之JDK动态代理 * Created by heqianqian on 2017/4/26. */public class ProxyTest {
public static void main(String[] args) { Object proxyedObject = new UserServiceImpl(); ProxyUtil proxyUtil = new ProxyUtil(proxyedObject); //Proxy.newProxyInstance() 能够利用中间对象来生产代理对象 UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), UserServiceImpl.class.getInterfaces(), proxyUtil); proxyObject.addUser(new User()); proxyObject.getUser(1); }}

输出结果

do someting before invoke methodAdd Userdo someting after invoke methoddo someting before invoke methodGet Userdo someting after invoke method

总结:该方式有一个要求, 被代理的对象必须实现接口,而且只有接口中的方法才能被代理

2) CGLIB (code generate libary)

  1. 字节码生成技术实现AOP
  2. 其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。因为需要Override被代理对象的方法,所以自然CGLIB技术实现AOP时,就必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖 。我们使用CGLIB实现上面的例子:
package aop.cglib;import org.springframework.cglib.proxy.Enhancer;import org.springframework.cglib.proxy.MethodInterceptor;import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/** * CGProxy * Spring Aop之CGLIB动态代理 * Created by heqianqian on 2017/4/26. */public class CGProxy implements MethodInterceptor {
private Object target; public CGProxy(Object target) { this.target = target; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("do someting before invoke method"); Object result = methodProxy.invokeSuper(o, objects); System.out.println("do someting after invoke method"); return result; } public Object getProxyObject() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 设置父类 // 设置回调 enhancer.setCallback(this); // 在调用父类方法时,回调 this.intercept() // 创建代理对象 return enhancer.create(); }}
package aop.cglib;import aop.User;import aop.UserService;import aop.UserServiceImpl;/** * CGProxyTest * 测试类 * Spring Aop之CGLIB动态代理 * Created by heqianqian on 2017/4/26. */public class CGProxyTest {
public static void main(String[] args) { Object proxyedObject = new UserServiceImpl(); CGProxy cgProxy = new CGProxy(proxyedObject); UserService proxyObject = (UserService) cgProxy.getProxyObject(); proxyObject.getUser(1); proxyObject.addUser(new User()); }}

输出结果

do someting before invoke methodGet Userdo someting after invoke methoddo someting before invoke methodAdd Userdo someting after invoke method

总结:原理是生成一个父类 enhancer.setSuperclass( this.target.getClass()) 的子类 enhancer.create() ,然后对父类的方法进行拦截enhancer.setCallback( this) . 对父类的方法进行覆盖,所以父类方法不能是final的。

3) Spring AOP的配置

Spring中AOP的配置一般有两种方法,一种是使用 <aop:config> 标签在xml中进行配置,一种是使用注解以及@Aspect风格的配置。

3.1 基于<aop:config>的AOP配置

<aop:aspect> 配置一个切面;<aop:pointcut>配置一个切点,基于切点表达式;<aop:before>,<aop:after>,<aop:around>是定义不同类型的advise. aspectBean 是切面的处理bean:

3.2 基于注解和@Aspect风格的AOP配置

我们以事务配置为例:首先我们启用基于注解的事务配置

然后扫描Service包:

最后在service上进行注解:

@Service("userService")@Transactionalpublic class UserServiceImpl implements UserService{
@Autowired private UserMapper userMapper; @Transactional (readOnly=true) public User getUser(int userId) { System.out.println("in UserServiceImpl getUser"); System.out.println(DataSourceTypeManager.get()); return userMapper.getUser(userId); } public void addUser(String username){ userMapper.addUser(username);// int i = 1/0; // 测试事物的回滚 } public void deleteUser(int id){ userMapper.deleteByPrimaryKey(id);// int i = 1/0; // 测试事物的回滚 } @Transactional (rollbackFor = BaseBusinessException.class) public void addAndDeleteUser(String username, int id) throws BaseBusinessException{ userMapper.addUser(username); this.m1(); userMapper.deleteByPrimaryKey(id); } private void m1() throws BaseBusinessException { throw new BaseBusinessException("xxx"); } public int insertUser(User user) { return this.userMapper.insert(user); }}

这种事务配置方式,不需要我们书写pointcut表达式,而是我们在需要事务的类上进行注解。但是如果我们自己来写切面的代码时,还是要写pointcut表达式

下面看一个例子(自己写切面逻辑):

首先去扫描 @Aspect 注解定义的 切面

切面代码:

import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;@Aspect // for aop@Component // for auto scan@Order(0)  // execute before @Transactionalpublic class DataSourceInterceptor {
@Pointcut("execution(public * net.aazj.service..*.get*(..))") public void dataSourceSlave(){}; @Before("dataSourceSlave()") public void before(JoinPoint jp) { DataSourceTypeManager.set(DataSources.SLAVE); }}

们使用到了 @Aspect 来定义一个切面;@Component是配合<context:component-scan/>,不然扫描不到;@Order定义了该切面切入的顺序 ,因为在同一个切点,可能同时存在多个切面,那么在这多个切面之间就存在一个执行顺序的问题。该例子是一个切换数据源的切面,那么他应该在 事务处理 切面之前执行,所以我们使用 @Order(0) 来确保先切换数据源,然后加入事务处理。@Order的参数越小,优先级越高,默认的优先级最低

3.3 切点表达式(pointcut)

上面我们看到,无论是 风格的配置,还是 @Aspect 风格的配置,切点表达式都是重点。都是我们必须掌握的。

  1. pointcut语法形式(execution):
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

带有 ? 号的部分是可选的,所以可以简化成: ret-type-pattern name-pattern(param_pattern) 返回类型,方法名称,参数三部分来匹配 。

配置起来其实也很简单: * 表示任意返回类型,任意方法名,任意一个参数类型; .. 连续两个点表示0个或多个包路径,还有0个或多个参数 。就是这么简单。看下例子:

execution(* net.aazj.service...get(..)) :表示net.aazj.service包或者子包下的以get开头的方法,参数可以是0个或者多个(参数不限);

execution(* net.aazj.service.AccountService.*(..)): 表示AccountService接口下的任何方法,参数不限;

注意这里,将类名和包路径是一起来处理的,并没有进行区分,因为类名也是包路径的一部分。

参数param- pattern 部分比较复杂: () 表示没有参数,(..)参数不限,(*,String) 第一个参数不限类型,第二参数为String .

  1. within() 语法:

within()只能指定(限定)包路径(类名也可以看做是包路径),表示某个包下或者子报下的所有方法:

within(net.aazj.service.), within(net.aazj.service..),within(net.aazj.service.UserServiceImpl.*)

  1. this() 与 target():

this是指代理对象,target是指被代理对象(目标对象)。所以 this() 和 target() 分别限定 代理对象的类型和被代理对象的类型:

this(net.aazj.service.UserService): 实现了UserService的代理对象(中的所有方法);

target (net.aazj.service.UserService): 被代理对象 实现了UserService(中的所有方法);

  1. args():

限定方法的参数的类型:

args(net.aazj.pojo.User): 参数为User类型的方法。

  1. @target(), @within(), @annotation(), @args():

这些语法形式都是针对注解的 ,比如 带有某个注解的 类 , 带有某个注解的 方法 , 参数的类型 带有某个注解 :

@within(org.springframework.transaction.annotation.Transactional)

@target(org.springframework.transaction.annotation.Transactional)

两者都是指被代理对象 类 上有 @Transactional 注解的(类的所有方法),(两者似乎没有区别???)

@annotation(org.springframework.transaction.annotation.Transactional): 方法 带有 @Transactional 注解的所有方法

@args(org.springframework.transaction.annotation.Transactional): 参数的类型 带有 @Transactional 注解 的所有方法
6. bean(): 指定某个bean的名称

bean(userService): bean的id为 “userService” 的所有方法;

bean(*Service): bean的id为 “Service”字符串结尾的所有方法;

另外注意上面这些表达式是可以利用 ||, &&, ! 进行自由组合的。比如:execution(public * net.aazj.service..*.getUser(..)) && args(Integer,..)

  1. 向注解处理方法传递参数

有时我们在写注解处理方法时,需要访问被拦截的方法的参数。此时我们可以使用 args() 来传递参数,下面看一个例子:

@Aspect@Component // for auto scan//@Order(2)public class LogInterceptor {       @Pointcut("execution(public * net.aazj.service..*.getUser(..))")    public void myMethod(){};    @Before("myMethod()")    public void before() {        System.out.println("method start");    }     @After("myMethod()")    public void after() {        System.out.println("method after");    }     @AfterReturning("execution(public * net.aazj.mapper..*.*(..))")    public void AfterReturning() {        System.out.println("method AfterReturning");    }     @AfterThrowing("execution(public * net.aazj.mapper..*.*(..))")//  @Around("execution(public * net.aazj.mapper..*.*(..))")    public void AfterThrowing() {        System.out.println("method AfterThrowing");    }     @Around("execution(public * net.aazj.mapper..*.*(..))")    public Object Around(ProceedingJoinPoint jp) throws Throwable {        System.out.println("method Around");        SourceLocation sl = jp.getSourceLocation();        Object ret = jp.proceed();        System.out.println(jp.getTarget());        return ret;    }     @Before("execution(public * net.aazj.service..*.getUser(..)) && args(userId,..)")    public void before3(int userId) {        System.out.println("userId-----" + userId);    }      @Before("myMethod()")    public void before2(JoinPoint jp) {        Object[] args = jp.getArgs();        System.out.println("userId11111: " + (Integer)args[0]);        System.out.println(jp.getTarget());        System.out.println(jp.getThis());        System.out.println(jp.getSignature());        System.out.println("method start");    }   }

方法:

@Before("execution(public * net.aazj.service..*.getUser(..)) && args(userId,..)")    public void before3(int userId) {        System.out.println("userId-----" + userId);    }

它会拦截 net.aazj.service 包下或者子包下的getUser方法,并且该方法的第一个参数必须是int型的, 那么使用切点表达式args(userId,..) 就可以使我们在切面中的处理方法before3中可以访问这个参数。

before2方法也让我们知道也可以通过 JoinPoint 参数来获得被拦截方法的参数数组。 JoinPoint 是每一个切面处理方法都具有的参数, @Around 类型的具有的参数类型为ProceedingJoinPoint。通过 JoinPoint或者 ProceedingJoinPoint 参数可以访问到被拦截对象的一些信息(参见上面的 before2 方法)。


转载地址:http://sxqli.baihongyu.com/

你可能感兴趣的文章
[转]在ASP.NET 2.0中操作数据::创建一个数据访问层
查看>>
Linux命令之chmod详解
查看>>
【java小程序实战】小程序注销功能实现
查看>>
JVM内存模型详解
查看>>
(六) Git--标签管理
查看>>
建造者模式(Builder)-设计模式(三)
查看>>
Verilog编程网站学习——门电路、组合电路、时序电路
查看>>
android——学生信息显示和添加
查看>>
Android——ImageSwitcher轮流显示动画
查看>>
Android——利用手机端的文件存储和SQLite实现一个拍照图片管理系统
查看>>
图像调优1:清晰度相关参数MTF,SFR,MTF50,MTF50P 以及TVL的概念以及换算说明
查看>>
罗永浩欲直播带货,京东说可以帮忙联系
查看>>
B站,正在变成下一个“公众号”?
查看>>
小米启动安心服务月 手机家电产品可免费清洁保养
查看>>
刘作虎:一加新品将全系支持 5G
查看>>
滴滴顺风车上线新功能,特殊时期便捷出行
查看>>
不会延期!iPhone 12S预计如期在9月发售:升级三星LTPO屏幕
查看>>
腾讯物联网操作系统TencentOS tiny线上移植大赛,王者机器人、QQ公仔、定制开发板等礼品等你来拿 !
查看>>
为云而生,腾讯云服务器操作系统TencentOS内核正式开源
查看>>
腾讯汤道生:开源已成为许多技术驱动型产业重要的创新推动力
查看>>