不得不知IOC和AOP

学完spring很久了,以前复习过好几遍,可是最近再看发现还是有会忘的。所以干脆写下来方便以后自己复习,仅供个人学习使用。

一、spring概述

Spring的核心是一个轻量级(Lightweight)的容器(Container),实现Ioc(Inversion of Control)容器和非入侵性(No intrusive)的框架,提供AOP(Aspect-oriented programming)概念的实现方式,提供对持久层(Persistence)、事务(Transaction)的支持,提供MVC Web框架的实现,并对一些常用的企业服务api提供一致的模型封装,提供对现存的各种框架(Structs、JSF、Hibernate、Ibatis、Webwork等)相整合的方案。

选择使用Spring框架的原因(Spring框架为企业级开发带来的好处有哪些)?
其实是要谈一下spring的优点。

  • 1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦
  • 2.容器提供单例模式支持 。
  • 3.spring属于低侵入式设计,不强制要求实现Spring框架中的接口或继承Spring框架中的类,代码的污染极低。
  • 4.spring的IoC容器帮助应用程序管理对象以及对象之间的依赖关系,DI机制降低了业务对象替换的复杂性。
  • 5.容器提供了AOP技术,将所有的横切关注功能封装到切面(aspect),通过配置的方式将横切关注功能动态添加到目标代码上,进一步实现了业务逻辑和系统服务之间的分离,而且很容易实现如权限拦截,运行期监控等功能 。
  • 6.事务管理:Spring提供了声明式的事务管理,在不需要任何一行代码的情况下就能够完成事务管理。
  • 7.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等 ,而且提供了众多的辅助类,能加快应用的开发 。

二、IOC与DI

参考:

  1. https://www.iteye.com/blog/jinnianshilongnian-1413846
  2. https://www.cnblogs.com/xdp-gacl/p/4249939.html

以下全部来自原文。

1.IOC
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  • 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
  • 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

传统程序设计:

  • 在这里插入图片描述

使用IOC/DI容器后:
在这里插入图片描述

  IoC是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。 这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。总之,IoC是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移给了第三方。

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。形象的说,即由容器动态的将某个依赖关系注入到组件之中。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

那如何自己实现IOC呢?

要想做到这个,首先要了解spring ioc容器加载Bean的流程
在这里插入图片描述
根据上图,自己实现IOC时,实现步骤大致如下:

(1).定义用来描述bean的配置的Java类

(2).解析bean的配置(applicationcontext.xml), 將bean的配置信息转换为BeanDefinition对象保存在内存中。(即ApplicationContext context = new ClassPathXmlApplicationContext(“applicationcontext.xml”))。

注意:在spring容器中,容器在创建的时候会使用XmlBeanDefinitionReader这个类去解析xml文件,解析完成一个bean后把这个bean标签的信息放到BeanDefinition的对象里面。

(3).遍历存放BeanDefinition的HashMap对象(spring中用HashMap进行对象存储),逐条取出BeanDefinition对象,获取bean的配置信息,利用Java的反射机制实例化对象,將实例化后的对象保存在另外一个Map中即可。

2.DI
DI是对IoC更准确的描述,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。

通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

而理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”。

-谁依赖于谁:当然是应用程序依赖于IoC容器;

  • 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
  • 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

常用的DI(依赖注入)的三种方式:

  • setter注入
  • 构造方法注入
  • 基于注解的注入

(其实还有一个接口注入,但是不常用)

1.set方法注入

IOC默认使用无参构造(就算不写也默认有)创建对象,set方法来实现依赖注入。

(在编写spring 框架的代码时候。建议所有在spring中注入的bean 都定义成私有的域变量。并且配套写上 get 和 set方法。)

pojo:

package com.pojo;

public class UserT {

    String name;

    public UserT() {
        System.out.println("UserT的无参构造");
    }

    public String getName() {
        return name;
    }
	//如果不写set方法,则无法实现依赖注入,会报错
    public void setName(String name) {
        this.name = name;
    }

    public void show(){
        System.out.println("name = " + name);
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userT" class="com.pojo.UserT">
        <property name="name" value="yangg"/>
    </bean>

</beans>

测试类:

import com.pojo.User;
import com.pojo.UserT;
import com.sun.deploy.panel.JreFindDialog;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Mytest {

    public static void main(String[] args) {
        //只要在xml文件中写一个bean标签,就相当于实例化了,这里获取容器的时候,就把文件中的所有东西都获取到了
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //User user = (User) context.getBean("user");
        UserT userT = (UserT) context.getBean("userT");
        userT.show();
    }
} 

2.构造方法注入

如果代码中只有有参构造方法时,系统就会使用有参构造来实现依赖注入。

package com.pojo;

public class User {

    String name;
    //有参构造
    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public void show(){
        System.out.println("name = " + name);
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--只有有参构造时,或者说你不想用set方法,想用构造方法来实现依赖注入的话,容器中创建对象就要使用以下三种方法:

    第一种方法,通过下标
    <bean id="user" class="com.pojo.User">
        0表示有参构造方法中的第一个参数,value是赋给这个参数的值
        <constructor-arg index="0" value="杨光"/>
    </bean>
    -->

    <!--第二种方法,通过类型创建
    <bean id="user" class="com.pojo.User">
        String是参数类型, 要写全限定名。value是赋的值
        <constructor-arg type="java.lang.String" value="杨光2"/>
    </bean>
    -->

    <!--第三种方式,直接通过参数名来设置(推荐使用)-->
    <bean id="user" class="com.pojo.User">
        <!--name是有参构造方法中的参数名,value是赋的值-->
        <constructor-arg name="name" value="杨光3"/>
    </bean>
    <!--
    id:bean的唯一标识符,相当于于对象名
    class:bean对象所对应的全限定类名
    name:别名,可以通过id取,也可以通过这个别名取,而且可以取多个别名,分隔符空格逗号等都可以
    -->
<!--    <bean id="userT" class="com.pojo.UserT" name="userNew,u2 u3">
            <property name="name" value="yangg"/>
        </bean>
-->
</beans>

除此之外,以上两种方式还有另一种表现形式:p命名空间注入 和c命名空间注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--p和c使用时,要先导入依赖
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
    -->

    <!--p命名空间注入,通过set方法,可以直接注入属性的值,相当于propertie-->
    <bean id="user" class="com.pojo.User" p:name="yangg" p:age="24"/>
    <!--相当于
        <bean id="user" class="com.pojo.User">
            <property name="name" value="yangg" />
            <property name="age" value= "24"/>
        </bean>
    -->

    <!--c命名空间注入,通过构造器注入(有参),construct-args-->
    <bean id="user2" class="com.pojo.User" c:name="杨光" c:age="23"/>
    <!--相当于
        <bean id="user2" class="com.pojo.User">
            <constructor-arg name="name" value="杨光"/>
            <constructor-arg name="age" value="23"/>
        </bean>
    -->
</beans>

3.注解注入:@Autowire和@Resource

可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 使用 @Autowired之后,可以不写 set 和get方法。至于为什么使用@autowired注解来完成属性的依赖注入时可以不写set方法,可以看这篇文章:https://blog.csdn.net/qq_19782019/article/details/85038081

也可以在xml配置autowire,了解即可。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="cat" class="com.pojo.Cat"/>
    <bean id="dog22" class="com.pojo.Dog"/>

    <bean id="people" class="com.pojo.People" autowire="byName">
        <property name="name" value="yangg"/>
   <!-- 显式配置
        <property name="cat" ref="cat"/>
        <property name="dog" ref="dog"/>
    -->
    </bean>
</beans>

1.@Autowired

首先解释一下byName和byType。
  byName:按名称注入,会自动在容器中查找,如果java类中引用类型的属性名和spring的配置文件中bean中的id名称一样,且数据类型一致,spring就可以自动赋值。cat和dog两个引用类型,id名字分别为cat和dog22,和people类中的属性名一致,所以可以自动赋值。弊端是需要保证所有引用类型的bean的id唯一。

 byType:spring扫描到People类中的Dog和Cat两个类型,然后来配置文件中找有没有相关bean中的class属性,有的话就赋值。需要保证所有bean的class属性(类型)唯一(比如不能有两个Dog类型或其同源类型),且这个bean需要和自动注入的属性的类型一致。(同源:同类型,父子类关系,接口和实现类关系)

@Autowired先通过byType去匹配相同的bean,要求对象必须存在。如果类型重复(匹配到不止一个),配合@Qualifier,通过byName注入。

pojo类:

package com.pojo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.Resource;

public class People {

    @Autowired
    private Dog dog;

    //如果xml文件中同一个对象被多个<bean>使用,Autowired无法按类型找到,就需要用@Qualifier指定一个唯一的<bean>(id)对象使用
    @Autowired
    @Qualifier(value = "cat111")
    private Cat cat;

/*    @Resource
    private Dog dog;
    @Resource(name = "cat");
    private Cat cat;
 */

    @Value("yangguang")
    private String name;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Dog getDog() {
        return dog;
    }
    public void setDog(Dog dog) {
        this.dog = dog;
    }
    public Cat getCat() {
        return cat;
    }
    public void setCat(Cat cat) {
        this.cat = cat;
    }
    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                ", cat=" + cat +
                '}';
    }
}


class Dog {
    public void shout(){
        System.out.println("dog叫");
    }
}


class Cat {
    public void shout(){
        System.out.println("cat叫");
    }
}

xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--要开启注解支持-->
    <context:annotation-config/>

    <bean id="cat" class="com.pojo.Cat"/>
    <bean id="cat111" class="com.pojo.Cat"/>
    <bean id="dog" class="com.pojo.Dog"/>
    <bean id="people" class="com.pojo.People"/>
</beans>

2.@Resource
默认通过byname的方式实现,如果找不到名字,则通过byType实现。如果byName和byType都找不到,就报错。

package com.pojo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.Resource;

public class People {

    //如果xml文件中同一个对象被多个<bean>使用,Autowired无法按类型找到,就需要用@Qualifier指定一个唯一的<bean>(id)对象使用
    @Autowired
    @Qualifier(value = "cat111")
    private Cat cat;

    @Resource
    private Dog dog;
    
   /* @Resource(name = "cat");
    private Cat cat;  */

    //@Value给基本类型赋值
    @Value("yangguang")
    private String name;

    public String getName() {
        return name;
    }

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

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                ", cat=" + cat +
                '}';
    }
}

测试类:

import com.pojo.People;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Mytest {

    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        People people = context.getBean("people", People.class);
        System.out.println(people.getName());
//        people.getCat().shout();
        people.getDog().shout();

    }
}

@Autowired 和@Resource异同:

1.@Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
2. @Autowired默认通过bytype自动注入,要求对象必须存在。如果类型重复,配合@Qualifier,通过byName注入。而 @Resource默认通过byname的方式自动注入,如果找不到,则通过byType实现,如果两个都找不到,就报错。

三、注解

主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:

  • @Component:通用注解,可以用于注册所有bean
  • @Repository:主要用于注册dao层(数据库操作)的bean
  • @Controller:主要用于注册控制层的bean,是spring-mvc的注解,具有将请求进行转发,重定向的功能。
  • @Service:主要用于注册服务层的bean
package com.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/*
最普通的组件,可以被注入到spring容器进行管理,放在类上,说明这个类被Spring管理了,就是bean。等价于<bean id = "user" class = "com.pojo.User"/>
属性value是对象的名称,即<bean>中id的值
@Component("user"),也可以省略value(常用)
@Component,也可以不写名称,默认id名称是类名的首字母小写
*/
@Component(value = "user")  //(把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>)
public class User {
    //给基本类型变量赋值,这个注解相当于<property name = "name" value = "yangg">,也可以注解到set方法上
    @Value("yangg")
    public String name;

/*    @Value("yangg")
    public void setName(String name) {
        this.name = name;
    }*/

}

xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--
    1.先配置 context:component-scan base-package="com.pojo",扫描包中的组件
      会把包和子包中的所有类,找到类中的注解,按照注解的功能创建对象或者给对象赋值
    2.在实体类中配置@Component,基本类型配置@Value,引用类型配置@Autowired或者@Resource

    -->
    <context:component-scan base-package="com.pojo"/>

</beans>

其他三个注解和@Component相同,都可以用来创建对象,注解后可以被spring框架所扫描并注入到spring容器来进行管理。@Repository,@Service,@Controller还可以用来给项目对象分层。

什么时候用@Component?
不属于以上三个类的时候用,比如工具类和实体类中

用这些注解对应用进行分层之后,就能将请求处理,义务逻辑处理,数据库操作处理分离出来,为代码解耦,也方便了以后项目的维护和开发。

四、Bean相关

1.Bean的生命周期
在这里插入图片描述
可以简述为以下八步:

  • 1.实例化bean对象(通过构造方法或者工厂方法)

    • ApplicationContext容器,启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
  • 2.设置对象属性(setter等)(依赖注入)

    • 实例化后的对象被封装在BeanWrapper对象中,Spring根据BeanDefinition中的信息,以及通过BeanWrapper提供的设置属性的接口完成依赖注入。
  • 3.处理Aware接口

    • 如果Bean实现了BeanNameAware接口,工厂调用Bean的setBeanName()方法传递Bean的id。
    • 如果Bean实现了BeanFactoryAware接口,工厂调用setBeanFactory()方法传入工厂自身。
  • 4.将Bean实例传递给Bean的前置处理器的postProcessBeforeInitialization(Object bean, String beanname)方法

  • 5.调用Bean的初始化方法

  • 6.将Bean实例传递给Bean的后置处理器的postProcessAfterInitialization(Object bean, String beanname)方法

  • 7.使用Bean

  • 8.容器关闭之前,调用Bean的销毁方法

2.Bean的作用域有哪些

Spring 3中为Bean定义了5种作用域:singleton(单例)prototype(原型) 以及三个Web环境下的作用域:requestsessionglobal session

singleton:Spring容器内只存在一个共享的Bean实例,即Bean以单例的方式存在(单例模式),无论有多少个Bean引用它,始终指向同一对象。

prototype:每次从容器中调用Bean时,都会返回一个新的实例。即每次调用都会创建一个Bean ,也就是每次getBean()就相当于是new Bean()的操作。

request:在一次Http请求中,容器会返回该Bean的同一实例。而对不同的Http请求则会产生新的Bean,而且该bean仅在当前Http Request内有效。(每次HTTP请求都会创建一个Bean)

session:在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。(HTTP Session共享一个Bean实例)

global Session:在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅用于 portlet 容器,因为每个 portlet 有单独的 session。

五、AOP

面向切面编程(AOP)就是纵向的编程。比如业务A和业务B现在需要一个相同的操作,传统方法我们可能需要在A、B中都加入相关操作代码,而应用AOP就可以只写一遍代码,A、B共用这段代码。并且,当A、B需要增加新的操作时,可以在不改动原代码的情况下,灵活添加新的业务逻辑实现。

在实际开发中,比如商品查询、促销查询等业务,都需要记录日志、异常处理等操作,AOP把所有共用代码都剥离出来,单独放置到某个类中进行集中管理,在具体运行时,由容器进行动态织入这些公共代码。

AOP主要一般应用于签名验签、参数校验、日志记录、事务管理、权限控制、性能统计、异常处理等。

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

Spring实现AOP:JDK动态代理CGLIB代理

JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理;其核心的两个类是InvocationHandler和Proxy。

CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强;需要引入包asm.jar和cglib.jar。 使用AspectJ注入式切面和@AspectJ注解驱动的切面实际上底层也是通过动态代理实现的。

Spring中用到的设计模式:

主要用到的设计模式有工厂模式和代理模式。

IOC是典型的工厂模式,通过sessionfactory去注入实例。
AOP是典型的代理模式的体现。

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

AOP涉及名词

切面(Aspect):共有功能的实现。如日志切面、权限切面、验签切面等。在实际开发中通常是一个存放共有功能实现的标准Java类。当Java类使用了@Aspect注解修饰时,就能被AOP容器识别为切面。

通知(Advice):切面的具体实现。就是要给目标对象织入的事情。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际开发中通常是切面类中的一个方法,具体属于哪类通知,通过方法上的注解区分。

连接点(JoinPoint):程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出等。Spring只支持方法级的连接点。一个类的所有方法前、后、抛出异常时等都是连接点。

切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。

比如,在上面所说的连接点的基础上,来定义切入点。我们有一个类,类里有10个方法,那就产生了几十个连接点。但是我们并不想在所有方法上都织入通知,我们只想让其中的几个方法,在调用之前检验下入参是否合法,那么就用切点来定义这几个方法,让切点来筛选连接点,选中我们想要的方法。切入点就是来定义哪些类里面的哪些方法会得到通知。

目标对象(Target):那些即将切入切面的对象,也就是那些被通知的对象。这些对象专注业务本身的逻辑,所有的共有功能等待AOP容器的切入。

代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象本身业务逻辑加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。目标对象被织入共有功能后产生的对象。

织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译时、类加载时、运行时。Spring是在运行时完成织入,运行时织入通过Java语言的反射机制与动态代理机制来动态实现。

Spring面试:对AOP的理解

AOP,即面向切面编程,在spring中主要表现为两个方面 :

1.面向切面编程提供声明式事务管理
2.spring支持用户自定义的切面

简单来说,AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了 多个类的公共行为,也就是那些与业务无关,但是每个业务模块都要用的 逻辑或责任(比如日志记录) 抽取出来,封装到一个可重用模块,并用“@Aspect”进行注解,然后注入到目标对象(具体业务逻辑)中去。便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。

Spring中AOP的实现方式有两种:JDK动态代理CGLIB代理

JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理;其核心的两个类是InvocationHandler和Proxy。

CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。使用AspectJ注入式切面和@AspectJ注解驱动的切面实际上底层也是通过动态代理实现的。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值