面向切面编程、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. 快速读写模板(int)

    一.快速读入模板(int) inline int read(int x){ char ch=getchar(); int x=0,f=1; while(ch>='9'||ch<='0'){ ...

  2. 清北学堂—2020.1提高储备营—Day 4 morning(数论)

    qbxt Day 4 morning --2020.1.20 济南 主讲:李奥 目录一览 1.一些符号与基本知识 2.拓展欧几里得,逆元与欧拉定理 3.线性筛法与积性函数(非重点) 总知识点:数论 一 ...

  3. C# WPF聊天界面(3/3)

    微信公众号:Dotnet9,网站:Dotnet9,问题或建议:请网站留言, 如果对您有所帮助:欢迎赞赏. C# WPF聊天界面(3/3) 阅读导航 本文背景 代码实现 本文参考 1.本文背景 系列文章 ...

  4. 通过CSS3属性值的变化实现动画效果+触发这些动画产生交互

    css3过渡 transition 兼容性:IE10+ transition: none | all | property 默认为none all 表示所有属性过渡 property 指定属性值,如c ...

  5. Spring Boot源码(五):BeanFactoryPostProcessor和BeanPostProcessor

    BeanFactoryPostProcessor是spring BeanFactory加载Bean后调用, BeanPostProcessor是Bean初始化前后调用. BeanFactoryPost ...

  6. 虚拟机(linux)怎么上网

    问题描述:本机并没有显示虚拟机(linux)的虚拟网卡,那能不能用虚拟机上网呢,如果要让本机显示出虚拟机的虚拟网卡会有一万步各种安装卸载,那么,在现有条件下可不可以上网呢,答案是可以的. 解决方案: ...

  7. win下的终端使用指南

    win下的终端使用指南 win 下的命令行工具是真的难用 . 具体的难用就不形容了 . 有了 PowerShell 也没觉得好用 . 还是喜欢Linux的终端,及Bash命令. 替换方案 比较好的替换 ...

  8. bugkuCTF-管理员系统(IP伪造)

    题目地址:http://123.206.31.85:1003/ 登进去是一个管理员后台登录的样子 试了sql的万能密码,发现进不了,而且下面还报错了ip禁止 禁止了我们的ip,但是他本地的ip肯定没有 ...

  9. 问题 C: To Fill or Not to Fill

    #include <cstdio> #include <vector> #include <algorithm> #include <cmath> us ...

  10. 记PHP使用rtrim()导致获得的数据乱码问题

    一.问题描述: 我用rtrim()函数去除右侧多余的"."号,去除之后,发现乱码. 二.出现原因: 当右侧没有分号,要去除的字符串的十六进制又刚刚好以81结尾,就导致81被去除,从 ...