Spring Proxy and Aop
1 代理模式
1.1 介绍
代理(Proxy)是一种设计模式,提供了目标对象另外的访问方式,即通过代理去访问,这样做可以在目标对象实现的基础上,增加额外的操作。
举例:面试者去面试,他们不能直接去找公司老板,而是去找面试官,面试官去做筛选。其中面试官就是代理,而筛选就是增加的额外的操作。
1.2 代理的几种方式
【1】静态代理
静态代理需要代理对象和目标对象实现同样的接口。
举例:模拟保存功能(贴上核心代码)
UserDao.class 直接保存
public class UserDao implements IUserDao{
public void save() {
System.out.println("保存");
}
}
ProxyFactory.class 给保存方法添加事务处理
public class ProxyFactory implements IUserDao{
private IUserDao target;
public ProxyFactory(IUserDao target){
this.target = target;
}
public void save() {
System.out.println("开启事务");
target.save();
System.out.println("提交事务");
}
}
静态代理虽然可以在不修改目标对象同时对目标对象的功能进行拓展,但是代理对象和目标对象都必须实现同样的接口,所以会导致很多的代理类出现,一旦出现新的方法,目标对象和代理对象都需要维护。
【2】动态代理
在动态代理中,目标对象需要实现接口,而代理对象可以不用实现接口。代理对象的生成是利用了 JDKAPI 来实现动态代理的。
举例:模拟保存功能(贴上核心代码)
ProxyFactory.class
//动态代理不需要实现接口
public class ProxyFactory{
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),//获取目标对象的类加载器
target.getClass().getInterfaces(),//获取目标对象的接口
new InvocationHandler() {//事件处理器
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
System.out.println("开启事务");
Object returnValue = method.invoke(target, args);//执行目标方法
System.out.println("提交事务");
return returnValue;
}
}
);
}
}
【3】Cglib 代理
使用 Cglib 代理目标对象和代理对象可以不用实现任何接口。
Cglib 代理也叫子类代理,在内存中构建一个子类对象从而实现对目标对象的功能的拓展。
注意事项:
- 使用 Cglib 代理需要导入
cglib – jar
,如果我们使用 Spring 框架,那么我们只需要导入spring-core.jar
即可 - 代理的类不能使用 final 修饰
- 目标对象的方法如果是 static / final 修饰,那么该方法将不会被拦截。
举例:模拟保存功能(贴上核心代码)
ProxyFactory.class
public class ProxyFactory implements MethodInterceptor {
//维护目标对象
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
//给目标对象创建代理对象
public Object getCglibProxy(){
//创建工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数(执行 intercept 方法)
en.setCallback(this);
//创建子类代理对象
return en.create();
}
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开启事务");
Object returnValue = method.invoke(target, objects);
System.out.println("提交事务");
return returnValue;
}
}
2 Aop 编程
2.1 介绍
Aop(aspect obeject programming)面向切面编程,让关注点代码和业务代码分离。
几个概念:
- 关注点:重复的代码
- 切面:关注点形成的类就叫切面类。面向切面编程就是将重复的代码进行抽离,再在运行的时候在业务方法上动态的植入切面类代码。
- 切入点:执行目标对象方法,可以通过切入点表达式来决定拦截哪些方法。
2.2 注解方式实现 Aop 编程
【1】步骤
- 引入 aop 相关 jar 文件
- spring-aop-3.2.5.RELEASE.jar
- aopalliance.jar
- aspectjweaver.jar
- aspectjrt.jar
- 在 bean.xml 中引入名称空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
- 开启 aop 注解
bean.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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.acey.proxy.f_aop_anno"/>
<!-- 开启aop注解方式 -->
<aop:aspectj-autoproxy expose-proxy="false"></aop:aspectj-autoproxy>
</beans>
4.使用注解
Aop.class (核心代码)
@Component
@Aspect //指定切面类
public class Aop{
//统一指定切入点表达式,拦截哪些方法
@Pointcut("execution(* com.acey.proxy.f_aop_anno.UserDao.*(..))")
public void pointCut() {
}
//每个通知都可以设置单独的表达式:
// @Before("execution(* com.acey.proxy.f_aop_anno.UserDao.*(..))") 或者
// 使用统一的切入点表达式
@Before("pointCut()") //前置通知,在方法执行前执行
public void before_() {
System.out.println("Before");
}
@After("pointCut()") // 后置通知,在方法执行后执行(方法出现异常继续执行)
public void after_() {
System.out.println("After");
}
@AfterReturning("pointCut()") // 返回后通知,目标方法结束后通知(方法出现异常不执行)
public void afterReturning_() {
System.out.println("AfterReturning");
}
@AfterThrowing("pointCut()") //方法出现异常时执行
public void afterThrowing_() {
System.out.println("After");
}
@Around("pointCut()") //环绕通知
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前");
pjp.proceed(); //执行目标方法
System.out.println("环绕后");
}
}
【2】注解说明
-
@Aspect:指定一个类为切面类
-
@Pointcut("execution(* com.acey.proxy.f_aop_anno.UserDao.*(..))"):指定切入点表达式
-
@Before("pointCut_()"):前置通知: 目标方法之前执行
-
@After("pointCut_()"):后置通知:目标方法之后执行(始终执行)
-
@AfterReturning("pointCut_()"):返回后通知: 执行方法结束前执行(异常不执行)
-
@AfterThrowing("pointCut_()"):异常通知: 出现异常时候执行
-
@Around("pointCut_()"):环绕通知: 环绕目标方法执行
2.3 xml 配置方式实现 Aop 编程
【1】步骤
- 引入相关 jar 文件
- 引入名称空间
- aop 配置
- 配置切面类(重复代码形成的类)
- aop 配置(拦截哪些方法以及拦截后的通知代码)
bean.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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- dao 实例-->
<bean id="userDao" class="com.acey.proxy.g_aop_xml.UserDao"></bean>
<bean id="orderDao" class="com.acey.proxy.g_aop_xml.OrderDao"></bean>
<!-- 切面类 -->
<bean id="aop" class="com.acey.proxy.g_aop_xml.Aop"></bean>
<!-- Aop 配置-->
<aop:config>
<!-- 切面表达式设置 -->
<aop:pointcut id="pt" expression="
execution(* com.acey.proxy.g_aop_xml.UserDao.*(..))"/>
<!--切面类的应用(通知代码)-->
<aop:aspect ref="aop">
<!-- 前置通知 -->
<aop:before method="before_" pointcut-ref="pt"></aop:before>
<!-- 后置通知-->
<aop:after method="after_" pointcut-ref="pt"></aop:after>
</aop:aspect>
</aop:config>
</beans>
2.4 切入点表达式
【1】切入点表达式的格式:
execution( modifiers-pattern? //修饰符
ret-type-pattern //返回值类型
declaring-type-pattern? // 类型声明
name-pattern(param-pattern)//方法名及参数列表
throws-pattern? //异常列表
)
其中:
- 修饰符:可选,如public、protected;
- 返回值类型:必填,可以是任何类型模式;“*”表示所有类型;
- 类型声明:可选,可以是任何类型模式;
- 方法名:必填,可以使用“*”进行模式匹配;
- 参数列表:
- “()”表示方法没有任何参数;
- “(..)”表示匹配接受任意个参数的方法
- 异常列表:可选,以“throws 异常全限定名列表”声明,异常全限定名列表如有多个以“,”分割,如throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException。
【2】常见的切入点表达式
-
拦截所有 public 方法
execution(public * *(..))
-
拦截所有 save 开头的方法
execution(* save*(..))
-
拦截指定类的指定方法
execution( * com.acey.UserDao.save(..))
-
拦截指定类的所有方法
execution( * com.acey.UserDao.*(..))
-
拦截指定包,以及其子包下所有类的所有方法
execution(* com..*.*(..))
所有实例代码地址 https://github.com/Aceysx/SpringDemo