AOP代理模式
概念
代理模式:代理模式的英文叫做Proxy或Surrogate,
所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
抽象主题角色:声明了真实主题和代理主题的共同接口,这样一来在任何可以使用真实主题的地方都可以是使用代理主题
客户端----代理对象(代理方法)---目标对象(真是方法)
模拟代理实现 操作商城某些界面(主页 购物车 订单)
定义userService方法
public interface UserService { /** * 查询关于主页信息 */ public void queryIndexInfo(int page); /** * 查询关于购物车界面 */ public void queryCartInfo(String userId); /** * 查询订单信息的界面 */ public void queryOrderInfo(String userId); }
定义目标类的方法
public class UserServiceImpl implements UserService { @Override public void queryIndexInfo(int page) { System.out.println("查询第" + page + "主页信息"); } @Override public void queryCartInfo(String userId) { System.out.println("查询" + userId + "购物车信息"); } @Override public void queryOrderInfo(String userId) { System.out.println("查询" + userId + "订单信息"); }
}
定义代理类对象
public class UserServiceProxy implements UserService { UserService userService = new UserServiceImpl(); @Override public void queryIndexInfo(int page) { userService.queryIndexInfo(page); } @Override public void queryCartInfo(String userId) { userService.queryCartInfo(userId); } @Override public void queryOrderInfo(String userId) { userService.queryOrderInfo(userId); }
测试代理类方法
@Test public void test() { // 定义代理类对象 UserService userService = new UserServiceProxy(); // 调用代理类对象的方法 userService.queryIndexInfo(1); // 查询购物车信息 userService.queryCartInfo("2"); // 查询订单信息 userService.queryOrderInfo("3"); }
需求:在查询购物车,订单操作之前判断用户是否登陆
@Override
public void queryCartInfo(String userId) {
checkUserLogin();
userService.queryCartInfo(userId);
}
@Override
public void queryOrderInfo(String userId) {
checkUserLogin();
userService.queryOrderInfo(userId);
}
private void checkUserLogin() {
System.out.println("执行与用户相关工作----验证用户是否登陆----");
}
JDK代理
使用jdkProxy进行替换
public class JDKProxy implements InvocationHandler { /** * 目标对象 */ private Object targetObject; /** * 定义一个方法--根据目标对象创建代理对象 * * @param target * @return */ public Object createProxyWithTarget(Object targetObject) { this.targetObject = targetObject; /** * 类加载器 参数一:类加载器 参数二:设置代理类 参数三 回调接口 */ Object newProxyInstance = Proxy.newProxyInstance(targetObject .getClass().getClassLoader(), targetObject.getClass() .getInterfaces(), this); return newProxyInstance; } /** * proxy 生成的代理对象 method 调用的方法 args 方法参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 判断方法中是否包含Cart和Order,如果包含,就进行验证 if (method.getName().contains("Cart") || method.getName().contains("Order")) { checkLogin(); } // 获取方法,对目标对象执行该方法 Object object = method.invoke(this.targetObject, args); return object; } private void checkLogin() { System.out.println("检查是否登陆"); }
}
测试
@Test public void test() { UserService userService = new UserServiceImpl(); JDKProxy jdkProxy = new JDKProxy(); UserService userServiceProxy = (UserService) jdkProxy.createProxyWithTarget(userService); userServiceProxy.queryCartInfo("1"); }
AOP中常用的概念
Aspect(切面): 是通知和切入点的结合,通知和切入点共同定义了关于切面的全部内容--
-它的功能、在何时和何地完成其功能
joinpoint(连接点):所谓连接点是指那些被拦截到的点。
在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.
Pointcut(切入点):所谓切入点是指我们要对哪些joinpoint进行拦截的定义.
通知定义了切面的”什么”和”何时”,切入点就定义了”何地”.
Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.
通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
Target(目标对象):代理的目标对象
Weaving(织入):是指把切面应用到目标对象来创建新的代理对象的过程.
切面在指定的连接点织入到目标对象
Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
target
目标对象,真实对象如上述示例中的goodsServiceImpl
Proxy
代理对象 如JDKProxy
jointpoint
连接点 目标对象中定义的所有方法,改方法就是连接点 queryIndexInfo queryCartInfo queryOrderInfo
PointCut
切入点 只需要拦截的某些方法,被拦截的方法就是切入点, 如queryCartInfo queryOrderInfo
Advice
通知,通常指方法,切入点需要做的事情,如checkLogin();
Aspect()
切面,是通知和切入点的结合,通知和切入点共同定义了关于切面的全部内容--它的功能,在何时何地完成其功能。 通知时一个方法,需要放置到某个类中,该类就是切面,如JDKProxy
Weaving
织入 :是指把切面应用到目标对象来创建新的代理对象的过程, 切面在指定的连接点织入到目标对象
Introduction
引入,在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法 或者field
使用CGLIB生成代理(了解)
//目标对象有实现的接口 goodsServiceImpl 使用jdk代理生成代理对象
//如果没有实现的接口,使用Cglib生成代理对象
public class CGlibProxy implements MethodInterceptor {
/**
* 目标对象
*/
private Object targetObject;
/**
* 定义一个方法--根据目标对象创建代理对象
*
* @param target
* @return
*/
public Object createProxyWithTarget(Object targetObject) {
this.targetObject = targetObject;
// 生成代理对象的增强者
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(targetObject.getClass().getClassLoader());
// 设置代理对象的增强类
enhancer.setSuperclass(targetObject.getClass());
// 设置回调
enhancer.setCallback(this);
// 创建代理对象
Object create = enhancer.create();
return create;
}
private void checkLogin() {
System.out.println("检查是否登陆");
}
/**
* proxy 代理对象
* method 调用的代理对象的方法
* args 参数
* methodProxy 方法的代理对象
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
// 判断方法中是否包含Cart和Order,如果包含,就进行验证
if (method.getName().contains("Cart")
|| method.getName().contains("Order")) {
checkLogin();
}
// 获取方法,对目标对象执行该方法
Object object = method.invoke(this.targetObject, args);
return object;
}
}
spring面向切面的编程(XML方式)
Spring提供2个代理模式,一个是jdk代理,另一个cglib代理
1.若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
2.若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。注意:开发时尽量使用接口的编程,
(1)对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统。
(2)标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的。
(3) spring只支持方法连接点,不支持属性的连接点
切面编程配置
导入jar包
com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.tools-1.6.6.RELEASE.jar spring-aop-3.2.0.RELEASE.jar spring-aspects-3.2.0.RELEASE.jar
配置文件声明
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
创建目标对象
<bean name="userService" class="com.example.proxy.demo5.UserServiceImpl"></bean>
创建切面类
//对于joinPoint适应于各种通知,可以获取通知中的一些信息 public void checkLogin(JoinPoint joinPoint) { // 代理对象 Class<? extends Object> proxy = joinPoint.getThis().getClass(); // 目标对象 Class<? extends Object> class1 = joinPoint.getTarget().getClass(); // 获取通知的方法 String name = joinPoint.getSignature().getName(); // 获取参数 Object[] args = joinPoint.getArgs(); }
声明切面对象
<bean name="loginAspect" class="com.example.proxy.demo5.LoginAspect"></bean>
声明切面
<!-- 配置切面 --> <aop:config> <!-- id随便定义,引用切面对象 --> <aop:aspect id="check" ref="loginAspect"> <!--导入切入点 指定id以及切入点的表达式 --> <aop:pointcut expression="execution(void com.example.proxy.demo5.UserServiceImpl.queryCartInfo(java.lang.String))" id="cart" /> <!-- 指定前置通知,引用切入点id --> <aop:before method="checkLogin" pointcut-ref="cart" /> </aop:aspect> </aop:config>
- *
表达式详解
表达式格式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) execution表达式支持5个参数,带?表示可选,即不是必须填写的,但是非?号必须填写,如果每个参数中间使用空格分开 execution(void com.example.proxy.demo5.UserServiceImpl.queryCartInfo(java.lang.String))
示例
public void com.example.proxy.demo5.UserServiceImpl. queryCartInfo(java.lang.String)
一:modifiers-pattern? 方法的修饰符(非必填项)
表示修饰符为public(非必填项)
二:ret-type-pattern 表示返回类型(必填项)
void:表示返回类型无返回值 java.lang.String:表示返回类型是String类型 *:表示任意类型
三:declaring-type-pattern? 表示包及其包中的类(不表示方法的名称)(非必填项)
com.example.proxy.demo5 标识包 UserServiceImpl 表示类 void *.queryCartInfo(java.lang.String) *:表示任意包中任意类,类中的queryCartInfo的方法
四:name-pattern(param-pattern):表示方法的名称,括号表示方法的参数(名称模式)(必填项)
void com.example.proxy.demo5.UserServiceImpl.*(*,*) *(*,*):表示所有的方法, 2个任意类型的参数 void com.example.proxy.demo5.UserServiceImpl.*(..) *(..):表示所有的方法,任意参数 void com.example.proxy.demo5.UserServiceImpl.*() *():表示所有的方法,没有参数
五:throws-pattern?异常(非必填项)(忽略)
六:示例:
任意公共方法的执行: execution(public * *(..)) 任何一个名字以“set”开始的方法的执行: execution(* set*(..)) AccountService接口定义的任意方法的执行: execution(* com.xyz.service.AccountService.*(..)) 在service包中定义的任意方法的执行: execution(* com.xyz.service.*.*(..)) 在service包或其子包中定义的任意方法的执行: execution(* com.xyz.service..*.*(..)) 在service包中的任意连接点(在Spring AOP中只是方法执行): within(com.xyz.service.*) 在service包或其子包中的任意连接点(在Spring AOP中只是方法执行): within(com.xyz.service..*) 实现了AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行): this(com.xyz.service.AccountService) 'this'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得代理对象在通知体内可用。 实现AccountService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行): target(com.xyz.service.AccountService) 'target'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得目标对象在通知体内可用。 任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点(在Spring AOP中只是方法执行) args(java.io.Serializable) 'args'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得方法参数在通知体内可用。 请注意在例子中给出的切入点不同于 execution(* *(java.io.Serializable)): args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个 Serializable类型的参数时候匹配。 目标对象中有一个 @Transactional 注解的任意连接点 (在Spring AOP中只是方法执行) @target(org.springframework.transaction.annotation.Transactional) '@target'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。 任何一个目标对象声明的类型有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行): @within(org.springframework.transaction.annotation.Transactional) '@within'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。 任何一个执行的方法有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行) @annotation(org.springframework.transaction.annotation.Transactional) '@annotation'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。 任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点(在Spring AOP中只是方法执行) @args(com.xyz.security.Classified) '@args'在绑定表单中更加常用:- 请参见后面的通知一节中了解如何使得注解对象在通知体内可用。 任何一个在名为'tradeService'的Spring bean之上的连接点 (在Spring AOP中只是方法执行): bean(tradeService) 任何一个在名字匹配通配符表达式'*Service'的Spring bean之上的连接点 (在Spring AOP中只是方法执行): bean(*Service)
各种通知分析
前置通知
在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。 使用场景:校验,事务处理,权限
后置通知
在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回 日志记录
- 异常通知
在方法抛出异常退出时执行的通知。
记录错误信息,监控 最终通知
当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 后置通知+异常通知
环绕通知
包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。 必须有返回值,ProceedingJoinPoint