两个时机:
1.在编译java源代码的时候 ----编译时增强
2.在运行时动态地修改类 ----运行时增强(动态代理)
实现代理的方法
JDK的方法和CGLIB的方法
1.JDK方法:利用java.lang.reflect.Proxy类,但局限在于需要被代理的对象必须实现一个接口,如果被代理对象没有实现任何接口,或者被代理的业务方法没有相应的接口,就无法得到代理对象,这个时候就需要CGLIB方式产生代理对象。
2.CGLIB方法:原理实际上是动态生成被代理类的子类字节码,由于其字节码都是按照jvm编译后的class文件的规范编写的,因而其可以被jvm正常加载并运行,当然他也有局限,如果被代理类的方法被声明final类型,那么Cglib代理是无法正常工作的,因为final类型方法不能被重写。
一.静态代理模式
1.1目标类 创建Subject接口
package com.itcast.aop;
public interface Subject {
void request();
}
1.1.1目标类 创建SubjectTmp1 继承Subject,这是真实的访问类,Proxy类继承Subject这是代理类
package com.itcast.aop;
public class SubjectTpl1 implements Subject {
public void request() {
System.out.println("real subject do request");
}
}
1.2创建代理类 代理对象中request方法返回的其实是subjectTpl1.request()执行的结果,
package com.itcast.aop;
/**
* 代理对象
*/
public class Proxy implements Subject {
private SubjectTpl1 subjectTpl1;
/**
* 真实目标对象需要通过构造方法传进来
* @param subjectTpl1
*/
public Proxy(SubjectTpl1 subjectTpl1) {
this.subjectTpl1 = subjectTpl1;
}
public void request() {
System.out.println("before real request");
try {
result = subjectTpl1.request();
}catch(Exception e){
System.out.println("exception:"+e.getMessage());
throw e;
}finally {
System.out.println("after real request");
}
return result;
}
}
1.3 客户端其实是访问代理类的request
package com.itcast.aop;
public class Client {
public static void main(String[] arg){
//获取代理对象,然后调用代理对象的方法
Subject subject = new Proxy(new SubjectTpl1());
try {
subject.request();
}catch (Exception e){
}
}
}
代理类和目标类都继承同一个接口,然后代理类在模板类方法的结果上加入其它逻辑,就实现了侵入;缺点就是每次增加一个方法,都要在代理类重写一遍。
二 .动态代理
1.基于接口的代理 jdk代理
1.1创建一个代理类,实现InvocationHandler接口
package com.itcast.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 代理类,需要实现InvocationHandler接口
*/
public class JdkProxySubject implements InvocationHandler {
//还是需要引入目标类对象
private SubjectTpl1 subjectTpl1;
public JdkProxySubject(SubjectTpl1 subjectTpl1) {
this.subjectTpl1 = subjectTpl1;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("before");
//调用目标对象的方法,使用反射
result = method.invoke(subjectTpl1, args);
}catch (Exception e){
throw e;
}finally {
System.out.println("after");
return result;
}
}
}
1.2 client端调用也是先获取代理类对象 ,使用java.lang.reflect.Proxy
package com.itcast.aop;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] arg){
//获取代理对象
Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{Subject.class}, new JdkProxySubject(new SubjectTpl1()));
try {
subject.request();
}catch (Exception e){
}
}
}
1.3 jdk动态代理的原理,其实就是通过Proxy.newProxyInstance()动态生成代理类,传入不同的接口,就生成不同的代理类。
2.基于继承的代理Cgllib代理
2.1 生成动态代理类 CglibProxy
package com.itcast.cglib;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* cglib动态代理类
*/
public class CglibProxy implements MethodInterceptor{
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
try {
System.out.println("before cglib");
result = methodProxy.invokeSuper(o,objects);
}catch (Exception e){
throw e;
}finally {
System.out.println("after cglib");
}
return result;
}
}
2.2 客户端调用
import com.itcast.aop.JdkProxySubject;
import com.itcast.aop.Subject;
import com.itcast.aop.SubjectTpl1;
import com.itcast.cglib.CglibProxy;
import org.junit.Test;
import org.springframework.cglib.proxy.Enhancer;
public class CglibTest {
@Test
public void test() throws Exception {
//获取代理类对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SubjectTpl1.class);
enhancer.setCallback(new CglibProxy());
Subject subject = (Subject) enhancer.create();
subject.request();
}
}
三.责任链模式处理多个AOP
1.责任链模式演示
1.1首先新建一个Handler抽象类,所有的操作节点继承它
package com.itcast.chain;
public abstract class Handler {
//下一个链节点
private Handler successor;
public Handler getSuccessor() {
return successor;
}
public void setSuccessor(Handler successor) {
this.successor = successor;
}
//节点要做的事情
public abstract void handlerProcess();
//节点要执行的方法,先执行本节点的事情,如果存在下个节点,则调用下个节点的excute
public void excute(){
this.handlerProcess();
if (this.successor != null) {
this.successor.excute();
}
}
}
1.2 创建操作节点,实现节点自己的逻辑handlerProcess; 可以创建多个节点,HandlerTpl1,HandlerTpl2...
package com.itcast.chain;
public class HandlerTpl1 extends Handler {
public void handlerProcess() {
System.out.println("这是Tpl1的处理逻辑");
}
}
1.3 要实现链式操作,需要实例化链上所有节点,然后按节点顺序传入。
import com.itcast.chain.Handler;
import com.itcast.chain.HandlerTpl1;
import com.itcast.chain.HandlerTpl2;
import org.junit.Test;
public class ChainTest {
@Test
public void run(){
HandlerTpl1 handler1 = new HandlerTpl1();
HandlerTpl2 handler2 = new HandlerTpl2();
//按照节点顺序,把后一个节点传入到前一个节点
handler1.setSuccessor(handler2);
//只要执行第一个节点的excute,就会执行整个链的节点的逻辑
handler1.excute();
}
}
2. 责任链模式改进
2.1引入一个Chain类,管理这条链,传入一个list
package com.itcast.chain;
import java.util.List;
/**
* 通过list来管理链
*/
public class Chain {
//所有节点放到list中,而且必须把list传进来;
private List handlers;
private int index = 0;
public Chain(List handlers) {
this.handlers = handlers;
}
/**
* 每执行一次,就执行下一个节点的excute,并且把游标向后移动一位
*/
public void process(){
if (this.index < this.handlers.size() ) {
//根据下标获取节点,执行excute,需要把chain对象传过去,在下个节点执行chain.process
this.handlers.get(index++).excute(this);
}else{
return;
}
}
}
2.2 节点的父抽象类修改为这样
package com.itcast.chain;
public abstract class Handler {
//节点要做的事情
public abstract void handlerProcess();
//节点要执行的方法,
public void excute(Chain chain){
this.handlerProcess();
chain.process();
}
}
2.3 节点类不变
package com.itcast.chain;
public class HandlerTpl1 extends Handler {
public void handlerProcess() {
System.out.println("这是Tpl1的处理逻辑");
}
}
2.4 客户端使用时,只需要操作Chain对象即可
import com.itcast.chain.Chain;
import com.itcast.chain.Handler;
import com.itcast.chain.HandlerTpl1;
import com.itcast.chain.HandlerTpl2;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
public class ChainTest {
@Test
public void run(){
//得到节点的list作为参数,得到对应的Chain对象。
List handlers = Arrays.asList(
new HandlerTpl1(),
new HandlerTpl2()
);
Chain chain = new Chain(handlers);
chain.process();
}
}
四.springAOP spring框架对应AOP实现有写好的jar,方便我们实现AOP编程
原理就是让spring生成代理对象,然后从容器中获取代理对象。
1.需要引入的包
aopalliance aop联盟接口
spring-aop 实现联盟接口的包
org.aspectjrt 用于@Aspect 使用注解实现aop
org.aspectjweaver @Aspect依赖包
2.xml配置实现AOP
2.1创建目标类(同上,SubjectTpl1)
2.2 创建通知类 MyAdvice 自定义一些通知方法
package com.itcast.springAOP;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAdvice {
//前置通知
public void before(){
System.out.println("前置通知");
}
//后置通知
public void afterReturn(){
System.out.println("后置通知 (只出现在没有发生异常)");
}
//环绕通知 常用
//需要ProceedingJoinPoint接口作为参数
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕通知的之前部分");
Object proceed = pjp.proceed();
System.out.println("环绕通知的之后部分");
return proceed;
}
//异常抛出通知
public void afterException(){
System.out.println("异常出现之后的通知");
}
//最终通知
public void after(){
System.out.println("后置通知(不管是否发生异常)");
}
}
2.3 xml配置
2.4 测试类 正常调用就行
@Test
public void run() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
SubjectTpl1 subject = (SubjectTpl1)context.getBean("subjectTpl1", SubjectTpl1.class);
subject.request();
}
这个是通过aop:config,aop:aspect 把通知类和切点关联起来,得到一个切面,我们可以通过注解的方式简化操作
3.注解实现aop
3.1 创建切面类 @Aspect (切点,通知方法都在这个切面类定义)
package com.itcast.springAOP;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 切面类需要@Component注入到容器,@Aspect定义类是切面类
*
*/
@Component
@Aspect
public class MyAspect{
//定义切点,切点名称为doLog
@Pointcut("execution(* com.itcast.aop.Bean1.*(..))")
public void doLog(){}
//after通知
@After("doLog()")
public void after(){
System.out.println("do log");
}
//before通知
@Before("doLog()")
public void before(){
System.out.println("before action");
}
}
3.2 在spring配置文件开启注解扫描,开启aop注释自动代理
3.3 客户端调用(完全按照正常的方式使用)
package com.itcast.aop;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
@Test
public void zhujie(){
ApplicationContext context = new ClassPathXmlApplicationContext("beanAOP.xml");
Bean1 bean1 = (Bean1)context.getBean("bean1",Bean1.class);
bean1.run();
}
}