代理模式
定义一个类后,如果在不修改类的前提下,对类进行方法的扩充,譬如添加方法。可以通过继承这个类,在对这个类的方法进行重写,从而达到不修改类的前提下对类进行增强;
除此之外,还可以使用代理的方式;
代理分为:
-
静态代理
-
动态代理
动态代理又有
1. jdk的动态代理
2. cglib
jdk的动态代理中,会自动创建一个代理类对象,这个代理类对象会实现目标对象的所有接口,所以代理类对象和目标对象是同一等级的;从而得出三个结论:- 目标对象至少实现一个接口
- 代理对象无法调用目标对象的类中有自己的方法
- 代理对象不能强转成目标对象类型,只能转成实现的接口类型
cglib:
jdk的动态代理中的目标对象必须实现至少一个接口,那如果我所需要代理的类不实现任何接口呢,那就需要用到clib动态的代理;
cgllib是通过自动创建目标类的子类,然后在子类中修改方法,从而实现对目标对象的功能扩展;
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展间java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截);
Cglib包的底层是通过使用一个小块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
cglib代理实现方法:- 需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,直接引入spring-core-3.2.5.jar是不行的,因为这个核心包与其他包有依赖关系
- 引入功能包后就可以在内存中动态创建实例
- 代理的类不能为final,否则报错;因为final修饰的类不能被继承
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
cglib的具体使用方法:
创建目标对象类Man.java
public class Man{
public void eat() {
System.out.println("喝酒");
}
}
创建代理工厂ProxyFactory.java
public class ProxyFactory implements MethodInterceptor{
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object newInstance() {
// 工具类对象
Enhancer hancer = new Enhancer();
// 设置父类
hancer.setSuperclass(target.getClass());
// 回调,其实就是调用 MethodInterceptor接口实现类的intercept方法
hancer.setCallback(this);
// 返回创建的代理类
return hancer.create();
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
before();
arg1.invoke(target, arg2);
after();
return null;
}
public void before() {
System.out.println("抽烟");
}
public void after() {
System.out.println("烫头");
}
}
测试:
public class Test {
public static void main(String[] args) {
Man man = new Man();
ProxyFactory proxyFactory = new ProxyFactory(man);
Man newInstance = (Man)proxyFactory.newInstance();
newInstance.eat();
}
}
AOP面向切面编程
Aop(Aspect Oriented Programing)面向切面编程,它是一种编程思想,它采用的横向抽取机制,取代了传统的继承纵向继承体系重复性代码(性能监视、事务管理,安全检查,缓存)
普通AOP开发
- JoinPoint(连接点)
- PointCut(切入点)
- Advice(通知/增强)
- Introduction(引介)
编写目标类Man.java
public class Man{
public void eat() {
System.out.println("喝酒");
}
}
1. 前置通知:
前置通知,即能够实现在目标类执行之前增加一些方法
前置通知需实现MethodBeforeAdvice接口,并实现它的before方法,在这个方法中可以定义需要在目标类执行之前的添加的功能;
public class MyBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("抽烟");
}
}
当然,在定义了前置通知的类的后,为了能时目标类执行之前执行添加的功能,应该要搭建一个桥梁,连接这两个类;所以需要在配置文件中搭建这个这个桥梁。
首先,在applicationContext.xml文件中声明目标类对象以及前置增强类对象,然后搭建这两个对象之间的强梁;
<bean id="man" class="com.sxt.bean.Man"></bean>
<bean id="myBeforeAdvice" class="com.sxt.bean.MyBeforeAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.sxt.bean.Man.*())" id="pc"/>
<!--advice-ref的值是前置增强对象的id,pointcut-ref的值是aop:pointcut标签的ID-->
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="pc"/>
</aop:config>
2. 后置通知
与前置通知的意义是一样的,只不过后置增强是在目标类执行之后添加的功能;
后置通知需要实现AfterReturningAdvice接口,并且实现他的afterReturning类
public class MyAfterAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("烫头");
}
}
定义了后置通知的类后也需要与目标类搭建桥梁,首先,在applicationContext.xml文件中声明目标类对象以及后置通知类对象,然后搭建这两个对象之间的强梁;
<bean id="man" class="com.sxt.bean.Man"></bean>
<bean id="myAfterAdvice" class="com.sxt.bean.MyAfterAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.sxt.bean.Man.*())" id="pc"/>
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="pc"/>
<aop:advisor advice-ref="myAfterAdvice" pointcut-ref="pc"/>
</aop:config>
3. 环绕通知
环绕通知可以视为前置通知和后置通知的叠加,其效果是一样的
环绕通知类需要实现MethodInterceptor接口,并且实现它的invoke方法;
注意:此处的MethodInterceptor接口是org.aopalliance.intercept包下的
public class MyArroundAdvice implements MethodInterceptor{
/*
* arg0.proceed();的作用是执行目标对象的方法
*/
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("抽烟");
arg0.proceed();
System.out.println("烫头");
return null;
}
}
环绕通知在applicationContext.xml文件中的配置如下
<bean id="man" class="com.sxt.bean.Man"></bean>
<bean id="myArroundAdvice" class="com.sxt.bean.MyArroundAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.sxt.bean.Man.*())" id="pc"/>
<aop:advisor advice-ref="myArroundAdvice" pointcut-ref="pc"/>
</aop:config>
4. 异常通知
异常通知是在目标对象执行过程中发生了异常后,需要添加的功能;
先定义一个异常通知类,这个类需要实现ThrowsAdvice接口,这个接口是空的,没有需要实现的方法。为了 添加需要的功能,需要在这个类中写一个方法。但是,这里需要注意,虽然没有实现方法,可以写一个方法,但是这个方法的名称必须为afterThrowing,并且需要接受一个Throwable对象,否则就会报错
public class MyExceptionAdvice implements ThrowsAdvice{
public void afterThrowing(Throwable tw) throws Throwable {
System.out.println("====发生了异常======");
}
}
异常通知在applicationContext.xml文件中的配置如下
<bean id="man" class="com.sxt.bean.Man"></bean>
<bean id="myExceptionAdvice" class="com.sxt.bean.MyExceptionAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.sxt.bean.Man.*())" id="pc"/>
<aop:advisor advice-ref="myExceptionAdvice" pointcut-ref="pc"/>
</aop:config>
总结:
由以上的各段代码可以的到一个规律,普通AOP开发的配置文件的方法,不同的通知,其配置的形式都是一样的;
每一个通知类都是实现了一个接口,并且实现了它的一个方法,在这个方法中定义功能;但异常通知是一个意外,它所实现的接口并没有需要实现的方法,所以这个方法需要自己写。