spring day02——AOP
- AOP面向切面编程的相关概念
- 什么是AOP ?
AOP (Aspect Oriented Programing) 称为:面向切面编程,它是一种编程思想。
AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码的编写方式(例如性能监视、事务管理、安全检查、缓存、日志记录等)。
【扩展了解】AOP 是 OOP(面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构),思想延续 !
什么是OCP:即开闭原则。
参考网站:http://www.cnblogs.com/muzongyan/archive/2010/08/05/1793454.html
AOP 思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 !
切面:需要代理一些方法和增强代码 。
- AOP的应用场景
场景一: 记录日志
场景二: 监控方法运行时间 (监控性能)
场景三: 权限控制
场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
场景五: 事务管理 (调用方法前开启事务, 调用方法后提交或者回滚、关闭事务 )
- Spring AOP编程两种方式
- Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类植入增强代码。
- AsPecJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持。
简单的说,Spring内部支持两套AOP编程的方案:
- Spring 1.2 开始支持AOP编程 (传统SpringAOP 编程),编程非常复杂 ---- 更好学习Spring 内置传统AOP代码
- Spring 2.0 之后支持第三方 AOP框架(AspectJ ),实现另一种 AOP编程 -- 推荐
- AOP编程相关术语
AOP思想编程的机制
AOP的相关术语
Aspect(切面): 是通知和切入点的结合,通知和切入点共同定义了关于切面的全部内容---它的功能、在何时和何地完成其功能
joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.
Pointcut(切入点):所谓切入点是指我们要对哪些joinpoint进行拦截的定义.
通知定义了切面的”什么”和”何时”,切入点就定义了”何地”.
Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
Target(目标对象):代理的目标对象
Weaving(织入):是指把切面应用到目标对象来创建新的代理对象的过程.切面在指定的连接点织入到目标对象
Introduction(引入)(不要求掌握):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
通过案例解释AOP相关概念
需求:UserDao中有5个方法,分别是save()、update()、delete()、find()、login();在访问UserDao的save()、update()、delete()方法之前,进行记录日志的操作。
Aspect切面(类):增强代码 Advice(writeLog方法)和 切入点 Pointcut(save,update,delete) 的结合。换句话说:对哪些方法进行怎样的代码增强。
- 2 AOP编程底层实现机制(了解)
AOP 就是要对目标进行代理对象的创建, Spring AOP是基于动态代理的,基于两种动态代理机制: JDK动态代理和CGLIB动态代理
面试题:动态代理和静态代理区别?
动态代理:在虚拟机内部,运行的时候,动态生成代理类(运行时生成,runtime生成) ,并不是真正存在的类, 一般格式:Proxy$$ (Proxy$$Customer)
静态代理:实际存在代理类 (例如:struts2 Action的代理类 ActionProxy,struts2的拦截器)
附录:什么是ActionProxy
参考:http://www.tuicool.com/articles/RNRRriz
- JDK动态代理
JDK动态代理,针对目标对象的接口进行代理 ,动态生成接口的实现类 !(必须有接口)
【过程要点】:
1、 必须对接口生成代理
2、 采用Proxy对象,通过newProxyInstance方法为目标创建代理对象。
该方法接收三个参数 :
(1)目标对象类加载器
(2)目标对象实现的接口
(3)代理后的处理程序InvocationHandler
使用 Proxy类提供 newProxyInstance 方法对目标对象接口进行代理
参数说明:
loader:定义代理类的类加载器
interfaces:代理类要实现的接口列表
h:指派方法调用的调用处理程序
3、 实现InvocationHandler 接口中 invoke方法,在目标对象每个方法调用时,都会执行invoke
【示例】:需求:对目标对象中存在保存和查询的方法,在执行保存的方法的时候,记录日志
第一步:新建web工程:spring4_d02_c04。创建包com.igeek
第二步:编写业务接口,接口中定义save()和find()的方法。
//接口(表示代理的目标接口)
public interface ICustomerService {
//保存
public void save();
//查询
public int find();
}
第三步:编写业务类,类要实现接口
//实现类
public class CustomerServiceImpl implements ICustomerService{
//保存
public void save() {
System.out.println("客户保存了。。。。。");
}
//查询
public int find() {
System.out.println("客户查询数量了。。。。。");
return 100;
}
}
第四步:使用JDK代理完成
代理工厂:
有三种方案完成JDK动态代理:
方案一:在内部实现new InvocationHandler(),指定匿名类
//专门用来生成jdk的动态代理对象的-通用
public class JdkProxyFactory{
//成员变量
private Object target;
//注入target目标对象
public JdkProxyFactory(Object target) {
this.target = target;
}
public Object getProxyObject(){
//参数1:目标对象的类加载器
//参数2:目标对象实现的接口
//参数3:回调方法对象
/**方案一:在内部实现new InvocationHandler(),指定匿名类*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//如果是保存的方法,执行记录日志操作
if(method.getName().equals("save")){
writeLog();
}
//目标对象原来的方法执行
Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象
return object;
}
});
}
//记录日志
private static void writeLog(){
}
}
方案二:传递内部类的对象,指定内部类
//专门用来生成jdk的动态代理对象的-通用
public class JdkProxyFactory{
//成员变量
private Object target;
//注入target
public JdkProxyFactory(Object target) {
this.target = target;
}
public Object getProxyObject(){
//参数1:目标对象的类加载器
//参数2:目标对象实现的接口
//参数3:回调方法对象
/**方案二:传递内部类的对象,指定内部类*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new MyInvocationHandler());
}
//自己制定内部类:类的内部可以多次使用类型
private class MyInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//如果是保存的方法,执行记录日志操作
if(method.getName().equals("save")){
writeLog();
}
//目标对象原来的方法执行
Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象
return object;
}
}
//记录日志
private static void writeLog(){
System.out.println("增强代码:写日志了。。。");
}
}
方案三:直接使用调用类作为接口实现类,实现InvocationHandler接口
//专门用来生成jdk的动态代理对象的-通用
public class JdkProxyFactory implements InvocationHandler{
//成员变量
private Object target;
//注入target
public JdkProxyFactory(Object target) {
this.target = target;
}
public Object getProxyObject(){
//参数1:目标对象的类加载器
//参数2:目标对象实现的接口
//参数3:回调方法对象
/**方案三:直接使用调用类作为接口实现类,实现InvocationHandler接口*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
//记录日志
private static void writeLog(){
System.out.println("增强代码:写日志了。。。");
}
//参数1:代理对象
//参数2:目标的方法对象
//参数3:目标的方法的参数
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//如果是保存的方法,执行记录日志操作
if(method.getName().equals("save")){
writeLog();
}
//目标对象原来的方法执行
Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象
return object;
}
}
第五步:使用SpringTest.java进行测试
//目标:使用动态代理,对原来的方法进行功能增强,而无需更改原来的代码。
//JDK动态代理:基于接口的(对象的类型,必须实现接口!)
@Test
public void testJdkProxy(){
//target(目标对象)
ICustomerService target = new CustomerServiceImpl();
//实例化注入目标对象
JdkProxyFactory jdkProxyFactory = new JdkProxyFactory(target);
//获取Proxy Object代理对象:基于目标对象类型的接口的类型的子类型的对象
ICustomerService proxy = (ICustomerService)jdkProxyFactory.getProxyObject();
//调用目标对象的方法
proxy.save();
System.out.println("————————————————————————————————————————");
proxy.find();
}
第六步:在控制台查看输出结果
从结果上看出:在保存方法的前面,输入了日志增强。
最后,使用断点查看JDK代理,生成的代理对象
说明
Interface ICustomerService{
//目标接口
}
Class CustomerServiceImpl implements ICustomerService{
//目标类实现接口
}
JDK代理对接口代理
Class $Proxy2 implements ICustomerService{
//JDK代理类是目标接口的实现类
ICustomerService customerService = new CustomerServiceImpl();
public void save() {
writeLog()
customerService.save();
}
public int find() {
int returnValue = customerService.find();
return returnValue;
}
//记录日志
private static void writeLog(){
System.out.println("增强代码:写日志了。。。");
}
}
注意:
JDK动态代理的缺点: 只能面向接口代理,不能直接对目标类进行代理 ,如果没有接口,则不能使用JDK代理。
- Cglib动态代理
Cglib的引入为了解决类的直接代理问题(生成代理子类),不需要接口也可以代理 !
什么是cglib ?
CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
百度百科:参考网站
http://baike.baidu.com/link?url=A19bwp5SinV_KZPV6TMorLe4oGpPVdAHgdRBJZC3HCuVqNm7JaDCnkOpvxdF4bnq-f9Wni4CBm5rZFiygUgHCa
该代理方式需要相应的jar包,但不需要导入。因为Spring core包已经包含cglib ,而且同时包含了cglib 依赖的asm的包(动态字节码的操作类库)
【示例】:需求:对目标对象中存在保存和查询的方法,在执行保存的方法的时候,记录日志
第一步:将spring的核心jar导入进来,因为spring的包,包含了cglib的包
将spring的core包引进来(他包含了cglib)
第二步:创建包com.igeek.cglib,编写业务类,创建类ProductService.java,类不需要实现接口
//没有接口的类
public class ProductService {
public void save() {
System.out.println("商品保存了。。。。。");
}
public int find() {
System.out.println("商品查询数量了。。。。。");
return 99;
}
}
第三步:使用cglib代理,创建类CglibProxyFactory.java
//cglib动态代理工厂:用来生成cglib代理对象
public class CglibProxyFactory implements MethodInterceptor{
//声明一个代理对象引用
private Object target;
//注入代理对象
public CglibProxyFactory(Object target) {
this.target = target;
}
//获取代理对象
public Object getProxyObject(){
//1.代理对象生成器(工厂思想)
Enhancer enhancer = new Enhancer();
// 类加载器
enhancer.setClassLoader(target.getClass().getClassLoader());
//2.在增强器上设置两个属性
//设置要生成代理对象的目标对象:生成的目标对象类型的子类型
enhancer.setSuperclass(target.getClass());
//设置回调方法
enhancer.setCallback(this);
// Callback
//3.创建获取对象
return enhancer.create();
}
//回调方法(代理对象的方法)
//参数1:代理对象
//参数2:目标对象的方法对象
//参数3:目标对象的方法的参数的值
//参数4:代理对象的方法对象
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
//如果是保存的方法,执行记录日志操作
if(method.getName().equals("save")){
writeLog();
}
//目标对象原来的方法执行
Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象
return object;
}
//写日志的增强功能
//Advice通知、增强
//记录日志
private static void writeLog(){
System.out.println("增强代码:写日志了。。。");
}
}
第四步:测试代码,使用SpringTest.java进行测试
//cglib动态代理:可以基于类(无需实现接口)生成代理对象
@Test
public void testCglibProxy(){
//target目标:
ProductService target = new ProductService();
//weave织入,生成proxy代理对象
//代理工厂对象,注入目标
CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(target);
//获取proxy:思考:对象的类型
//代理对象,其实是目标对象类型的子类型
ProductService proxy=(ProductService) cglibProxyFactory.getProxyObject();
//调用代理对象的方法
proxy.save();
System.out.println("————————————————————————————————————————");
proxy.find();
}
第五步:控制台输出结果
最后,使用断点查看cglib代理,生成的代理对象
说明
Class ProductService{
//目标类
}
Cglib对类代理(动态生成)
Class ProductService$$EnhancerByCGLIB$$df9980d0 extends ProductService{
//CGLIB代理类是目标类的子类
ProductService productService= new ProductService();
public void save() {
writeLog()
productService.save();
}
public int find() {
int returnValue = productService.find();
return returnValue;
}
//记录日志
private static void writeLog(){
System.out.println("增强代码:写日志了。。。");
}
}
- 代理知识小结
区别:
- Jdk代理:基于接口的代理,一定是基于接口,会生成目标对象的接口类型的子对象。
- Cglib代理:基于类的代理,不需要基于接口,会生成目标对象类型的子对象。
代理知识总结:
- spring在运行期,生成动态代理对象,不需要特殊的编译器.
- spring有两种代理方式:
1.若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。(默认)
2.若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
- 使用该方式时需要注意:
1.对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,所以spring默认是使用JDK代理。
对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案。
2.标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的。
3.spring只支持方法连接点:不提供属性接入点,spring的观点是属性拦截破坏了封装。
面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果。
提示:
- Spring AOP 优先对接口进行代理 (使用Jdk动态代理)
- 如果目标对象没有实现任何接口,才会对类进行代理 (使用cglib动态代理)