ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringIOC和AOP简介以及基本使用

2019-07-05 11:36:08  阅读:267  来源: 互联网

标签:SpringIOC String Spring spring AOP 简介 注解 方法 public


一、Spring 体系结构

Spring 有可能成为所有企业应用程序的一站式服务点,然而,Spring 是模块化的,允许你挑选和选择适用于你的模块,不必要把剩余部分也引入。下面的部分对在 Spring 框架中所有可用的模块给出了详细的介绍。

Spring 框架提供约 20 个模块,可以根据应用程序的要求来使用。

spring overview

1、核心容器

核心容器由spring-core,spring-beans,spring-context,spring-context-support和spring-expression(SpEL,Spring表达式语言,Spring Expression Language)等模块组成,它们的细节如下:

  • spring-core模块提供了框架的基本组成部分,包括 IoC 和依赖注入功能。

  • spring-beans 模块提供 BeanFactory,工厂模式的微妙实现,它移除了编码式单例的需要,并且可以把配置和依赖从实际编码逻辑中解耦。

  • context模块建立在由core和 beans 模块的基础上建立起来的,它以一种类似于JNDI注册的方式访问对象。Context模块继承自Bean模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过Servelet容器)等功能。Context模块也支持Java EE的功能,比如EJB、JMX和远程调用等。ApplicationContext接口是Context模块的焦点。spring-context-support提供了对第三方库集成到Spring上下文的支持,比如缓存(EhCache, Guava, JCache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker, JasperReports, Velocity)等。

  • spring-expression模块提供了强大的表达式语言,用于在运行时查询和操作对象图。它是JSP2.1规范中定义的统一表达式语言的扩展,支持set和get属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等。

它们的完整依赖关系如下图所示:

Spring 体系结构

2、数据访问/集成

数据访问/集成层包括 JDBC,ORM,OXM,JMS 和事务处理模块,它们的细节如下:

(注:JDBC=Java Data Base Connectivity,ORM=Object Relational Mapping,OXM=Object XML Mapping,JMS=Java Message Service)

  • JDBC 模块提供了JDBC抽象层,它消除了冗长的JDBC编码和对数据库供应商特定错误代码的解析。

  • ORM 模块提供了对流行的对象关系映射API的集成,包括JPA、JDO和Hibernate等。通过此模块可以让这些ORM框架和spring的其它功能整合,比如前面提及的事务管理。

  • OXM 模块提供了对OXM实现的支持,比如JAXB、Castor、XML Beans、JiBX、XStream等。

  • JMS 模块包含生产(produce)和消费(consume)消息的功能。从Spring 4.1开始,集成了spring-messaging模块。。

  • 事务模块为实现特殊接口类及所有的 POJO 支持编程式和声明式事务管理。(注:编程式事务需要自己写beginTransaction()、commit()、rollback()等事务管理方法,声明式事务是通过注解或配置由spring自动处理,编程式事务粒度更细)

3、Web

Web 层由 Web,Web-MVC,Web-Socket 和 Web-Portlet 组成,它们的细节如下:

  • Web 模块提供面向web的基本功能和面向web的应用上下文,比如多部分(multipart)文件上传功能、使用Servlet监听器初始化IoC容器等。它还包括HTTP客户端以及Spring远程调用中与web相关的部分。。

  • Web-MVC 模块为web应用提供了模型视图控制(MVC)和REST Web服务的实现。Spring的MVC框架可以使领域模型代码和web表单完全地分离,且可以与Spring框架的其它所有功能进行集成。

  • Web-Socket 模块为 WebSocket-based 提供了支持,而且在 web 应用程序中提供了客户端和服务器端之间通信的两种方式。

  • Web-Portlet 模块提供了用于Portlet环境的MVC实现,并反映了spring-webmvc模块的功能。

4、其他

还有其他一些重要的模块,像 AOP,Aspects,Instrumentation,Web 和测试模块,它们的细节如下:

  • AOP 模块提供了面向方面的编程实现,允许你定义方法拦截器和切入点对代码进行干净地解耦,从而使实现功能的代码彻底的解耦出来。使用源码级的元数据,可以用类似于.Net属性的方式合并行为信息到代码中。

  • Aspects 模块提供了与 AspectJ 的集成,这是一个功能强大且成熟的面向切面编程(AOP)框架。

  • Instrumentation 模块在一定的应用服务器中提供了类 instrumentation 的支持和类加载器的实现。

  • Messaging 模块为 STOMP 提供了支持作为在应用程序中 WebSocket 子协议的使用。它也支持一个注解编程模型,它是为了选路和处理来自 WebSocket 客户端的 STOMP 信息。

  • 测试模块支持对具有 JUnit 或 TestNG 框架的 Spring 组件的测试。

Spring的核心是控制反转(IOC)和面向切面(AOP)

一、控制反转(IOC)

控制反转(IOC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的装配和管理。

IoC 是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式是依赖注入,应用广泛。

依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即 classA 对 classB 有依赖。

IOC 的实现:

  • 依赖查找:DL ( Dependency Lookup ), 容器提供回调接口和上下文环境给组件。
  • 依赖注入:DI (Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。

依赖注入 DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。

Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持 POJO 之间依赖关系的管理。

依赖注入是目前最优秀的解耦方式。依赖注入让 Spring 的 Bean 之间以配置文件的方式组织在一起,而不是以硬编码的方式耦合在一起的。

二、第一个Spring案例实现

1、添加依赖

 <spring-version>4.3.9.RELEASE</spring-version>
    <log4j-api-version>2.3</log4j-api-version>
 

<!--Spring依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <!--要使用spring注解功能需要加入该依赖,注解的后台实现用到了 AOP 编程-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>${spring-version}</version>
    </dependency>

    <!-- Log4j2依赖的JAR配置 -->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>${log4j-api-version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>${log4j-api-version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-jcl</artifactId>
      <version>${log4j-api-version}</version>
    </dependency>

2、创键业务层

package io.spirng.service;

public interface SomeService {
    void doSome();
}

实现类

package io.spring.service.impl;

import io.spring.service.SomeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;

/**
 * 注解@Component,该注解的 value 属性用于指定该 bean 的 id 值
 * 外,Spring 还提供了 3 个功能基本和@Component 等效的注解:
 * @Repository 用于对 DAO 实现类进行注解
 * @Service 用于对 Service 实现类进行注解
 * @Controller 用于对 Controller 实现类进行注解
 * 之所以创建这三个功能与@Component 等效的注解,是为了以后对其进行功能上的扩展
 * 注解不指定 value 属性,bean 的 id 是类名的首字母小写
 */
@Service("someService")
public class SomeServiceImpl implements SomeService {
    /**
     * 需要在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。
     * 使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
     */
    @Value("张三")
    private String  name;

    private String address;

    @Value("北京")
    public void setAddress(String address) {
        this.address = address;
    }

    /**
     * @Autowired 该注解式通过类型注入的,同样可以在放在set方法上可以放在变量上,按类型注入
     * 如果要按名称注入可以配合@Qualifier("")使用
     *  @Autowired 的required 默认值为 true表示当匹配失败后,会终止程序运行。
     *  若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
     */
    @Autowired(required = false)
    @Qualifier("myOrder")
    private Order order;

    /**
     * JDK 注解@Resource 自动注入
     * Spring 提供了对jdk中@Resource注解的支持。@Resource注解既可以按名称匹配Bean,
     * 也可以按类型匹配 Bean。默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。
     * @Resource 可在属性上,也可在 set 方法上。
     */
    @Resource(name = "friend")
    private Friend myFriend;


    public SomeServiceImpl(){
        System.out.println("调用了无参构造");
    }
    @Override
    public void doSome() {
        System.out.println("业务方法执行doSome...");
    }

    /**
     * Bean 的生命始末@PostConstruct 与@PreDestroy
     * 在方法上使用@PostConstruct 与原来的 init-method 等效。在方法上使用@PreDestroy,
     * 与 destroy-method 等效。
     */
    @PostConstruct
    public void initMethod(){
        System.out.println("初始化方法执行...");
    }
    @PreDestroy
    public void destroyMethod(){
        System.out.println("销毁方法执行...");
    }

    @Override
    public String toString() {
        return "SomeServiceImpl{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", order=" + order +
                ", myFriend=" + myFriend +
                '}';
    }
}

@Component("myOrder")
class Order{
    @Value("订单编号1")
    private String orderNum;

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

@Component("friend")
class Friend{

    @Value("张三")
    private String name;

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

实体类

package io.spring.domain;

import java.util.*;

public class Student {

    private String name;
    private int age;
    private School mySchool;
    private String[] strings;
    private List<School> schoolLists;
    private Set<String> schoolSet;
    private Map<String,Integer> map;
    private Properties properties;
    private List<Map<String,String>> mapList;

    public Student() {
    }

    public Student(String name, int age, School mySchool) {
        this.name = name;
        this.age = age;
        this.mySchool = mySchool;
    }

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

    public void setAge(int age) {
        this.age = age;
    }

    public void setMySchool(School mySchool) {
        this.mySchool = mySchool;
    }

    public void setStrings(String[] strings) {
        this.strings = strings;
    }

    public void setSchoolLists(List<School> schoolLists) {
        this.schoolLists = schoolLists;
    }

    public void setSchoolSet(Set<String> schoolSet) {
        this.schoolSet = schoolSet;
    }

    public void setMap(Map<String, Integer> map) {
        this.map = map;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public void setMapList(List<Map<String, String>> mapList) {
        this.mapList = mapList;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", mySchool=" + mySchool +
                ", strings=" + Arrays.toString(strings) +
                ", schoolLists=" + schoolLists +
                ", schoolSet=" + schoolSet +
                ", map=" + map +
                ", properties=" + properties +
                ", mapList=" + mapList +
                '}';
    }
}
class School {
    private String name;

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

3、在resources文件下创键Spring配置文件applicationContext.xml

如何配置去这里面https://repo.spring.io/release/org/springframework/spring/4.3.9.RELEASE/spring-framework-4.3.9.RELEASE-dist.zip找spring-framework-4.3.9.RELEASE-dist.zip\spring-framework-4.3.9.RELEASE\docs\spring-framework-reference\html\xsd-configuration.html,CV就行了。

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- bean definitions here -->

    <!--
        基于注解的DI:
        配置组件扫描器,如果需要扫描多个包可以使用“,”、“;”、“空格”、配置多个<context:component-scan/>都行
        base-package 是指定到父包名,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。所以 base-package 可以指定一个父包就可以。

    -->
    <context:component-scan base-package="io.spring.service"/>



    <!--导入其他配置文件-->
    <!--<import resource="classpath:application-city.xml"/>-->
    <!-- bean definitions here -->
    <!--
    <bean />
        用于定义一个实例对象。一个实例对应一个 bean 元素。
        id:该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean,Bean 与 Bean 间的依
        赖关系也是通过 id 属性关联的。
        class:指定该 Bean 所属的类,注意这里只能是类,不能是接口。


        ApplicationContext 容器中对象的装配时机 :ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。 以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。
        当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以通过 scope 属性,为 Bean 指定特定的作用域。Spring 支持 5 种作用域。
         代码通过 getBean()方式从容器获取指定的 Bean 实例,容器初始化bean的时候首先会调用 Bean 类的无参构造器,创建空值的实例对象。
        (1)singleton:单例模式。即在整个 Spring 容器中,使用 singleton 定义的 Bean 将是单例的, 只有一个实例。默认为单例的。
        (2)prototype:原型模式。即每次使用 getBean 方法获取的同一个的实例都是一个新的实例。
        (3)request:对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。
        (4)session:对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。
        注意:
        (1)对于 scope 的值 request、session 只有在 Web 应用中使用 Spring 时,该作用域才有效。
        (2)对于 scope 为 singleton 的单例模式,该 Bean 是在容器被创建时即被装配好了。
        (3)对于 scope 为 prototype 的原型模式,Bean 实例是在代码中使用该 Bean 实例时才进行 装配的。
        在配置文件的<bean>标签中增加如下属性: init-method:指定初始化方法的方法名 destroy-method:指定销毁方法的方法名
        注意,若要看到 Bean 的 destroy-method 的执行结果,需要满足两个条件:
        (1)Bean 为 singleton,即单例
        (2)要确保容器关闭。接口 ApplicationContext 没有 close()方法,但其实现类有。所以,可以将 ApplicationContext 强转为其子类对象。
    -->
   <!-- <bean id="someService" class="io.spring.service.impl.SomeServiceImpl" scope="singleton" init-method="initMethod"
          destroy-method="destroyMethod"/>
-->
    <bean id="mySchool" class="io.spring.domain.School">
        <property name="name" value="蓝翔"></property>
    </bean>

    <!--设值注入/set注入-->
    <!--

        byName方式:当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用byName方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bea类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
        byType方式:要求配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知道该如何匹配了。
哪一个了。
    -->
    <bean id="student" class="io.spring.domain.Student" autowire="byName">
        <!--构造注入指定参数名-->
        <constructor-arg name="name" value="李四"/>
        <constructor-arg name="age" value="21"/>
        <constructor-arg name="mySchool" ref="mySchool"/>
        <!--构造注入,指定下标-->
        <!--  <constructor-arg index="0" value="李四"/>
          <constructor-arg index="1" value="21"/>
          <constructor-arg index="2" value="mySchool"/>-->

        <property name="name" value="张三"/>
        <property name="age" value="20"/>
        <!--引用其他bean,这里使用autowire="byName"给引用类型注入-->
        <!-- <property name="mySchool" ref="mySchool"/>-->

        <!--注入集合性质的属性-->
        <!--数组-->
        <property name="strings">
            <array>
                <value>abc</value>
                <value>def</value>
            </array>
        </property>
        <!--list-->
        <property name="schoolLists">
            <list>
                <ref bean="mySchool"/>
            </list>
        </property>
        <!--set-->
        <property name="schoolSet">
            <set>
                <value>值1</value>
                <value>值2</value>
            </set>
        </property>
        <!--map-->
        <property name="map">
            <map>
                <entry key="height" value="180"/>
                <entry key="weight" value="60"/>
            </map>
        </property>
        <!--properties-->
        <property name="properties">
            <props>
                <prop key="tel">110</prop>
                <prop key="address">北京</prop>
            </props>
        </property>
        <!--复杂集合属性的注入List<Map<String,String>>-->
        <property name="mapList">
            <list>
                <map>
                    <entry key="height" value="180"/>
                    <entry key="weight" value="60"/>
                </map>
                <map>
                    <entry key="city" value="北京"/>
                    <entry key="province" value="北京"/>
                </map>
            </list>
        </property>
    </bean>
</beans>

新建log4j2.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="info">
    <!--先定义所有的appender-->
    <appenders>
        <!--这个输出控制台的配置-->
        <Console name="Console" target="SYSTEM_OUT">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="ACCEPT"/>
            <!--这个都知道是输出日志的格式-->
            <PatternLayout pattern="[demo] %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
        </Console>

        <!--这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFile" fileName="/opt/tomcat_licaihost/logs/app.log" filePattern="/opt/tomcat_licaihost/logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
            <PatternLayout pattern="[demo] %d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
            <SizeBasedTriggeringPolicy size="300MB"/>
        </RollingFile>
    </appenders>

    <!--然后定义logger,只有定义了logger并引入appender,appender才会生效-->
    <loggers>
        <!--建立一个默认的root的logger-->
        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFile"/>
        </root>
    </loggers>
</configuration>

四、测试

查看类或接口的继承关系:IDEA中使用Ctrl+h,Eclipse使用Ctrl+t

package io.spring.test;

import io.spring.domain.Student;
import io.spring.service.SomeService;
import javafx.scene.media.SubtitleTrack;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class Test01 {
    public static void main(String[] args) {
        //创建spring容器对象
        //1、spring配置文件在类路径下使用该方式加载applicationContext.xml
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");

        //2、spring配置文件存放在本地磁盘目录中,使用 FileSystemXmlApplicationContext 实现类进行加载。
        //ApplicationContext applicationContext=new FileSystemXmlApplicationContext("c:\\applicationContext.xml");

        //3、Spring 配置文件存放在项目的根路径下同样使用 FileSystemXmlApplicationContext实现类进行加载
        //下面是存放在项目根路径下的情况,该配置文件与src目录同级
        //ApplicationContext applicationContext=new FileSystemXmlApplicationContext("applicationContext.xml");

        //获取实例
        SomeService someService = applicationContext.getBean("someService", SomeService.class);
        //调用方法
        someService.doSome();

        System.out.println(someService);

        Student student = applicationContext.getBean("student", Student.class);
        System.out.println(student);

        ((AbstractApplicationContext) applicationContext).close();


    }
}

三、AOP 面向切面

AOP 简介

AOP(Aspect Orient Programming),面向切面编程,是面向对象编程 OOP 的一种补充。 面向对象编程是从静态角度考虑程序的结构,而面向切面编程是从动态角度考虑程序运行过 程。

AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理。

AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和 运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中 的一个热点,也是 Spring 框架中的一个重要内容。利用 AOP 可以对业务逻辑的各个部分进 行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开 发的效率。

                                                                                                                                                         百度百科《AOP》

面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志、缓存等。

若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。

例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事 务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占 比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大 大干扰了主业务逻辑---转账。

AOP 编程术语

1) 切面(Aspect

切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。

2) 织入(Weaving织入是指将切面代码插入到目标对象的过程。 JDK静态代理、JDK动态代理、Cglib动态代理区别中TimeInvocationHandler 类中的invoke() 方法完成的工作,就可以称为织入。

3) 连接点(JoinPoint

连接点指可以被切面织入的方法。通常业务接口中的方法均为连接点。

4) 切入点(Pointcut

切入点指切面具体织入的方法。在 一个类中有两个方法doSome(),doOther(),若 doSome()将被增强,而 doOther()不被增强,则 doSome()为切入点,而 doOther()仅为连接点。

被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

5) 目标对象(Target

目标对象指将要被增强的对象。 即包含主业务逻辑的类的对象 。上例中的SomeServiceImpl的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然, 不被增强,也就无所谓目标不目标了。

6) 通知(Advice

通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。上例 中的 TimeInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码 切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。 切入点定义切入的位置,通知定义切入的时间。

AspectJ 对 AOP 的实现

对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便, 而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。 在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。

AspectJ 简介

AspectJ 是一个面向切面的框架,它扩展了 Java 语言。AspectJ 定义了 AOP 语法,它有 一个专门的编译器用来生成遵守 Java 字节编码规范的 Class 文件。

                                                                                                                                           ---- 百度百科《AspectJ》

官网地址:http://www.eclipse.org/aspectj/

AspetJ 是 Eclipse 的开源项目,官网介绍如下:

a seamless aspect-oriented extension to the Javatm programming language(一种基于 Java 平台 的面向切面编程的语言

Java platform compatible(兼容 Java 平台,可以无缝扩展)

easy to learn and use(易学易用)

Aspectj 是静态织入。静态织入:指在编译时期就织入,即:编译出来的 class 文件,字节码就已经被织入了。

AspectJ 的通知类型

AspectJ 中常用的通知有五种类型:

(1)前置通知

(2)后置通知

(3)环绕通知

(4)异常通知

(5)最终通知

AspectJ 的切入点表达式

AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:

execution ( [modifiers-pattern] 访问权限类型

                     ret-type-pattern 返回值类型

                     [declaring-type-pattern] 全限定性类名

                     name-pattern(param-pattern) 方法名(参数名)

                     [throws-pattern] 抛出异常类型

         )

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中 可以使用以下符号:

举例:

execution(public * *(..))

指定切入点为:任意公共方法。

execution(* set*(..))

指定切入点为:任何一个以“set”开始的方法。

execution(* com.xyz.service.*.*(..))

指定切入点为:定义在 service 包里的任意类的任意方法。

execution(* com.xyz.service..*.*(..))

指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后 面必须跟“*”,表示包、子包下的所有类。

execution(* *.service.*.*(..))

指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点

execution(* *..service.*.*(..))

指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点

execution(* *.ISomeService.*(..))

指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点

execution(* *..ISomeService.*(..))

指定所有包下的 ISomeSerivce 接口中所有方法为切入点

execution(* com.xyz.service.IAccountService.*(..))

指定切入点为:IAccountService 接口中的任意方法。

execution(* com.xyz.service.IAccountService+.*(..))

指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意 方法;若为类,则为该类及其子类中的任意方法。

execution(* joke(String,int)))

指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参 数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用 全限定类名,如 joke( java.util.List, int)。

execution(* joke(String,*)))

指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类 型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但 joke(String s1,double d2,String s3)不是。

execution(* joke(String,..)))

指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有任意个参数且 参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3) 都是。

execution(* joke(Object))

指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob) 是,但,joke(String s)与 joke(User u)均不是。

execution(* joke(Object+)))

指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。 不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。

AspectJ 的开发环境

(1)在上面案例继续添加AspectJ依赖

<aspectjweaver-version>1.8.9</aspectjweaver-version>


    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>${aspectjweaver-version}</version>
    </dependency>

(2)引入 AOP 约束

在前面 Spring 实现 AOP 时,并未引入 AOP 的约束,而在 AspectJ 实现 AOP 时,才提出 要引入 AOP 的约束。说明,配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的而非 Spring 框架本身在实现 AOP 时使用的。

AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。

AspectJ 基于注解的 AOP 实现

AspectJ 提供了以注解方式对于 AOP 的实现

step1:定义业务接口

接口

package io.spring.aop.service;


public interface SomeService {
    String doSome(String arg);
}

实现类

package io.spring.aop.service.impl;

import io.spring.aop.service.SomeService;

public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String arg) {
        System.out.println("执行doSome...参数为:"+arg);
        return "aaa";
    }
}

step2:定义切面 POJO 类

step3:在切面类上添加@Aspect 注解

Step4:在 POJO 类的普通方法上添加通知注解

package io.spring.aop.aspect;


import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

//该类为一个 POJO 类,将作为切面出现。其中定义了若干普通方法,将作为不同的通知方法
//在定义的 POJO 类上添加@Aspect 注解,指定当前 POJO 类将作为切面。
@Aspect
public class MyAspect {
    //在 POJO 类的普通方法上添加通知注解
    /**
     * 切面类是用于定义增强代码的,即用于定义增强目标类中目标方法的增强方法。这些增
     * 强方法使用不同的“通知”注解,会在不同的时间点完成织入。当然,对于增强代码,还要
     * 通过 execution 表达式指定具体应用的目标类与目标方法,即切入点。
     */
    @Before("execution(* *..SomeServiceImpl.doSome(..))")
    public void beforeSome() {
        System.out.println("前置增强");
    }
}

Step5:注册目标对象与 POJO 切面类,仙剑一个applicationContext-aop.xml文件

Step6:注册 AspectJ 的自动代理

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> 

    <!--注册 AspectJ 的自动代理-->
    <!--
        在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理
    对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的
    自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型与切入点,将其织入,并
    生成代理。
        <aop:aspectj-autoproxy/>的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。
    从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。
    其工作原理是,<aop:aspectj-autoproxy/>通过扫描找到@Aspect 定义的切面类,再由切
    面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
    -->
    <aop:aspectj-autoproxy/>


    <!--配置目标类对象-->
    <bean id="someServiceImpl" class="io.spring.aop.service.impl.SomeServiceImpl"/>
    <!--配置切面类对象-->
    <bean id="myAspect" class="io.spring.aop.aspect.MyAspect"/>

</beans>

Step7:测试类中使用目标对象的 id

package io.spring.aop.test;


import io.spring.aop.service.SomeService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test01 {
    public static void main(String[] args) {
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext-aop.xml");
        SomeService someService = applicationContext.getBean("someServiceImpl", SomeService.class);
        someService.doSome("arg1");

    }
}

通知类型

(1)@Before 前置通知-方法有 JoinPoint 参数

在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参 数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、 目标对象等。

不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。

package io.spring.aop.aspect;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

//该类为一个 POJO 类,将作为切面出现。其中定义了若干普通方法,将作为不同的通知方法
//在定义的 POJO 类上添加@Aspect 注解,指定当前 POJO 类将作为切面。
@Aspect
public class MyAspect {
    //在 POJO 类的普通方法上添加通知注解
    /**
     * 切面类是用于定义增强代码的,即用于定义增强目标类中目标方法的增强方法。这些增
     * 强方法使用不同的“通知”注解,会在不同的时间点完成织入。当然,对于增强代码,还要
     * 通过 execution 表达式指定具体应用的目标类与目标方法,即切入点。
     */
    @Before("execution(* *..SomeServiceImpl.doSome(..))")
    public void beforeSome(JoinPoint joinPoint) {
        System.out.println("前置通知(切入点表达式为):"+joinPoint);
        System.out.println("前置通知(方法签名为)"+joinPoint.getSignature());
        System.out.println("前置通知(目标对象为)"+joinPoint.getTarget());
        Object[] args = joinPoint.getArgs();
        System.out.print("前置通知(方法参数为):");
        for (Object o:args){
            System.out.print(o+" ");
        }
        System.out.println("\n前置增强");
    }
}

(2)@AfterReturning 后置通知-注解有 returning 属性

在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变 量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

  @AfterReturning(value = "execution(* *..SomeServiceImpl.doSome(..))",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result) {
        System.out.println("后置通知(切入点表达式为):"+joinPoint);
        System.out.println("后置通知(方法签名为)"+joinPoint.getSignature());
        System.out.println("后置通知(目标对象为)"+joinPoint.getTarget());
        Object[] args = joinPoint.getArgs();
        System.out.print("后置通知(方法参数为):");
        for (Object o:args){
            System.out.print(o+" ");
        }

        System.out.println("后置增强目标方法结果"+result);

        System.out.println("\n后置增强");
    }

(3)@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数

在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。

 @Around(value = "execution(* *..SomeServiceImpl.doSome(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知:前");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("目标方法的返回值为:"+proceed);
        System.out.println("环绕通知:后");
    }

(4)@AfterThrowing 异常通知-注解中有 throwing 属性

在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。 当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

   @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSome(..))",throwing = "e")
    public void afterThrowing(Throwable e){
        System.out.println("异常通知" + e);
    }

(5)@After 最终通知

无论目标方法是否抛出异常,该增强均会被执行

  @After(value = "execution(* *..SomeServiceImpl.doSome(..))")
    public void after(){
        System.out.println("最终通知");
    }

(6)@Pointcut 定义切入点

当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。

其用法是,将@Pointcut 注解在一个方法之上,以后所有的 executeion 的 value 属性值 均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcute 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

 

 

更多可以学习参考:Spring 教程

标签:SpringIOC,String,Spring,spring,AOP,简介,注解,方法,public
来源: https://blog.csdn.net/qq_39669058/article/details/94608323

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有