如果说面向对象编程是关注将需求功能划分为不同的并且相对独立,封装良好的类,并让它们有着属于自己的行为,依靠继承和多态等来定义彼此的关系的话;那么面向切面编程则是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只需要修改这个行为即可。
JDK的动态代理是利用JAVA动态代理:使用java.lang.reflect包中的Proxy类与InvocationHandler接口完成的;
API中Proxy和InvocationHandler的使用和原理解析:http://rejoy.iteye.com/blog/1627405
代理模式
为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。
总结:静态代理在使用时,需要定义接口或是父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。
静态代理存在一个问题:当我们在被代理的类中增加了一个方法,代理类中也要增加相应方法。
JDK动态代理
// 做日志的JDK动态代理对象
public class LogJdkProxy implements InvocationHandler {
private Object target; // 代理的目标对象
public LogJdkProxy(Object target) {
this.target = target;
}
/** 创建代理对象 */
public Object createProxyInstance() {
// 第1个参数设置代码使用的类装载器,一般采用跟目标类相同的类装载器
// 第2个参数设置代理类实现的接口
// 第3个参数设置回调对象,当代理对象的方法被调用时,会调用该参数指定对象的invoke方法
return Proxy.newProxyInstance(//
getClass().getClassLoader(), // 第1个参数
target.getClass().getInterfaces(), // 第2个参数
this); // 第3个参数
}
/**
* @param proxy 目标对象的代理类实例
* @param method 对应于在代理实例上调用接口方法的Method实例
* @param args 传入到代理实例上方法参数值的对象数组
* @return 方法的返回值,没有返回值(void)时是null
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("== Log:开始执行操作,方法名:"+method.getName()+" ==");
Object result = method.invoke(target, args); // 执行原方法,把方法调用委派给目标对象
System.out.println("== Log:操作执行完毕 ==");
return result;
}
}
a) JAVA动态代理是使用java.lang.reflect包中的Proxy类与InvocationHandler接口完成的;
b) 要使用JDK动态代理,必须要定义接口(因为动态代理生成的代理类继承了Proxy类,java的单继承模式和代理类和被代理类必须实现同一接口的规定,所以必须定义接口);
c) JDK动态代理将会拦截所有public的方法(因为只能调用接口中定义的方法,在代理类和被代理类共有的接口中定义相应的被代理的函数),这样即使在接口中增加了新的方法,不用修改代码也会被拦截;
d) 如果只想拦截一部分方法,可以在invoke方法中对要执行的方法名进行判断。
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。
因为代理类和被代理类必须实现相同的接口,代理类本身继承了Proxy类,所以必须使用接口。
原对象
// 此类不能是final的,否则不能有子类,CGLIB也就不能工作了
public class UserServiceImpl {
// 这是final的方法,不能被子类重写,所以CGLIB不会拦截这个方法
public final void foo1() {
System.out.println(">> final的方法 <<");
}
// 这是static的方法,CGLIB不会拦截这个方法
public static void foo2() {
System.out.println(">> static的方法 <<");
}
// 这是private的方法,CGLIB不会拦截这个方法
private void foo3() {
System.out.println(">> private的方法 <<");
}
// CGLIB会拦截这个方法,可以是public, protected, default的修饰符
public void deleteUser() {
System.out.println(">> 删除一个User <<");
}
}
CGLIB代理类
// CGLIB代理类
public class LogCglibProxy implements MethodInterceptor {
private Object target; // 代理的目标对象
/**
* 创建代理对象
* @param
* @param target 目标对象
*/
public
T createProxyInstance(T target) {
this.target = target;
Enhancer enhancer = new Enhancer(); // 该类用于生成代理对象
enhancer.setSuperclass(target.getClass()); // 设置父类
enhancer.setCallback(this); // 设置回调对象为自己这个对象
return (T) enhancer.create(); // 创建代理对象并返回
}
/**
* @param proxy 目标对象代理类的实例
* @param method 代理类实例上调用父类方法的Method实例
* @param args 传入到方法参数值的对象数组
* @param methodProxy 使用它调用父类的方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println(proxy.getClass());
System.out.println("== Log:开始执行操作,方法名:" + method.getName() + " ==");
Object result = methodProxy.invoke(target, args); // 执行原方法
System.out.println("== Log:操作执行完毕 ==");
return result;
}
}
1. CGLIB可以生成目标类的子类,并重写父类非final修饰符的方法。
2. 要求类不能是final的,要拦截的方法要是非final、非static、非private的。
一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现InvocationHandler接口创建自己的调用处理器 InvocationHandler handler = new InvocationHandlerImpl(...);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
1、通过实现InvocationHandler接口创建自己的调用处理器 InvocationHandler handler = new InvocationHandlerImpl(...);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newProxyInstance方法封装了2~4,只需两步即可完成代理对象的创建。
public static Object newProxyInstance( ClassLoader loader,Class<?>[] interfaces,throws IllegalArgumentException
生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))
代理总结:
spring在运行期创建代理,不需要特殊的编译器。spring有两种代理方式:
a) 若目标对象实现了若干接口,spring就会使用JDK动态代理;
b) 若目标对象没有实现任何接口,spring就使用CGLIB库生成目标对象的子类。
对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统。
切面
切面=切入点+通知
切入点指定要拦截哪些方法
通知(Advice接口的实现类)分为前置通知、后置通知、异常通知、最终通知、环绕通知切入点表达式格式
格式:
execution(
修饰符?
返回值类型
类的全限定名?
方法名(参数)
)
前置通知 before
后置通知 afterReturning
异常通知 afterThrowing
最终通知 after
环绕通知(前、后)
以上所有通知都配置上时,执行结果如下:
1,不抛异常:
== before ==
== 环绕通知(前) ==
>> 删除一个User <<
== after ==
== afterReturning ==
== 环绕通知(后) ==
2,抛异常
== before ==
== 环绕通知(前) ==
>> 查询所有User <<
== after ==
== afterThrows ==
// 定义通知类
public class MyAdvice {
public void before() {
System.out.println("== 前置通知 ==");
}
public void afterReturning() {
System.out.println("== 后置通知 ==");
}
public void throwsException() {
System.out.println("== 异常通知 ==");
}
public void after() {
System.out.println("== 最终通知 ==");
}
// 参数ProceedingJoinPoint与返回值都不是强制性的,但是要写上才能实完整的功能。
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("== 环绕通知(前) ==");
Object result = point.proceed();
System.out.println("== 环绕通知(后) ==");
return result;
}
}
XML文档配置
b) 基于注解
// 定义通知类
@Aspect
public class MyAdvice {
// 在@AspectJ注解风格的AOP中 一个切入点签名通过一个普通的方法来定义
// 1,作为切入点签名的方法必须不能有参数
// 2,一般使用private void修饰,方法体为空
@Pointcut("execution(* cn.itcast..*(..))")
private void myPointcut() {
}
// Before 前置通知 在方法调用前执行
// "myPointcut()" 表示前面定义的切入点
// 也可以写为@Before("execution(* cn.itcast..*(..))")
@Before("myPointcut()")
public void before() {
System.out.println("== 前置通知 ==");
}
// AfterReturning,后置通知 在一个匹配的方法返回的时候执行
// pointcut 使用的切入点
// returning 表示方法的返回值,方法无返回值时,返回值为null
@AfterReturning(pointcut = "myPointcut()", returning = "returnValue")
public void afterReturning(Object returnValue) {
System.out.println("== 后置通知(returnValue=" + returnValue + ") ==");
}
// AfterThrowing 异常通知,在一个方法抛出异常后执行
// pointcut 使用的切入点
// throwing 表示抛出的异常
@AfterThrowing(pointcut = "myPointcut()", throwing = "ex")
public void throwsException(Exception ex) {
System.out.println("== 异常通知(ex=" + ex + ") ==");
}
// @After 最终通知 不论一个方法如何结束,最终通知都会执行
// 最终通知必须准备处理正常和异常两种情况,通常用它来释放资源
@After("myPointcut()")
public void after() {
System.out.println("== 最终通知 ==");
}
// @Around 环绕通知 可以控制方法的执行
// 通知的第一个参数必须是 ProceedingJoinPoint类型
// 调用ProceedingJoinPoint的proceed()方法会导致 后台的连接点方法执行
@Around("myPointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("== 环绕通知(前) ==");
Object result = point.proceed();
System.out.println("== 环绕通知(后) ==");
return result;
}
}
事务
事务:一组操作的执行单元,对于数据库来说,事务管理是一组SQL指令,比如增加、删除、修改等。事务的一致性要求这个事务内的操作必须全部执行成功,如果在此过程中出现了差错,那么这一组操作都将全部回滚。
仅用四个词解释事务(ACID)
atomic(原子性):要么都发生,要么都不发生。
consistent(一致性):数据应该不被破坏。
Isolate(隔离性):用户间操作不相混淆。
Durable(持久性):永久保存,例如保存到数据库中等。
Spring提供了两种事务管理方式:
1)
编程式事务管理
编写程序式的事务管理可以清楚的定义事务的边界,可以实现细粒度的事务控制,比如你可以通过程序代码来控制你的事务何时开始,何时结束等,与后面介绍的声明式事务管理相比,它可以实现细粒度的事务控制。
2) 声明式事务管理
如果你并不需要细粒度的事务控制,你可以使用声明式事务,在Spring中,你只需要在Spring配置文件中做一些配置,即可将操作纳入到事务管理中,解除了和代码的耦合, 这是对应用代码影响最小的选择。当你不需要事务管理的时候,可以直接从Spring配置文件中移除该设置。
声明式事务管理-基于XML配置:
1、在spring配置文件中引入tx命名空间
2、在spring配置文件中做关于事务管理的配置
编写程序式的事务管理可以清楚的定义事务的边界,可以实现细粒度的事务控制,比如你可以通过程序代码来控制你的事务何时开始,何时结束等,与后面介绍的声明式事务管理相比,它可以实现细粒度的事务控制。
2) 声明式事务管理
如果你并不需要细粒度的事务控制,你可以使用声明式事务,在Spring中,你只需要在Spring配置文件中做一些配置,即可将操作纳入到事务管理中,解除了和代码的耦合, 这是对应用代码影响最小的选择。当你不需要事务管理的时候,可以直接从Spring配置文件中移除该设置。
声明式事务管理-基于XML配置:
1、在spring配置文件中引入tx命名空间
2、在spring配置文件中做关于事务管理的配置
声明式事务管理-基于XML配置:
1、在spring配置文件中引入tx命名空间
2、在spring配置文件中做关于事务管理的配置
声明式事务管理-基于注解配置
@Transaction:方法的事务设置将被优先执行;
@Transactional注解可以被继承,即:在父类上声明了这个注解,则子类中的所有public方法也都是会开事务的。