Spring的AOP

AOP相关简介 

AOP:预编译,在运行期执行的。

动态代理是在不修改源码的情况下,对代码进行相应的增强。可以完成程序功能上松耦合。

AOP和动态代理功能差不多,且不要自己写动态代理的代码了。

AOP松耦合,抽取功能,减少重复代码,提高开发效率,且便于维护。

Spring采用动态代理技术。AspectJ采用编译器植入和类装载期织入。

AOP底层:spring集成了jdk代理技术和gclib代理技术。

jdk代理:目标对象基于目标接口生成代理对象放入运行时的内存中。

cglib代理:为目标对象动态生成子对象,但这个子对象比父对象功能更强大,不是继承是动态生成基于目标对象的对象,放入运行时内存中。

在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式(jdk\cglib)。

 AOP相关术语

Target(目标对象:代理的目标对象

Proxy(代理):一个类被AOP织入增强后,禅城一个结果代理类

JoinPoint(连接点):被拦截到的点(方法)。可以被增强的方法叫做连接点。

Pointcut(切入点):即我们要对哪些JoinPoint进行拦截。

注:切入点是真正被增强的方法,被选中增强的连接点才是切入点。连接点和切入点的关系就像公民和人大代表一样。

Advice(通知/增强):即拦截到JoinPoint后要做的事情。即用于增强功能额外功能的代码。

Aspect(切面):切入点和通知的结合。切面=目标方法+增强方法。

Weaving(织入):把增强用于目标对象来创建新的代理对象的过程。织入是个动词,可以理解为切点和增强(通知)结合的过程就是织入。

AOP技术实现内容

 Spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,则使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

举例

比如日志控制

传统的抽取日志控制代码:在业务方法中还需要引用如save(user)、update(user)之类调用抽取出来的日志控制代码,还是出现耦合。

使用AOP:

将业务方法作为切入点,将日志控制代码作为增强,通过对业务方法使用配置文件或注解,使得在运行业务方法(切点)前,通过spring动态代理技术对切点进行增强的织入,基于目标对象生成代理对象。运行时代理对象的根据增强类型先后,调用增强代码和目标对象的切点方法即可。这样,目标方法和日志控制完全解耦,仅在运行时结合在一起。

快速入门

基于jdk的动态代理

创建一个mavenWeb项目(cglib需要web环境)。

 com.kdy.proxy.jdk包中创建TargetInterface目标类接口

public interface TargetInterface {
    public void save();
}

com.kdy.proxy.jdk包中创建Target(目标类接口实现类)实现TargetInterface接口

public class Target implements TargetInterface {
    @Override
    public void save() {
        System.out.println("save running...");
    }
}

 com.kdy.proxy.jdk包中创建Advice增强类

public class Advice {
    //前置增强
    public void before(){
        System.out.println("before...");
    }
    //后置增强
    public void afterReturning(){
        System.out.println("afterReturning...");
    }
}

 com.kdy.proxy.jdk包中创建测试类ProxyTest

public class ProxyTest {
    public static void main(String[] args) {
        //目标对象
        final Target target = new Target();
        //增强对象
        final Advice advice = new Advice();
        //基于目标对象动态生成代理对象:返回值 就是动态生成的代理对象
        TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),//目标对象类加载器
                target.getClass().getInterfaces(),//目标对象相同的接口字节码对象数组
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        advice.before();//前置增强
                        Object invoke = method.invoke(target, args);//执行目标方法
                        advice.afterReturning();//后置增强
                        return invoke;
                    }
                }
        );
        proxy.save();
    }
}

运行main方法即可。

基于cglib的动态代理

上面项目pom我呢见引入spring依赖(spring依赖中包含cglib包)

        
            org.springframework
            spring-context
            5.0.5.RELEASE
        

com.kdy.proxy.jdk包中创建Target(目标类)

public class Target {
    public void save(){
        System.out.println("save running......");
    }
}

 com.kdy.proxy.jdk包中创建Advice增强类

public class Advice {
    //前置增强
    public void before(){
        System.out.println("before...");
    }
    //后置增强
    public void afterReturning(){
        System.out.println("afterReturning...");
    }
}

 com.kdy.proxy.jdk包中创建测试类ProxyTest

public class ProxyTest {
    public static void main(String[] args) {
        //目标对象
        final Target target = new Target();
        //增强对象
        final Advice advice = new Advice();

        //返回值 就是动态生成的代理对象、基于cglib
        //1、创建增强器
        Enhancer enhancer = new Enhancer();
        //2、设置父类(目标)
        enhancer.setSuperclass(Target.class);
        //3、设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                advice.before();//执行前置
                Object invoke = method.invoke(target, args);//执行目标
                advice.afterReturning();//执行后置
                return invoke;
            }
        });
        //创建代理对象
        Target proxy = (Target)enhancer.create();

        proxy.save();
    }
}

运行main方法即可。

面向切面编程-AOP开发 

基于XML的AOP开发

快速入门

创建mavenWeb项目

引入spring、aspectJ、junit、spring-test依赖

 
        
            org.springframework
            spring-context
            5.0.5.RELEASE
        
        
            org.aspectj
            aspectjweaver
            1.9.5
        
        
            junit
            junit
            4.12
            test
        
        
            org.springframework
            spring-test
            5.0.5.RELEASE
        
    

在com.kdy.aop包中创建TargetInterface接口和Target目标类

public class Target implements TargetInterface{
    public void  save(){
        System.out.println("save running...");
//        int i =  1/0;//异常抛出
    }
}

在com.kdy.aop包中创建MyAspect切面类

public class MyAspect {
    //前置增强
    public void before() {
        System.out.println("before...");
    }
    //后置增强
    public void afterReturning() {
        System.out.println("afterReturning...");
    }
    //环绕增强
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("before around...");//环绕前增强
        Object proceed = pjp.proceed();//切点方法
        System.out.println("after around...");//环绕后
        // 增强
        return proceed;
    }
    //异常抛出增强
    public void afterThrowing() {
        System.out.println("afterThrowing...");
    }
    //最终增强
    public void after() {
        System.out.println("after...");
    }
}

resource下applicationContext配置文件创建目标类的bena和切面类的bean,并配置织入



    
    
    
    
    
    
        
        
            
       
            
            
            
            
            
        
    

切点表达式:

execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 

修饰符可省略,返回值类型、包名、类名、方法名可用*代表任意

包名和类名之间一个点代表当前包下的类,两个点..代表当前包及其子包下的类

参数列表可使用两个点..表示任意个数,任意类型的参数列表

xml中的切点表达式除了写全,还可简写举例如下:

execution(void com.kdy.aop.Target.*(..))      execution(* com.kdy.aop.*.*(..))

execution(* com.kdy.aop..*.*(..))  execution(* com.kdy.aop.*.*(..))  execution(* *..*.*(..))

一般常用   execution(* com.kdy.aop.*.*(..))

通知类型:

前置通知:  用于配置前置通知。指定增强的方法在切入点方法之前执行。
后置通知:配置后置通知。指定增强的方法在切入点方法之后执行。
环绕通知:< aop:around>用于环绕通知。指定增强的方法在切入点方法之前和之后都执行。
异常抛出通知:配置异常抛出通知。指定增强的方法在出现异常时执行。
最终通知:用于配置最终通知。无论增强方式执行是否有异常都会执行。

在test目录中写测试类,注入目标类,直接调用目标类的方法,运行测试用例即可

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    /*
    * 注:这里要注入接口TargetInterface,因为xml中声明的是Target实现类的bean,但经过织入生成代理对象的bean是最终要注入进来的
    * 因为Target有接口所以spring基于jdk来动态代理生成代理对象,这个代理对象也是TargetInterface接口类型的,
    * 所以spring生命周期中最终通过TargetInterface注入时找到的是动态代理对象的bean,将其注入进来
    * */
    @Autowired
    private TargetInterface target;

    @Test
    public void test1(){
        target.save();
    }
}

注:@Autowired注入的类型为接口,就是考虑到spring中aop,虽然类型为实现类也行,但aop的时候必须注入类型为接口,所以通常平时都注入接口类型。若某个注入接口有多实现类,可用配合@Qualify或直接用@Resource。

执行顺序增强类型都是相对切点方法的先后属顺序。

applicationContext.xml织入中抽取切点表达式的写法为:



    
    
    
    
    
    
        
        
            
            
            
            
            
            
            
        
    

基于注解的AOP开发

快速入门

mavenWeb项目,pom文件引入spring、aspectJ、junit、spring-test依赖

 
        
            org.springframework
            spring-context
            5.0.5.RELEASE
        
        
            org.aspectj
            aspectjweaver
            1.9.5
        
        
            junit
            junit
            4.12
            test
        
        
            org.springframework
            spring-test
            5.0.5.RELEASE
        
    

在com.kdy.anoo包中创建Target目标类实现目标类接口TargetInterface,并使用spring注解声明bean。

@Component("target")
public class Target implements TargetInterface {
    public void  save(){
        System.out.println("save running...");
//        int i =  1/0;//异常抛出
    }
}

在com.kdy.anoo中创建MyAspect,并使用spring注解声明bean,并使用@Aspect声明为切面类

@Component("myAspect")
@Aspect//切面类
public class MyAspect {
    //抽取切点表达式
    @Pointcut("execution(* com.kdy.anno.*.*(..))")
    public void myPointcut(){}
    //前置增强
    @Before("myPointcut()")//使用上面抽取的切点表达式
    public void before() {
        System.out.println("before...");
    }
    //后置增强
    @AfterReturning("MyAspect.myPointcut()")//使用上面抽取的切点表达式
    public void afterReturning() {
        System.out.println("afterReturning...");
    }
    //环绕增强
    @Around("execution(* com.kdy.anno.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("before around...");//环绕前增强
        Object proceed = pjp.proceed();//切点方法
        System.out.println("after around...");//环绕后
        // 增强
        return proceed;
    }
    //异常抛出增强
    @AfterThrowing("execution(* com.kdy.anno.*.*(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing...");
    }
    //最终增强
    @After("execution(* com.kdy.anno.*.*(..))")
    public void after() {
        System.out.println("after...");
    }
}

resource下创建applicationContext.xml



    
    
    
    

 test目录下的测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    /*
    * 注:这里要注入接口TargetInterface,因为xml中声明的是Target实现类的bean,但经过织入生成代理对象的bean是最终要注入进来的
    * 因为Target有接口所以spring基于jdk来动态代理生成代理对象,这个代理对象也是TargetInterface接口类型的,
    * 所以spring生命周期中最终通过TargetInterface注入时找到的是动态代理对象的bean,将其注入进来
    * */
    @Autowired
    private TargetInterface target;
    @Test
    public void test1(){
        target.save();
    }
}

其实也可以创建如applicationContext-anno.xml,并复制以上applicationContext.xml中的内容,并在test的测试类中上方的改成@ContextConfiguration("classpath:applicationContext-anno.xml")。

运行测试用例即可。

你可能感兴趣的:(spring,java)