面向切面编程、AOP手动代理和spring编写代理

一、什么是AOP

1.AOP简介:

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

2.AOP特点:

AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码

经典应用:事务管理、性能监视、安全检查、缓存 、日志等

Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码

AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入

3.AOP实现原理:

aop底层将采用代理机制进行实现。

当目标类实现了某一接口(接口+类) :spring采用 jdk 的动态代理Proxy。

当接口没有实现任何接口(实现类):spring 采用 cglib字节码增强。

4.AOP的术语解释:

1.target:目标类,需要被代理的类。例如:UserService

2.Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法

3.PointCut 切入点:已经被增强的连接点。例如:addUser()

4.advice 通知/增强,增强代码。例如:after、before

5. Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.

6.proxy 代理类

7. Aspect(切面): 是切入点pointcut和通知advice的结合

一个线是一个特殊的面。

一个切入点和一个通知,组成成一个特殊的面。

配和下图去理解这些术语分别代表什么:

二、手动代理的两种方式:

1.JDK的动态代理方式

JDK动态代理 对“装饰者”设计模式 简化。使用前提:必须有接口

①目标类:接口 + 实现类

②切面类:用于存通知 MyAspect

③工厂类:编写工厂生成代理

目标类:接口+实现类

 public interface UserService {
void add();
void delete();
void modify();
}

UserService接口

 public class UserServiceImpl implements UserService {

     public void add() {
System.out.println("添加用户成功!!!");
} public void delete() {
System.out.println("删除用户成功!!!");
}
public void modify() {
System.out.println("编辑用户成功!!!");
}
}

UserService实现类UserServiceImpl

切面类:用于存通知MyAspect

 package cn.itcast.a_jdk_proxy;

 public class MyAspect {
public void myBefore(){
System.out.println("方法执行前"); } public void myAfter(){
System.out.println("方法执行后");
}
}

MyAspact通知

工厂类:

 public class MyProxyFactoryBean {
/*
* 动态代理需要准备参数
* 1.类加载器:可以是本类的类加载器或者是目标对象的类加载器两个类加载器是同一个。
* (静态方法中不能使用this)MyProxyFactoryBean.class.getClassLoader()或者目标对象.getClass().getClassLoader()
* 2.Class[] interfaces:目标对象实现的接口,但是注意只能获取自己的实现的接口但是父接口实现的什么接口却不知道
* 或者 new Class[]{目标对象.class}
* 3.InvocationHandler处理类对象:
* invoke方法参数:参数1 代理对象 参数二 代理对象当前执行的方法的描述对象(反射) 参数三 实际参数
*
*/
public static Object createProxyBean(){
final UserService userService =new UserServiceImpl();//目标对象目标类
final MyAspect myAspect =new MyAspect();//增强的内容,通知 Object proxyObject=Proxy.newProxyInstance(MyProxyFactoryBean.class.getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
myAspect.myBefore();
Object obj=method.invoke(userService, args); myAspect.myAfter(); return obj;
}
}); return proxyObject; } }

工厂类用来生成代理类对象

测试类:

 public class JDKProxyTest {//关于jdk动态代理的测试类

     @Test
public void fun01(){
Object proxyObject=MyProxyFactoryBean.createProxyBean();//得到代理对象
UserService userService=(UserService) proxyObject;//强转
//调用动态代理后的方法
userService.add();
userService.delete();
userService.modify();
} }

测试类

最后代理的userService的类型为:说明这个代理是JDK动态代理

2.CGLIB代理方式:

前提:①没有接口,只有实现类。②导入jar包:

自己导包(了解):

核心:hibernate-distribution-3.6.10.Final\lib\bytecode\cglib\cglib-2.2.jar

依赖:struts-2.3.15.3\apps\struts2-blank\WEB-INF\lib\asm-3.3.jar

spring-core..jar 已经整合以上两个内容

采用字节码增强框架 cglib,在运行时 创建目标类的子类,从而对目标类进行增强。

目标类+切面类+工厂类+测试

目标类代码:

 public class UserServiceImpl {

     public void add() {
System.out.println("添加用户成功!!!");
} public void delete() {
System.out.println("删除用户成功!!!");
}
public void modify() {
System.out.println("编辑用户成功!!!");
}
}

目标类

切面类代码:

 public class MyAspect {
public void myBefore(){
System.out.println("方法执行前"); } public void myAfter(){
System.out.println("方法执行后");
}
}

切面类代码

工厂类代码(重点):

 public class MyProxyFactoryBean {

     public static Object createProxyBean(){
//1.准备目标类(spring 创建对象,IOC)
final UserServiceImpl userServiceImpl =new UserServiceImpl();
//2.准备切面的实例
final MyAspect myAspect = new MyAspect();
//3.生成核心类,CGLIB在运行时,生成指定对象的子类,增强
//3.1 核心类
Enhancer enhancer =new Enhancer();
//3.2确定需要增强的类
enhancer.setSuperclass(userServiceImpl.getClass());
//3.3添加回调函数
enhancer.setCallback(new MethodInterceptor() { //里面有一个拦截方法,用来拦截切入点相当于invoke,前三个参数和jdk invoke
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable { myAspect.myBefore();
Object obj = method.invoke(userServiceImpl, args);
// * 执行代理类的父类 ,执行目标类 (目标类和代理类 父子关系)
//Object obj=methodProxy.invokeSuper(proxy, args);
myAspect.myAfter(); return obj;
}
});
//创建代理类对象
Object obj=enhancer.create();
return obj; }
}

工厂类代码

3.AOP联盟通知类型

AOP联盟为通知Advice定义了org.aopalliance.aop.Advice

Spring按照通知Advice在目标类方法的连接点位置,可以分为5类

• 前置通知 org.springframework.aop.MethodBeforeAdvice

• 在目标方法执行前实施增强

• 后置通知 org.springframework.aop.AfterReturningAdvice

• 在目标方法执行后实施增强

• 环绕通知 org.aopalliance.intercept.MethodInterceptor

• 在目标方法执行前后实施增强

• 异常抛出通知 org.springframework.aop.ThrowsAdvice

• 在方法抛出异常后实施增强

• 引介通知 org.springframework.aop.IntroductionInterceptor

       • 在目标类中添加一些新的方法和属性

我们只需要记特殊一点的环绕通知:环绕通知,必须手动执行目标方法

引介通知我们用不到可以不记

其他几个通知在类中的位置是:

try{
//前置通知
//执行目标方法
//后置通知
} catch(){
//抛出异常通知
}

三、spring的半自动和全自动编写动态代理

1.spring半自动编写动态代理:让spring 创建代理对象,从spring容器中手动的获取代理对象。

前提:

导入jar包:

核心:4+1

AOP:AOP联盟(规范)、spring-aop (实现)

【目标接口】(目标接口可以不要spring代理和手动代理的区别是自动去判断你应该用哪一种底层去实现,有接口和实现就用JDK动态代理反之)+目标类+切面类(通知类)+spring配置容器+测试类

目标接口+目标实现类:

 public interface UserService {
void add();
void delete();
void modify();
}

目标接口

 public class UserServiceImpl implements UserService {

     public void add() {
System.out.println("添加用户成功!!!");
} public void delete() {
System.out.println("删除用户成功!!!");
}
public void modify() {
System.out.println("编辑用户成功!!!");
}
}

目标实现类

切面类:切面类中确定通知,需要实现不同接口,接口就是规范,从而就确定方法名称。采用“环绕通知” MethodInterceptor

 public class MyAspect implements MethodInterceptor {

     public Object invoke(MethodInvocation invocation) throws Throwable {
//环绕通知MethodInterceptor,必须手动执行目标方法
System.out.println("方法执行前!!!"); Object obj=invocation.proceed();//执行目标方法 System.out.println("方法执行后");
return obj;
} }

继承环绕通知的子接口MethodInterceptor 的实现类

spring容器配置:

<!-- 1 创建目标类 -->

<bean id="userServiceId" class="com.itheima.b_factory_bean.UserServiceImpl"></bean>

<!-- 2 创建切面类 -->

<bean id="myAspectId" class="com.itheima.b_factory_bean.MyAspect"></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

-->

 1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans
5 http://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <!-- 目标类 -->
8 <bean id="userService" class="cn.itcast.c_spring_bzdproxy.UserServiceImpl"></bean>
9 <!-- 通知类,增强的类 -->
10 <bean id="myAspect" class="cn.itcast.c_spring_bzdproxy.MyAspect"></bean>
11
12 <!-- FactoryBean 得到特殊的bean -->
13 <bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
14 <!-- 代理实现接口 -->
15 <property name="interfaces" value="cn.itcast.c_spring_bzdproxy.UserService"></property>
16 <!-- 目标类 -->
17 <property name="target" ref="userService"></property>
18 <!-- 用通知增强目标 这里填写的是切面类的id名-->
19 <property name="interceptorNames" value="myAspect"></property>
20 <property name="proxyTargetClass" value="true"></property>
21 </bean>
22 </beans>

测试代码:

 public class SpringProxyTest {//spring半自动的动态代理测试
@Test
public void fun01(){
String xmlPath ="cn/itcast/c_spring_bzdproxy/applicationContext.xml";
ClassPathXmlApplicationContext applicationContext =new ClassPathXmlApplicationContext(xmlPath);
UserService userService = (UserService) applicationContext.getBean("userServiceProxy");//这里填写的是动态代理的ID名称 userService.add();
userService.delete();
userService.modify();
} }

2.spring全自动编写动态代理(spring的配置文件是重点)

 从spring容器获得目标类,如果配置aop,spring将自动生成代理。

 要确定目标类,aspectj 切入点表达式,这里引入一个新的表达式稍后会讲到,导入jar包

spring-framework-3.0.2.RELEASE-dependencies\org.aspectj\com.springsource.org.aspectj.weaver\1.6.8.RELEASE

【目标接口】(目标接口可以不要spring代理和手动代理的区别是自动去判断你应该用哪一种底层去实现,有接口和实现就用JDK动态代理反之)+目标类+切面类(通知类)+spring配置容器+测试类

  目标接口和目标类、切面类与半自动的是一样的这里不再写,重要的是spring容器的配置!

Spring容器的配置:前提导入命名空间如下(这个命名空间的配置信息和P命名空间的命名信息在同一个地方 查询AOP关键字)

<!-- 1 创建目标类 -->

<bean id="userServiceId" class="com.itheima.c_spring_aop.UserServiceImpl"></bean>

<!-- 2 创建切面类(通知) -->

<bean id="myAspectId" class="com.itheima.c_spring_aop.MyAspect"></bean>

<!-- 3 aop编程

3.1 导入命名空间

3.2 使用 <aop:config>进行配置

proxy-target-class="true" 声明时使用cglib代理

<aop:pointcut> 切入点 ,从目标对象获得具体方法

<aop:advisor> 特殊的切面,只有一个通知 和 一个切入点

advice-ref 通知引用

pointcut-ref 切入点引用

3.3 切入点表达式

execution(* com.itheima.c_spring_aop.*.*(..))

选择方法         返回值任意   包             类名任意   方法名任意   参数任意

-->

spring配置信息如下:

 <!-- 目标对象 -->
<bean id="userService" class="cn.itcast.d_spring_qzdproxy.UserServiceImpl"></bean>
<!-- 创建通知类 -->
<bean id="myAspect" class="cn.itcast.d_spring_qzdproxy.MyAspect"></bean> <!-- 使用aop编程,导入命名空间,命名空间的位置详情见笔记
使用 <aop:config>进行配置
proxy-target-class="true" 声明时使用cglib代理
<aop:pointcut> 切入点 ,从目标对象获得具体方法
<aop:advisor> 特殊的切面,只有一个通知 和 一个切入点
advice-ref 通知引用
pointcut-ref 切入点引用
切入点表达式
execution(* com.itheima.c_spring_aop.*.*(..))
选择方法 返回值任意 包 类名任意 方法名任意 参数任意
-->
<aop:config proxy-target-class="true">
<aop:pointcut expression="execution(* cn.itcast.d_spring_qzdproxy.*.*(..))" id="myPointCut"/>
<aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut"/>
</aop:config>
</beans>

测试类:

 public class SpringProxyTest {//spring全自动的动态代理测试
@Test
public void fun01(){
String xmlPath ="cn/itcast/d_spring_qzdproxy/applicationContext.xml";
ClassPathXmlApplicationContext applicationContext =new ClassPathXmlApplicationContext(xmlPath);
UserService userService = (UserService) applicationContext.getBean("userService"); userService.add();
userService.delete();
userService.modify();
} }

day02_1spring3的更多相关文章

随机推荐

  1. 【redis】基于redis实现分布式并发锁

    基于redis实现分布式并发锁(注解实现) 说明 前提, 应用服务是分布式或多服务, 而这些"多"有共同的"redis"; (2017-12-04) 笑哭, 写 ...

  2. 笔记-Git基础

    git配置 git config --global user.name "xxx" //配置用户名 git config --global user.email "xxx ...

  3. FIB表中 Next Hop 的几种状态码(drop/receive/attached/no route)的含义

    以一个例子来说明,假设有如下两个路由器R1,R2,且均配置了到达彼此环回地址的静态路由. (1.1.1.1/24)R1(Gig0/0)(.1)——12.0.0.0/24——(.2)(Gig0/0)R2 ...

  4. BZOJ2005: [Noi2010]能量采集(欧拉函数)

    Description 栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量.在这些植物采集能量后, 栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起. 栋栋的植物种 ...

  5. vim 配置 jedi-vim( ubuntu:15.10 )

    确保 vim 支持 python3 或者 python 如果你已经安装了 vim, 可以通过vim --version, 在输出中找到 +python3 或者 +python 字样的话, 就可以跳过该 ...

  6. [P5748] 集合划分计数 - 生成函数,NTT

    求 \(10^5\) 以内的所有贝尔数:将 \(n\) 个有标号的球划分为若干非空集合的方案数 Solution 非空集合的指数生成函数为 \(F(x)=e^x-1\) 枚举一共用多少个集合,答案就是 ...

  7. 通过sd文件发布的FeatureAccess服务不能查看到图层

    发布服务有两种方法, 1. 用ArcMap --Share As - service --publish a service 此方法可以直接将地图数据发布到ArcGIS  Server 的地图服务中, ...

  8. 框架里增加.env文件的作用

    在实际开发中我们常常遇到这样的问题,就是开发地点不固定,这就造成了我们需要频繁的更改数据库配置,给开发工作造成了麻烦,.env环境文件的出现解决了这个麻烦,我们只需要在不同的工作地点配置好.env文件 ...

  9. 从Windows10中彻底删除【3D对象】文件夹

    Remove "3D object" folder from My Computer Windows Registry Editor Version 5.00 [-HKEY_LOC ...

  10. 关于BaseServlet的使用

    一篇很棒的参考 https://blog.csdn.net/weixin_42425970/article/details/84279257