spring ioc和aop的含义

自己整理的一些笔记,如有错误欢迎提出指正,谢谢

1.IOC的定义?

IOC:另外一种说法叫DI(Dependency Injection),即依赖注入,是一种设计思想。

我们通过IOC将相互依赖对象的创建、协调工作交给Spring容器去处理,每个对象只需要关注其自身的业务逻辑关系就可以了。

作用是为了解耦,降低类之间的耦合度,其设计思想就是设计模式的工厂模式,

我们并不需要知道其生产的具体过程,我们只要其产出的对象即可。

其工作流程就是:在Spring容器启动的时候,Spring会把你在application.xml中配置好的bean都初始化,

在你需要调用的时候,把已经初始化的bean分配给你要调用这些bean的类,而不用去创建一个对象的实例。

2.IOC的传值方式有哪些?

一般的是设值传入和构造方法传入。

3.IOC的容器有哪些?

常见的一般是ApplicationContext和BeanFactory,这两个容器的区别请看这里:
http://blog.csdn.net/hi_kevin/article/details/7325554

4.依赖注入的实现方式

就是Java的反射,通俗的来讲就是根据给出的类名来动态地生成对象,用set方法将事先保存在hashmap中的类属性注入到类中。

AOP:
面向切面编程,被定义为促使软件系统实现关注点的分离的技术。

系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。

例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。

AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类,

静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;

动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。

与AspectJ的最大区别在于—Spring AOP的运行时增强,而AspectJ是编译时增强。

Spring AOP使用了AspectJ的Annotation,使用了Aspect来定义切面,使用Pointcut来定义切入点,使用Advice来定义增强处理。

虽然使用了Aspect的Annotation,但是并没有使用它的编译器和织入器。Spring AOP实现原理是JDK 动态代理,在运行时生成代理类。

AOP 代理 = 原来的业务类+增强处理。

这个生成AOP 代理由 Spring 的 IoC 容器负责生成,也由 IoC 容器负责管理。

AOP 代理可以直接使用容器中的其他 Bean 实例作为目标,这种关系可由 IoC 容器的依赖注入提供。

CGLIB(Code Generation Library)它是一个代码生成类库。

它可以在运行时候动态生成某个类的子类。代理模式为要访问的目标对象提供了一种途径,当访问对象时,它引入了一个间接的层。

JDK自从1.3版本开始,就引入了动态代理,并且经常被用来动态地创建代理。

JDK的动态代理唯一限制便是使用动态代理的对象必须实现一个或多个接口。而CGLIB缺不必有此限制。

要想Spring AOP 通过CGLIB生成代理,只需要在Spring 的配置文件引入

<aop:aspectj-autoproxy proxy-target-class="true"/>

1.@AspectJ
AspectJ是一个AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器),可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易. 其实AspectJ单独就是一门语言,它需要专门的编译器(ajc编译器).
Spring AOP 与ApectJ的目的一致,都是为了统一处理横切业务,但与AspectJ不同的是,Spring AOP并不尝试提供完整的AOP功能(即使它完全可以实现),Spring AOP 更注重的是与Spring IOC容器的结合,并结合该优势来解决横切业务的问题,因此在AOP的功能完善方面,相对来说AspectJ具有更大的优势,同时,Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。在AspectJ 1.5后,引入@Aspect形式的注解风格的开发,Spring也非常快地跟进了这种方式,因此Spring 2.0后便使用了与AspectJ一样的注解。请注意,Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器。
所以,大家要明白,Spring AOP虽然是使用了那一套注解,其实实现AOP的底层是使用了动态代理(JDK或者CGLib)来动态植入。

2.Spring AOP - AspectJ注解
谈到Spring AOP,老程序员应该比较清楚,之前的Spring AOP没有使用@Aspect这一套注解和aop:config这一套XML解决方案,而是开发者自己定义一些类实现一些接口,而且配置贼恶心。本文就不再演示了,后来Spring痛下决心,把AspectJ"整合"进了Spring当中,并开启了aop命名空间。现在的Sping AOP可以算是朗朗乾坤。上例子之前,还是把AOP的概念都提一提:

	切点:定位到具体方法的一个表达式
	切面: 切点+建言
	建言(增强):定位到方法后干什么事

网上有很多概念,什么连接点,织入,目标对象,引入什么的。我个人觉得,在Java的Spring AOP领域,完全不用管。别把自己绕晕了。就按照我说的这三个概念就完事了,好了,先来搞个例子:

maven依赖:
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>4.2.3.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.2.3.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>4.2.3.RELEASE</version>
    </dependency>
    //下面这两个aspectj的依赖是为了引入AspectJ的注解
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.6.12</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.6.12</version>
    </dependency>
//Spring AOP底层会使用CGLib来做动态代理
    <dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2</version>
  </dependency>

复制代码小狗类,会说话:

public class Dog {

    private String name;


    public void say(){
        System.out.println(name + "在汪汪叫!...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

切面类:

@Aspect //声明自己是一个切面类
public class MyAspect {
    /**
     * 前置通知
     */
     //@Before是增强中的方位
     // @Before括号中的就是切入点了
     //before()就是传说的增强(建言):说白了,就是要干啥事.
    @Before("execution(* com.zdy..*(..))")
    public void before(){
        System.out.println("前置通知....");
    }
}

这个类是重点,先用@Aspect声明自己是切面类,然后before()为增强,@Before(方位)+切入点可以具体定位到具体某个类的某个方法的方位.
Spring配置文件:

    //开启AspectJ功能.
    <aop:aspectj-autoproxy />

    <bean id="dog" class="com.zdy.Dog" />
    <!-- 定义aspect类 -->
    <bean name="myAspect" class="com.zdy.MyAspect"/>

然后Main方法:

ApplicationContext ac =new ClassPathXmlApplicationContext("applicationContext.xml");
    Dog dog =(Dog) ac.getBean("dog");
    System.out.println(dog.getClass());
    dog.say();

输出结果:

class com.zdy.Dog$$EnhancerBySpringCGLIB$$80a9ee5f
前置通知....
null在汪汪叫!...

说白了,就是把切面类丢到容器,开启一个AdpectJ的功能,Spring AOP就会根据切面类中的(@Before+切入点)定位好具体的类的某个方法(我这里定义的是com.zdy包下的所有类的所有方法),然后把增强before()切入进去.
然后说下Spring AOP支持的几种类似于@Before的AspectJ注解:

前置通知@Before: 前置通知通过@Before注解进行标注,并可直接传入切点表达式的值,该通知在目标函数执行前执行,注意JoinPoint,是Spring提供的静态变量,通过joinPoint 参数,可以获取目标对象的信息,如类名称,方法参数,方法名称等,该参数是可选的。

@Before("execution(...)")
public void before(JoinPoint joinPoint){
    System.out.println("...");
}

后置通知@AfterReturning:
通过@AfterReturning注解进行标注,该函数在目标函数执行完成后执行,并可以获取到目标函数最终的返回值returnVal,当目标函数没有返回值时,returnVal将返回null,必须通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同。请注意,在任何通知中这些参数都是可选的,需要使用时直接填写即可,不需要使用时,可以完成不用声明出来。

@AfterReturning(value="execution(...)",returning = "returnVal")
public void AfterReturning(JoinPoint joinPoint,Object returnVal){
   System.out.println("我是后置通知...returnVal+"+returnVal);
}

异常通知 @AfterThrowing:该通知只有在异常时才会被触发,并由throwing来声明一个接收异常信息的变量,同样异常通知也用于Joinpoint参数,需要时加上即可.

@AfterThrowing(value="execution(....)",throwing = "e")
public void afterThrowable(Throwable e){
  System.out.println("出现异常:msg="+e.getMessage());
}

最终通知 @After:该通知有点类似于finally代码块,只要应用了无论什么情况下都会执行.

@After("execution(...)")
public void after(JoinPoint joinPoint) {
    System.out.println("最终通知....");
}

环绕通知 @Around: 环绕通知既可以在目标方法前执行也可在目标方法之后执行,更重要的是环绕通知可以控制目标方法是否指向执行,但即使如此,我们应该尽量以最简单的方式满足需求,在仅需在目标方法前执行时,应该采用前置通知而非环绕通知。案例代码如下第一个参数必须是ProceedingJoinPoint,通过该对象的proceed()方法来执行目标函数,proceed()的返回值就是环绕通知的返回值。同样的,ProceedingJoinPoint对象也是可以获取目标对象的信息,如类名称,方法参数,方法名称等等

@Around("execution(...)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("我是环绕通知前....");
    //执行目标函数
    Object obj= (Object) joinPoint.proceed();
    System.out.println("我是环绕通知后....");
    return obj;
}

然后说下一直用"…"忽略掉的切入点表达式,这个表达式可以不是exection(…),还有其他的一些,我就不说了,说最常用的execution:

//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
<fully-qualified-class-name>.*(parameters)

注意这一块,如果没有精确到class-name,而是到包名就停止了,要用两个"…"来表示包下的任意类:

execution(* com.zdy…(…)):com.zdy包下所有类的所有方法.
execution(
com.zdy.Dog.*(…)): Dog类下的所有方法.

具体详细语法,大家如果有需求自行google了,我最常用的就是这俩了。要么按照包来定位,要么按照具体类来定位.
在使用切入点时,还可以抽出来一个@Pointcut来供使用:

/**
 * 使用Pointcut定义切点
 */
@Pointcut("execution(...)")
private void myPointcut(){}

/**
 * 应用切入点函数
 */
@After(value="myPointcut()")
public void afterDemo(){
    System.out.println("最终通知....");
}

可以避免重复的execution在不同的注解里写很多遍…

参考文献:
1.https://juejin.im/post/5a55af9e518825734d14813f
2.https://blog.csdn.net/gloomy_114/article/details/68946881
3.https://www.jianshu.com/p/fe8d1e8bd63e
4.https://www.cnblogs.com/puyangsky/p/6218925.html
5.https://juejin.im/post/5a55af9e518825734d14813f
6.https://blog.csdn.net/qq_35347459/article/details/70810725

发布了34 篇原创文章 · 获赞 21 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/lizhengze1117/article/details/104091140