Spring

Spring框架
一、Spring概述
1.Spring就是为了解决软件设计的"解耦"而出生的,它提出的控制反转IoC和面向切面编程AOP都是优秀的解耦方式。
(1)IoC将原来主调方自己产生对象的过程交给Spring容器,由Spring容器创建后自动"注入"给主调方。

(2)AOP将混杂在主业务逻辑中的交叉业务逻辑,如事务,日志,安全等,封装成"切面",在主业务逻辑运行的时候,动态的"织入"给主业务逻辑。这样切面可以复用,主业务逻辑也比原来清晰。AOP面向切面编程是对面向对象编程的一个补充。

(3)Spring的优良特性
[1]非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
[2]依赖注入:DI——Dependency Injection,反转控制(IOC)最经典的实现。
[3]面向切面编程:Aspect Oriented Programming——AOP
[4]容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
[5]组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
[6]一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。

二、Spring与IoC
1.IoC:将原来主调方自己产生对象的过程交给Spring容器,由Spring容器创建后自动"注入"给主调方。通俗讲,不用用户自己创建对象了,而将对象的创建交给Spring容器,并且对象之间的依赖关系也交给Spring容器管理。

举例:
传统的做法:
有Person类
String getName(){
Person p=new Person();
return p.getName();
}

//主调方
main(){
sysout(getName());
}

有Spring框架的做法:
在Spring容器中产生Person的对象,比如对象名为p

String getName(Person p){
    return p.getName();
}

//主调方
main(){
    //从Spring获得对象p
    getName(p);
}

2.Ioc的实现方式:主要分为两种
(1)依赖查找:DL,对象使用者通过Spring提供的回调和上下文,然后自己在Spring容器中查找对应的对象。比如JNDI系统查找。

(2)依赖注入:DI,对象使用者本身不会去new对象也不会去查找Spring容器来获得对象,而是在程序运行的时候,如使用者需要另外的对象支持的时候,Spring容器就"注入"给该使用者,使用者类和被调方类均为POJO(普通类),由于Spring的出现,两者完全基于POJO的依赖。
依赖注入是目前最优秀的解耦方式。

3.第一Spring应用:
开发步骤:
(1)在Eclispe中安装Spring Tools,在线安装地址:https://download.springsource.com/release/TOOLS/update/e4.10/

(2)导入Spring的jar包和log4j的jar包,并配置log4j的属性文件

(3)创建JavaBean

(4)使用Spring Tools创建Spring的配置文件,并配置bean对象:
(选择约束对象与版本)

<!-- 定义一个Emp的bean对象,将emp对象交给Spring容器来管理 -->
<!-- id:该bean的id名称,可以唯一的标志出该bean,class:该bean对应的全类名 -->
<bean id="emp" class="com.edu.beans.Emp"/>

<bean id="student" class="com.atguigu.helloworld.bean.Student">
        <!-- 使用property子元素为bean的属性赋值 -->
        <property name="studentId" value="1001"/>
        <property name="stuName" value="Tom2015"/>
        <property name="age" value="20"/>
</bean>

(5)从Spring容器中获得bean对象并使用

//获得Spring容器,从类路径(即src目录)下加载applicationContext.xml配置文件来产生Spring容器
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
//从Spring容器中通过bean的id获得bean对象
Emp emp = (Emp)ac.getBean("emp");

4.Spring的容器ApplicationContext,有两个实现类:

ClassPathXmlApplicationContext//从classpath下(src目录下)找applicationContext.xml/ioc容器配置文件在类路径下
FileSystemXmlApplicationContext//从文件路径中找applicationContext.xml/iocioc容器配置文件在磁盘路径下

当容器ApplicationContext被实例化后,bean对象就已经被容器创建,而不是在bean被使用时才创建。

存在的几个问题:
    1) src.源码包开始的路径,称为类路径的开始;所有源码包里面的东西都会被合并放在类路径里面;
        java:/bin/
        web:/WEB-INF/classes/
    2) 导包commons-logging-1.1.3.jar(依赖)
    3) 先导包再创建配置文件
    4) Spring的容器接管了标志了s的类
    
几个细节:
    1) ApplicationContext(IOC容器的接口)
    2) 给容器中注册一个组件;我们也从组件中按照id拿到了这个组件的对象?
        组件的创建工作,是容器完成;
        Person对象是什么时候创建好了呢?
    3) 同一个组件在ioc容器中是单实例的;容器启动完成都已经创建准备好的
    4)容器中如果没有这个组件,获取组件?报异常
    org.springframework.beans.factory.NoSuchBeanDefinitionException: 
    No bean named 'person02' is defined
    5) ioc容器在创建这个组件对象的时候,(property)会利用setter方法为javaBean的属性进行赋值
    6) javaBean的属性名是由什么决定的?getter/setter方法是属性名:set去掉,后面那一串首字母小写
        所有getter/setter方法都自动生成!
  

当容器ApplicationContext被实例化后,bean对象就已经被容器创建,而不是在bean被使用时才创建。

通过Bean的类型从IOC中获取bean的实例

ApplicationContext ioc = 
                new ClassPathXmlApplicationContext("ioc.xml");
        Person person= (Person) ioc.getBean(Person.class);
  如果IOC该类型的bean有多个,就会报错
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.atguigu.bean.Person] is defined: expected single matching bean but found 2: person01,person02
Person person= ioc.getBean("person01",Person.class);//传id时,规定类型

5.<u>BeanFactory,Spring的容器的另种表示方式。</u>
该类的实现类XmlBeanFactory
BeanFactory bf=new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
Emp emp =(Emp)bf.getBean("emp");

当容器BeanFactory被实例化后,bean对象就没有被容器创建,而是在bean被使用时才创建。

6.Spring容器中bean的作用域:创建<bean>的时候,该标签有一个scope属性,表示该bean的作用域:
(1)singleton:单例的,是<u>默认</u>的作用域,即在整个Spring容器中只有一个该bean对象的实例,无论产生多少次。bean对象在容器初始化 的时候就已经创建。

​ (2)propotype:多例的,每次从容器中获得的该bean的实例都是一个新的实例。bean对象在使用的时候才初始化,即进行的延迟。

​ (3)request:对于每一个请求都会创建一个新的bean对象,当然,该属性仅对于WEB应用有效。

​ (4)session:对于每一次会话都会创建一个新的bean对象,当然,该属性仅对于WEB应用有效。

​ (5)global session:对于每一次全局的会话会创建一个新的bean对象,主要用于集群方面。

7.Bean后处理器:BeanPostProcessor接口,在Spring容器初始化bean对象的时候,接口的方法被调用,它所包含的两个接口方法:

Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
在bean被容器初始化之前被调用的方法,其中参数bean表示当前被初始化的bean对象,beanName表示当前被初始化的bean的id或name,方法返回的对象即为该bean对象。

Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
在bean被容器初始化之后被调用的方法,其中参数bean表示当前被初始化的bean对象,beanName表示当前被初始化的bean的id或name,方法返回的对象即为该bean对象。

后处理的配置:

<!-- 配置BeanPostProcessor,该bean由容器自动调用,所以无需id -->
<bean class="com.edu.utils.MyBeanPostProcessor"/>

8.定制bean的始末:

public void setUp() {
    System.out.println("setup...................");
}

public void tearDown() {
    System.out.println("teardown...................");
}


<bean id="emp" class="com.edu.beans.Emp" init-method="setUp" destroy-method="tearDown"/>

9.Spring的执行流程:bean的生命周期
(1)调用无参构造创建bean
(2)如果bean实现了BeanNameAware,则执行setBeanName(String bn)设置bean名称
(3)如果bean实现了BeanFactoryAware,则执行setBeanFactory(BeanFactory bf)设置BeanFactory
(4)如果bean实现了BeanPostProcessor,则执行BeanPostProcessBeforeInitialization()方法
(6)如果实现了InintializtionBean,则执行AfterPropertiesSet(),此时标志该bean即该bean的属性被初始化完成
(7)调用init-method
(8)执行则执行BeanPostProcessAfterInitialization()
(9)执行bean的业务方法
(10)如果实现了DisposableBean接口,调用destory()
(11)调用destroy-method

基于XML配置的注入

class Person{
    private int pid;
    private String pname;

    public Person(){
    
    }
    
    public Person(int pid,String pname){
        this.pid=pid;
        this.pname=pname;
    }
    
    getter()/setter()
}
<bean id="person" class="Person"></bean>

person.setter();

1.通过Spring创建一个bean对象的时候,其实质就是调用bean的默认构造方法产生对象,此时该bean的属性没有初始化,而属性初始化的工作由Spring来完成,此时Spring给属性赋值的操作成为"注入",注入主要分为设值注入和构造注入。

(1)设值注入(本质是通过setter方法注入)
I.注入简单类型属性值

public class Student {
    private int sid;
    private String sname;
    private int sage;

    setter/getter
    ...
}
<bean id="student" class="com.edu.beans.Student">
    <!-- 设置注入,通过调用bean的setter方法来注入值 -->
    <property name="sid" value="1"/>
    <property name="sname" value="丽丽"/>
    <property name="sage" value="24"/>
</bean>

II.注入域属性值(属性的类型是自定义的类型)

public class Student {
    private int sid;
    private String sname;
    private int sage;
    private School school;//域属性

    setter/getter
    ...
}
<bean id="student" class="com.edu.beans.Student">
    <property name="sid" value="1"/>
    <property name="sname" value="张三"/>
    <!-- 给基本类型属性注入值,使用value属性注入 -->
    <property name="sage" value="21"/>
    <!-- 给域属性注入值,采用ref,因为此时的school是引用类型,ref的值为上面的school这个bean的id -->
    <property name="school" ref="school"/>
</bean>

(2)构造注入(调用带参的构造函数进行属性值的注入)

<bean id="student" class="com.edu.beans.Student">
    <!-- 构造注入:给基本类型属性注入值 -->
    <constructor-arg name="sid" value="1"/>
    <constructor-arg name="sname" value="李四"/>
    <constructor-arg name="sage" value="23"/>
    <!-- 构造注入:给引用类型属性(域属性)注入值 -->
    <constructor-arg name="school" ref="school"/>
</bean>

(3)p和c命名空间注入
p命名空间注入使用的是setter注入方式
c命名空间注入使用的是构造注入方式

<bean id="student" class="com.edu.beans.Student" p:sid="1" p:sname="丽丽" p:sage="22" p:school-ref="school"/>

<bean id="student" class="com.edu.beans.Student" c:sid="1" c:sname="王五" c:sage="23" c:school-ref="sc"/>

(4)注入集合属性值

<bean id="sCollection" class="com.edu.beans.SCollections">
        <property name="strs">
            <!-- 对数组类型的属性注入值 -->
            <array>
                <!-- 注入简单数据类型的数据 -->
                <value>abc</value>
                <value>def</value>
                <value>ghi</value>
            </array>
        </property>
        <property name="students">
            <!-- 给List类型的属性注入值 -->
            <list>
                <!-- 注入的值为其他的引用数据类型 -->
                <ref bean="student"/>
                <ref bean="student2"/>
            </list>
        </property>
        <property name="ints">
            <!-- 给Set类型的属性注入值 -->
            <set>
                <!-- 注入简单数据类型的数据 -->
                <value>123</value>
                <value>456</value>
            </set>
        </property>
        <property name="myMap">
            <!-- 给map属性注入值 -->
            <map>
                <!-- 给map中的每一个键值对注入值 -->
                <entry key="南京" value-ref="school"/>
                <entry key="成都" value-ref="school1"/>
            </map>
        </property>
        <property name="props">
            <!-- 给Properties属性注入值 -->
            <props>
                <!-- 给Properties的每一个键值对赋值 -->
                <prop key="addr">南京市</prop>
                <prop key="tel">13800000000</prop>
            </props>
        </property>
</bean>

(5)域属性的自动注入,一个bean中包含的域属性(其他bean作为属性)可以不用显式的注入值,通过Spring的自动注入隐式的注入值。
自动注入的方式分为两种:byName:按名字注入,byType:按类型注入

I.byName:按名字注入,要求被注入域属性值的bean中的域属性名和配置文件中配置的域属性对应的bean的id同名,Spring就会隐式自动注入域属性值。
JavaBean:

public class Student {
    private int sid;
    private String sname;
    private int sage;
    private School school;//域属性,域属性名需要和配置文件中School这个bean的id同名,则能完成自动注入

    ...
    getter/setter
}
<bean id="school" class="com.edu.beans.School">
    <property name="scid" value="1001"/>
    <property name="scname" value="南京千峰教育"/>
</bean>
<!-- 通过autowire="byName"来按名称进行自动注入 -->
<bean id="student" class="com.edu.beans.Student" autowire="byName">
    <property name="sid" value="1"/>
    <property name="sname" value="张三"/>
    <property name="sage" value="21"/>
</bean>

II.byType:按类型注入,要求被注入域属性值的bean中的域属性的类型要和配置文件中配置的域属性对应的bean的类型同源(要么是相同类型,要么是它的子类),Spring就会按类型自动注入域属性值,注意:此时配置文件中只能有一个该类型的域属性对应的bean,多个这样的bean则Spring就不知道用哪一个来自动注入。
JavaBean:

public class Student {
    private int sid;
    private String sname;
    private int sage;
    private School school;//域属性,域属性名需要和配置文件中School这个bean的id同名,则能完成自动注入

    ...
    getter/setter
}
<bean id="school" class="com.edu.beans.School">
    <property name="scid" value="1001"/>
    <property name="scname" value="南京千峰教育"/>
</bean>
<!-- 通过autowire="byName"来按名称进行自动注入 -->
<bean id="student" class="com.edu.beans.Student" autowire="byType">
    <property name="sid" value="1"/>
    <property name="sname" value="张三"/>
    <property name="sage" value="21"/>
</bean>

(6)使用SPEL(Spring的EL表达式,符合OGNL表达式的规则)注入
用法:
调用java的API:

<property name="age" value="#{T(java.lang.Math).random()*30}"/>

<property name="school" value="#{school}"/>

<property name="age" value="#{myUtils.getAge()}"/>

(7)使用内部bean注入:如果用户希望某个bean只能被一个bean内部使用,而不是用于给其他bean共享,则可以把该bean定义为内部bean。

<!-- 内部bean -->
<bean id="student3" class="com.edu.beans.Student">
    <property name="sid" value="2"/>
    <property name="sname" value="李四"/>
    <!-- 给基本类型属性注入值,使用value属性注入 -->
    <property name="sage" value="25"/>
    <property name="school">
        <!-- 给域属性school注入内部bean,该bean只能在student内部使用,所有无需id -->
        <bean class="com.edu.beans.School">
            <property name="scid" value="2001"/>
            <property name="scname" value="杭州千峰"/>
        </bean>
    </property>
</bean>

(8)使用同类抽象bean注入:同一个类产生的不同对象中如果有一些属性具有相同的值,那么可以将这个类的这些属性专门封装到一个抽象的bean
中,然后把该bean注入给该类的对象,以期复用该抽象bean。

<!-- 
    同类的抽象bean,abstract="true"表示该bean是抽象的,不能实例化,
    即不能通过getBean()获取对象,该bean的作用就是用于给其他bean继承
 -->
<bean id="department" class="com.edu.beans.Employee" abstract="true">
    <property name="deptno" value="1002"/>
    <property name="dname" value="研发部"/>
</bean>

<!-- parent="department"表示从上面的抽象bean中继承抽象bean的属性及值 -->
<bean id="emp1" class="com.edu.beans.Employee" parent="department">
    <property name="empno" value="7788"/>
    <property name="ename" value="SCOTT"/>
</bean>

<bean id="emp2" class="com.edu.beans.Employee" parent="department">
    <property name="empno" value="7369"/>
    <property name="ename" value="SNAKE"/>
</bean>

(9)使用异类抽象bean注入:不同的类有相同的属性,可以将这些属性抽取出来形成一个异类的抽象bean,从而在其他具有这些属性的bean的定义时,继承这个异类抽象bean,以期复用这个异类抽象bean。

<!-- 
    异类的抽象bean,abstract="true"表示该bean是抽象的,不能实例化,
    即不能通过getBean()获取对象,该bean的作用就是用于给其他bean继承
 -->
 <bean id="dept" abstract="true">
    <property name="deptno" value="1002"/>
    <property name="dname" value="研发部"/>
 </bean>

 <!-- parent="dept"表示从上面的抽象bean中继承抽象bean的属性及值 -->
 <bean id="emp" class="com.edu.beans.Employee" parent="dept">
    <property name="empno" value="7788"/>
    <property name="ename" value="SCOTT"/>
 </bean>

<bean id="manager" class="com.edu.beans.Manager" parent="dept">
    <property name="mno" value="7906"/>
    <property name="mname" value="ALEN"/>
</bean>

(10)为Spring指定多个配置文件
I.平行关系的配置文件
比如有spring1.xml,spring2.xml,spring3.xml这三个Spring的配置文件时平行关系的,那么在获得bean对象的时候可以这样使用:

String[] configures={"spring1.xml","spring2.xml","spring3.xml"};

ApplicationContext ac=new ClassPathXmlApplicationContext(configures);

getBean(id); //该id为上面三个xml配置中任何一个里面配置的bean

II.包含关系:
比如springctrl.xml包含springservice.xml,springservice.xml包含springdao.xml,那么可以单独定义service和dao的xml,
然后在springctrl.xml总配置文件中通过<import>标签导入service和dao的xml。

<import resource="springservice.xml"/>
<import resource="springdao.xml"/>

ApplicationContext ac=new ClassPathXmlApplicationContext("springctrl.xml");

getBean(id); //该id为上面三个xml配置中任何一个里面配置的bean

三、基于注解的DI
第一步:定义bean,并注入简单属性值

/*
 * @Component等效于xml配置中的<bean>,其value属性等效于xml配置<bean>中的id属性,如果只有一个value属性,可以省略value,
 * @Component("school")等效 <bean id="school" class="com.edu.beans.School"/>
 *  除了使用@Component定义bean之外,还可以使用@Respository,@Service,@Controller来定义bean,这三个注解的功能是一样,都是定义Spring容器中的bean,扩展 这三个注解的目的是为了后期的扩展,比如@Repository,@Service,@Controller分别表示 Dao,Service,Controller层的bean.
 */
@Component("school")
public class School {
    /*
     * @Value:注入简单类型的值,如果只有一个value属性,可以省略value。
     * 该注解注入值的方式采用的是设值注入,但是如果将该注解添加到属性上面的时候,可以省略掉setter方法,如果有setter方法,该注解还可以加在 setter方法上。
     */
    @Value("1001")
    private int scid;
    @Value("千峰教育")
    private String scname;

    @Override
    public String toString() {
        return "School [scid=" + scid + ", scname=" + scname + "]";
    }

}

第二步:定义bean,并注入简单属性值和域属性值

@Component("student")
public class Student {
    @Value("11")
    private int sid;
    @Value("丽丽")
    private String sname;
    @Value("21")
    private int sage;
    /*
     * 此时,对于域属性,注解 方式注入值都采用自动注入的方式,分为按名称自动注入和按类型自动注入
     * 按名称自动注入:
     * @Autowired
     * @Qualifier("school")
     * 
     * 按类型自动注入:
     * @Autowired
     * 
     * 另外一个注解即可按名称也可按类型
     *     按类型:@Resource
     *     按名称:@Resource(name="school")
     */
    @Autowired
    private School school;//域属性

    @Override
    public String toString() {
        return "Student [sid=" + sid + ", sname=" + sname + ", sage=" + sage + ", school=" + school + "]";
    }

}

第三步:扫描注解基本包

<!-- 扫描注解的基本包,让Spring的注解生效 -->
<context:component-scan base-package="com.edu.beans"/>

第四步:测试

1.AOP:

面向切面编程,是程序设计的一种思想,是面向对象编程的一种补充。它的大致思想就是,代码主要分为主业务逻辑和交叉业务逻辑,主业务逻辑主要是指具体的业务,比如转账这个业务,交叉业务逻辑是指混杂在主业务逻辑中的一些其他代码,比如转账这个主业务逻辑在执行的时候,需要事务,日志,安全的代码支持,这些代码被称为交叉业务逻辑,这些交叉业务逻辑混杂在主业务逻辑中使代码看起来混杂不清,逻辑不清晰,AOP编程就是把这些交叉业务逻辑代码单独提取出来,通过AOP编程让这些交叉业务逻辑在主业务逻辑代码运行的时候,动态的"织入"进来,那么,这样主业务也显得清晰,交叉业务逻辑也可以得到复用。

2.引入AOP:

(1)通过继承交叉业务逻辑实现:交叉业务逻辑也纠缠在了主业务逻辑代码中,同时主业务逻辑继承了交叉业务逻辑就不能继承其他类了

(2)通过JDK动态代理方式实现

3.AOP的实现:

(1)AOP是对OOP的补充,OOP是从静态方面考虑代码的复用性,而AOP是从动态方面考虑代码的复用性。AOP的实现常用的方式有两种:通过JDK动态代理实现和通过CGLIB动态代理实现。

(2)AOP,面向切面编程,将可以复用的交叉业务逻辑提取出来形成"切面",通过动态代理,在主业务逻辑方法运行的时候,动态的"织入"给主业务逻辑方法。

(3)AOP编程的相关术语:

I.切面:泛指交叉业务逻辑,比如混杂在转账主业务中的譬如事务,日志,安全等交叉业务逻辑代码叫做"切面",如引入AOP中的MyAspect类就叫做切面

II.织入:在主业务逻辑运行的时候,动态的调用交叉业务逻辑方法的过程叫做"织入",入引入AOP中的new InvocationHandler() 匿名内部类中做的事情就叫做"织入"。

III.连接点:泛指可被织入切面的主业务逻辑方法,如引入AOP中的SomeServiceImpl类的doSome(),doOther()都叫做"连接点"。

IV.切入点:指已经被代理增强了的主业务逻辑方法,比如doSome方法被织入了切面,那么doSome()可以成为切入点,如果doOther没有被织入切面,那么doOther只能叫做连接点,不能叫做切入点。"切入点"定义了"切面"被"织入"到主业务逻辑的"位置",即具体"织入"给哪个主业务逻辑方法。

V.目标对象:即主业务逻辑对应的类的对象,如引入AOP中的service对象。

VI.通知:是切面的一种实现,通知可以完成简单的织入功能,如引入AOP中的new InvocationHandler() 可以泛指通知,通知定义了切面的织入时间,是在主业务方法执行之前执行还是之后执行。切入点定义了切面织入的位置,通知定义了切面织入的时间。

VII.顾问:是切面的另一种实现方式,顾问可以以复杂的方式来织入切面。

(4)Spring提供了对AOP的支持,其他很多框架都提供了对AOP的支持,其中AspectJ也实现了AOP,并且AspectJ支持XML配置和注解方式来使用AOP,并且使用方便快捷,所以Spring又将AspectJ吸收到自己的框架中,所以AOP程序员可以在Spring框架中使用AspectJ提供的AOP编程。

(5)AspectJ是一个面向切面编程的框架,它扩展了java语言,有一套自己对AOP支持的语法,通过自己的特有的编译器将AOP代码最终编译成符合java规范的字节码文件。

(6)AspectJ基于XML配置方式的AOP:

开发步骤:

第一步:导入Spring的所有jar,还要包含对AspectJ支持的jar包:
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.4.RELEASE.jar
spring-aop-4.3.9.RELEASE.jar
spring-aspects-4.3.9.RELEASE.jar

第二步:定义目标类及目标对象(主业务逻辑),将对象放在Spring容器中

<!-- 目标对象:待增强的对象,即即将被织入切面的代码 -->
<bean id="someService" class="com.edu.service.impl.SomeServiceImpl"/>

第三步:定义切面及通知

I.AspectJ的通知类型:通知主要是指切面中包含的方法。
A.前置通知:在目标方法(主业务逻辑方法)执行之前执行的方法。

public class MyAspect { //切面(交叉业务逻辑)
    //通知方法(交叉业务逻辑方法)
    
    /*
     * 前置通知:方法名任意,可以包含一个JoinPoint类型的参数,该类型的参数可以获得目标方法的相关信息
     */
    public void before(JoinPoint jp) {
        System.out.println("切面中的前置通知");
        System.out.println("切面中的前置通知,目标方法的签名:"+jp.getSignature());
        System.out.println("切面中的前置通知,目标对象名:"+jp.getTarget().getClass().getName());
    }

}

B.后置通知:在目标方法执行之后执行的方法。

/*
 * 后置通知:方法名任意,除了可以包含JoinPoint参数外,还可以包含一个Object的参数,参数名和xml中配置的后置通知的returning属性的值相同,
 * 表示目标方法的返回值
 */
public void afterReturning(Object res) {
    System.out.println("切面中的后置通知:目标方法的返回值为:"+res);
}


C.环绕通知(方法拦截器):在目标方法执行之前和之后执行的方法。
/*
 * 环绕通知:方法名任意,除了可以包含JoinPoint参数外,还可以包含ProceedingJoinPoint参数,该参数的proceed()方法的执行相当于执行目标方法
 * 所以
 *
 */
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("切面中的环绕通知前置增强.....");
    Object res = pjp.proceed(); //相当于执行目标方法,返回值代表目标方法的返回值
    System.out.println("切面中的环绕通知后置增强.....");
    return res;
}


D.异常通知:在目标方法发生异常的时候执行的方法。
/*
 * 异常通知:方法名随意,,除了可以包含JoinPoint参数外,还可以包含Throwalbe类型的参数,参数名需要和aop的xml配置中的throwing属性值相同
 * 表示当前的异常对象
 */
public void afterThrowing(Throwable e) {
    System.out.println("切面中的异常通知,异常信息:"+e.getMessage());    
}

E.最终通知:无论有没有发生异常,该最终通知都会执行的方法,类似try...catch的finally。

第四步:定义切入点表达式:切入点表达式的目的就是指明在哪些目标方法中织入通知
切入点表达式的语法:

execution(访问权限 返回值 全类名.方法名(参数列表) 异常类型)

切入点表达式中的通配符:

符号              意义

*               0个或多个任意字符

..              用在包中表示当前包及其子包路径
                用在方法参数中表示任意的参数

+               用在类上面表示当前类及其子类
                用在接口上面表示当前接口及其实现类

举例:

execution(* *.get(..)):匹配任意的get方法

execution(* com.edu.service..*.doSome(..)):匹配com.edu.service包或其子包下面的所有类的doSome方法,注意:当".."用在包中的时候,后面的类名只能用"*"。

execution(* com.edu.service..*.doOther(Object+)):匹配com.edu.service包或其子包下面的所有类的doOther方法,方法参数匹配所有引用 类型。

第五步:通过AOP的XML配置将通知通过切入点表达式织入到目标方法中

    <!-- 目标对象:待增强的对象,即即将被织入切面的代码 -->
    <bean id="someService" class="com.edu.service.impl.SomeServiceImpl"/>
    
    <!-- 切面对象:被织入到目标方法中的对象 -->
    <bean id="myAspect" class="com.edu.utils.MyAspect"/>


    
    <!-- 定义AspectJ的AOP配置,完成切面织入到目标方法 ,打开aop命名空间-->
    <aop:config>
        <aop:pointcut expression="execution(* com.edu.service..*.doSome(..))" id="aopPC"/>
        <aop:pointcut expression="execution(* com.edu.service..*.doOther(..))" id="aopPC1"/>
        <aop:pointcut expression="execution(* com.edu.service..*.doThird(..))" id="aopPC2"/>
        <!-- 定义切面,ref:指定切面对象,即为前面定义的切面的id -->
        <aop:aspect ref="myAspect">
            <!-- 切面的前置通知,method:切面中的前置通知方法的名称, pointcut-ref:前面定义的切入点的id,即将该前置通知织入给切入点指明的目标方法-->
            <aop:before method="before" pointcut-ref="aopPC"/>
            <!-- 切面的后置通知,returning:返回给通知方法的参数名,表示目标方法的返回值 -->
            <aop:after-returning method="afterReturning" pointcut-ref="aopPC1" returning="res"/>
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="aopPC2"/>
            <!-- 异常通知,throwing:异常通知方法参数名,表示当前发生异常 -->
            <aop:after-throwing method="afterThrowing" pointcut-ref="aopPC" throwing="e"/>
    </aop:config>

第六步:测试

(7)基于注解的Spring的AspectJ的AOP

开发步骤:
第一步:导入Spring的所有jar,还要包含对AspectJ支持的jar包:
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.4.RELEASE.jar
spring-aop-4.3.9.RELEASE.jar
spring-aspects-4.3.9.RELEASE.jar

第二步:定义目标类及目标对象(主业务逻辑),将对象放在Spring容器中
@Component("someService")
public class SomeServiceImpl implements ISomeService { //目标类(主业务逻辑)

...

}

第三步:定义切面及通知

@Component //将该切面类对象放入Spring容器
@Aspect //定义该类为切面
public class MyAspect { //切面(交叉业务逻辑)
    //通知方法(交叉业务逻辑方法)
    
    /*
     * 前置通知:方法名任意,可以包含一个JoinPoint类型的参数,该类型的参数可以获得目标方法的相关信息
     */
    @Before("execution(* com.edu.service..*.doSome(..))")
    public void before(JoinPoint jp) {
        System.out.println("切面中的前置通知");
        System.out.println("切面中的前置通知,目标方法的签名:"+jp.getSignature());
        System.out.println("切面中的前置通知,目标对象名:"+jp.getTarget().getClass().getName());
    }
    
    /*
     * 后置通知:方法名任意,除了可以包含JoinPoint参数外,还可以包含一个Object的参数,参数名和xml中配置的后置通知的returning属性的值相同,
     * 表示目标方法的返回值
     */
    @AfterReturning(value = "execution(* com.edu.service..*.doOther(..))",returning = "res")
    public void afterReturning(Object res) {
        System.out.println("切面中的后置通知:目标方法的返回值为:"+res);
    }
    
    /*
     * 环绕通知:方法名任意,除了可以包含JoinPoint参数外,还可以包含ProceedingJoinPoint参数,该参数的proceed()方法的执行相当于执行目标方法
     * 所以
     *
     */
    @Around("execution(* com.edu.service..*.doThird(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("切面中的环绕通知前置增强.....");
        Object res = pjp.proceed(); //相当于执行目标方法,返回值代表目标方法的返回值
        System.out.println("切面中的环绕通知后置增强.....");
        return res;
    }
    
    /*
     * 异常通知:方法名随意,,除了可以包含JoinPoint参数外,还可以包含Throwalbe类型的参数,参数名需要和aop的xml配置中的throwing属性值相同
     * 表示当前的异常对象
     */
    @AfterThrowing(value = "execution(* com.edu.service..*.doSome(..))",throwing = "e")
    public void afterThrowing(Throwable e) {
        System.out.println("切面中的异常通知,异常信息:"+e.getMessage());    
    }


}

第四步:配置自动代理及扫描注解基本包

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

<!-- 配置AspectJ的自动代理,会生成注解的AOP代理对象,将扫描注解中注解的切面和通知,然后在目标方法执行时,动态的将它们织入给目标方法 -->
<aop:aspectj-autoproxy/>

第五步:测试

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容