IOC:

Spring简介

  1. spring是分层的(一站式)轻量级开源框架,他以IoC(Inversion of Control,控制反转),和AOP(Aspect Oriented Programming,面向切面编程)为内核。
  2. Spring是一个企业级开发框架,是软件设计层面的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件。
  3. Spring框架的优点
  • 非侵入式设计:Spring是一种非侵入式(non-invasive)框架,它可以使应用程序代码对框架的依赖最小化。
  • 方便解耦、简化开发:Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护工作都交给Spring容器的管理,大大的降低了组件之间的耦合性。
  • 支持AOP:Spring提供了对AOP的支持,它允许将一些通用任务,如安全、事物、日志等进行集中式处理,从而提高了程序的复用性。
  • 支持声明式事务处理:只需要通过配置就可以完成对事物的管理,而无须手动编程。
  • 方便程序的测试:Spring提供了对Junit4的支持,可以通过注解方便的测试Spring程序。
  • 方便集成各种优秀框架:Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如Struts、Hibernate、MyBatis、Quartz等)的直接支持。
  • 降低Jave EE API的使用难度:Spring对Java EE开发中非常难用的一些API(如JDBC、JavaMail等),都提供了封装,使这些API应用难度大大降低。

第一个Spring

  • 新建Maven工程添加依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.4</version>
</dependency>
  • 创建实体类Student
package pojo;

public class Student {
    private long id;
    private String name;
    private int age;
	...请补充get、set
}
  • 通过IOC创建对象,在配置文件中添加需要管理的对象,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="student" class="pojo.Student">
    <property name="id" value="1"></property>
    <property name="name" value="wcy"></property>
    <property name="age" value="21"></property>
</bean>
</beans>

1、通过bean标签来完成对象的管理

  • id:对象名
  • class对象的模板类。(所有交给IoC容器来管理的类必须有无参构造函数,因为Spring底层是通过反射机制来创建对象,调用的是无参构造)

2、成员变量通过property标签完成赋值

  • name:成员变量名
  • value:成员变量(基本数据类型,String等可以直接赋值,如果是其他引用类型,不能通过value赋值)
  • ref:将IoC中的另外一个Bean赋给当前的成员变量(DI)
  • 通过IoC容器创建对象
package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;

public class Test {
    public static void main(String[] args) {
        //加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.XML");
        //通过运行时类获取
        Student student = (Student) applicationContext.getBean(Student.class);
        System.out.println(student);
    }
}

Spring的两大核心容器

ApplicationContext是BenaFactor的一个子接口,也被称为应用上下文,是另一种常用的Spring核心容器。它不仅包含了BeanFactor的所有功能,还添加了对国际化,资源访问,事件传播等方面的支持。建议使用该接口

  1. BeanFactory(IoC容器)
    使用BeanFactory加载spring.xml
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new FileSystemResource("D:\\files\\idea\\JAVA\\spring_2021.3.22\\src\\main\\resources\\spring.xml"));
        Student student =(Student) xmlBeanFactory.getBean("student");
        System.out.println(student);

通过绝对路径拿到spring.xml配置文件

  1. ApplicationContext(IoC容器)
    通过FileSystemXmlApplicationContext创建:通过绝对路径
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\files\\idea\\JAVA\\spring_2021.3.22\\src\\main\\resources\\spring.xml");
        Student student = (Student) applicationContext.getBean("student");
        System.out.println(student);

通过ClassPathXmlApplicationContext创建:通过类路径

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Student student =(Student)applicationContext.getBean("student");
        System.out.println(student);
  1. 从IoC中获取对象
    通过配置文件已经创建了IoC容器,下面通过两种方式从IoC容器中获取对象
    通过Bean的id属性获取Bean
package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;

public class Test {
    public static void main(String[] args) {
        //加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.XML");
        //通过id获取
        Student student = (Student) applicationContext.getBean("student");
        System.out.println(student);
    }
}

通过运行时类获取Bean

package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;

public class Test {
    public static void main(String[] args) {
        //加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.XML");
        //通过运行时类获取
        Student student = (Student) applicationContext.getBean(Student.class);
        System.out.println(student);
    }
}

但是这种方式有一种弊端,配置文件中只能有一个实例。否则会抛出异常,因为没有唯一的bean

IOC/DI(依赖注入)

Spring 最认同的技术是控制反转的依赖注入(DI)模式。控制反转(IoC)是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。

  1. 构造注入
    通过有参构造方法
<!-- 构造方式注入的三种方式 -->
    <!--下标-->
    <bean id="student2" class="www.sheep.pojo.Student2">
        <constructor-arg index="0" value="1"/>
        <constructor-arg index="1" value="c罗"/>
        <constructor-arg index="2" value="36"/>
    </bean>
    <!--名字-->
    <bean id="student3" class="www.sheep.pojo.Student2">
        <constructor-arg name="id" value="2"/>
        <constructor-arg name="name" value="内马尔"/>
        <constructor-arg name="age" value="29"/>
    </bean>
    <!--数据类型-->
    <bean id="student4" class="www.sheep.pojo.Student2">
        <constructor-arg type="int" value="3"/>
        <constructor-arg type="java.lang.String" value="林语堂"/>
        <constructor-arg type="java.lang.String" value="100"/>
    </bean>

通过有参构造注入(pojo、String[]、List、Map)

private wangchaoyu wangchaoyu;
private String[] strings;
private List<wangchaoyu> list;
private Map<String,wangchaoyu> map;
<!-- 引用类型 -->
        <constructor-arg name="wangchaoyu" ref="wangchaoyu"/>
        <!-- 数组类型 -->
        <constructor-arg name="strings">
            <array>
                <value>三国演义</value>
                <value>西游记</value>
                <value>红楼梦</value>
                <value>水浒传</value>
            </array>
        </constructor-arg>
        <!-- List集合 -->
        <constructor-arg name="list">
            <list>
                <ref bean="wangchaoyu2"/>
            </list>
        </constructor-arg>
        <!-- Map集合 -->
        <constructor-arg name="map">
            <map>
                <entry key="功夫" value-ref="wangchaoyu3"/>
            </map>
        </constructor-arg>
  1. Set注入
    通过Set方法(通过Set方法注入要写无参构造)
    基本数据类型注入
<bean id="student2" class="www.sheep.pojo.Student">
        <property name="id" value="1"/>
        <property name="name" value="我们梦想奔赴大海"/>
        <property name="age" value="21"/>
</bean>

复杂类型注入

private wangchaoyu wangchaoyu;
private String[] strings;
private List<wangchaoyu> list;
private Map<String,wangchaoyu> map;
<bean id="student2" class="www.sheep.pojo.Student">
        <!-- javaBean -->
        <property name="wangchaoyu" ref="wangchaoyu"/>
        <!-- String[] -->
        <property name="strings">
            <array>
                <value>红楼梦</value>
                <value>西游记</value>
                <value>三国演义</value>
                <value>水浒传</value>
            </array>
        </property>
        <!-- List -->
        <property name="list">
            <list>
                <ref bean="wangchaoyu2"/>
            </list>
        </property>
        <!-- Map -->
        <property name="map">
            <map>
                <entry key="hello" value-ref="wangchaoyu3"/>
            </map>
        </property>
    </bean>
  1. p命名空间注入、c名门空间注入
    导入C命名空间依赖、实体类中必须存在有参构造器。属于:<property/>
xmlns:c="http://www.springframework.org/schema/c"
<!-- C命名空间注入 -->
<bean id="wangchaoyu4" class="www.sheep.pojo.wangchaoyu" c:id="1" c:name="梅西" c:age="36"/>

实体类中必须有set方法、实体类中必须有无参构造器(默认存在)。属于:<constructor-arg/>

xmlns:p="http://www.springframework.org/schema/p"
<!-- p命名空间注入 -->
<bean id="wangchaoyu5" class="www.sheep.pojo.wangchaoyu" p:id="2" p:name="c罗" c:age="34"/>

Spring中的Bean

  1. Bean的实例化三种方式
  • 构造器实例化:构造器实例化是指IoC容器通过Bean对应类中默认的无参构造方法来实例化Bean。
  • 静态工厂实例化
    创建一个实体类
package www.sheep.pojo;

public class Car {
    private long id;
    private String name;
	...请补充get、set
}

创建一个静态工厂类

public class StaticCarFactory {
    private static Map<Long, Car> carMap;
    static {
        carMap = new HashMap<Long,Car>();
        carMap.put(1L,new Car(1L,"宝马"));
        carMap.put(2L,new Car(2L,"奔驰"));
        carMap.put(3L,new Car(3L,"奥迪"));
    }
    public static Car getCar(long id){
        return carMap.get(id);
    }
}

在spring.xml中配置静态工厂,通过factory-method:调用静态工厂的方法创建对象

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
 <!-- 配置静态工厂创建Car -->
    <bean id="car" class="www.sheep.factory.StaticCarFactory" factory-method="getCar">
        <constructor-arg value="2"/>
    </bean>
</beans>

创建Handler类

public class Test3 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-staticfactory.xml");
        Car car =(Car) applicationContext.getBean("car");
        System.out.println(car);
 }
}
  • 实例工厂实例化
    创建实体类
package www.sheep.pojo;
     
     public class Car {
         private long id;
         private String name;
  	...请补充get、set
     }

创建实例工厂

public class InstanceCarFactory {
     private Map<Long, Car> carMap;
     public InstanceCarFactory(){
         carMap = new HashMap<Long,Car>();
         carMap.put(1L,new Car(1L,"宝马"));
      carMap.put(2L,new Car(2L,"奔驰"));
         carMap.put(3L,new Car(3L,"奥迪"));
  }
     public Car getCar(long id){
      return carMap.get(id);
     }
 }

在spring.xml中配置调用动态工厂的方法创建对象,先创建InstanceCarFactory工厂,在通过InstanceCarFactory调用getCar方法创建对象。

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"> 
  <!-- 创建实例工厂bean -->
    <bean id="carFactory" class="www.sheep.factory.InstanceCarFactory"/>
    <!-- 赔偿实例工厂创建Car -->
    <bean id="car2" factory-bean="carFactory" factory-method="getCar">
        <constructor-arg value="1"/>
    </bean>
</beans>

创建Handler类

public class Test3 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-staticfactory.xml");
        Car car =(Car) applicationContext.getBean("car2");
        System.out.println(car);
	}
}
  1. Bean的作用域
    Spring管理的bean是根据scope来生成的,表示bean的作用域,共有7种。(默认单例模式)
  • singleton:单例,表示通过IoC容器获取的bean的唯一的。
  • prototype:原型,表示通过IoC容器获取的bean是不同的。
  • request:请求,表示一次HTTP请求内生效。
  • session:会话,表示一个用户会话内生效。
  • globalSession:在一个全局的HTTPSession中,容器会返回该Bean的同一个实例。仅在使用portlet上下文生效。
  • application:为每个ServletContext对象创建一个实例,仅在Web相关的ApplicationContext中生效。
  • websocket:为每个websocket对象创建一个实例。仅在Web相关的ApplicationContext中生效。
<!-- 单例模式 -->	
	<!-- singleton模式无论业务代码是否获取IoC容器中的bean时,Spring在加载spring.xml时就会创建bean -->
	<bean id="student" class="www.sheep.pojo.Student" scope="singleton">
    <!-- 原型模式 -->
    <!-- prototype模式当业务代码获取IoC容器中的bean时,Spring才去调用无参构造创建对应的bean -->
    <bean id="student" class="www.sheep.pojo.Student" scope="prototype">
  1. Spring的继承
    Spring的继承关注点在于具体对象,而不在于类,即不同的两个类的实例化对象可以完成继承,IoC容器会自动将父bean的数据类型和属性名与子类的数据类型与属性名进行匹配,同时可以在此基础上添加其他属性, 子 Bean 也可以覆盖从父 Bean 继承过来的配置
<!-- twoectends继承oneectends -->
<bean id="oneextends" class="www.sheep.pojo.oneExtends"></bean>
<bean id="twoectends" class="www.sheep.pojo.twoExtends" parent="oneextends"></bean>
  1. Spring的依赖
    与继承类似,依赖也是描述bean和bean之间的一种关系,配置依赖之后,被依赖的bean一定先创建,再创建依赖的bean,A依赖于B,先创建B,再创建A。
<!-- twoectends依赖于oneectends -->
<bean id="oneextends" class="www.sheep.pojo.oneExtends">
<bean id="twoectends" class="www.sheep.pojo.twoExtends" depends-on="twoectends">
  1. Bean的装载三种方式
    Bean的装配可以理解为依赖关系注入,Bean的装配方式即Bean依赖注入的方式。
  • XML装载:IoC容器通过了通过XML方式装配的两种方式:Set、构造。
  • 注解(Annotation)的装载
    spring.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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
 
    <context:annotation-config/>
 	<!--在使用注解功能之前要告诉IoC现在需要启用注解相关的
		功能,通过上下文级别的配置即可开启所有注解相关的功能-->
</beans>

三个实体类

public class Tools {
    public void minTool(){
        System.out.println("我是小扳手。。。。。");
    }
}
public class Utils {
    private int id;
    private String name;
    private String type;
}
/**
 * @Autowired注解:当bean中的id名与变量名相同时自动装载,当变量名不同时通过数据类型装载。
 * @Autowired
 *     @Qualifier(value = "utils"):当有多个相同类型的bena时可以使用该注解指定与id对应的名称。
 * */
public class Mankind {
    private String name;
    private String age;
    @Autowired
    private Tools tools;
    @Autowired
    @Qualifier(value = "utils")
    private Utils utils;  
}

在spring.xml中添加bean

<?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">
    <context:annotation-config/>
    <!-- Tools -->
    <bean id="tools" class="www.sheep.pojo.Tools"/>
    <!-- Utils -->
    <bean id="utils" class="www.sheep.pojo.Utils">
        <property name="id" value="1"/>
        <property name="name" value="鼠标"/>
        <property name="type" value="学习"/>
    </bean>
    <!-- Utils -->
    <bean id="utils2" class="www.sheep.pojo.Utils">
        <property name="id" value="1"/>
        <property name="name" value="鼠标"/>
        <property name="type" value="学习.com"/>
    </bean>
    <!-- Mankind -->
    <bean id="mankind" class="www.sheep.pojo.Mankind">
        <property name="name" value="梅西"/>
        <property name="age" value="21"/>
        <!-- 没有通过ref的方式,而是通过注解的方式进行装载 -->
    </bean>
</beans>

Handler

public class Test5 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-annotation.xml");
        Mankind mankind = (Mankind) applicationContext.getBean("mankind");
        mankind.getTools().minTool();
        System.out.println(mankind);
    }
}
  • 自动装载(Autowrie)
    IoC负责创建对象,DI负责完成对象注入,通过配置property标签的ref属性完成,同时Spring提供了另外一个更加简便的依赖注入方式:自动装载,不需要手动配置property,IoC容器会自动选择bean完成注入。
    自动装载有两种方式:
    byName:通过属性名自动装载
    创建实体类
public class Person {
    private long id;
    private String name;
    private Car car;
}
public class Car {
    private long id;
    private String name;
}

spring.xml

<bean id="car" class="www.sheep.pojo.Car">
        <property name="id" value="1"/>
        <property name="name" value="宝马"/>
    </bean>
    <bean id="person" class="www.sheep.pojo.Person" autowire="byName">
        <property name="id" value="1"/>
        <property name="name" value="编程"/>
    </bean>

Handler

public class Test4 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");
        Person person = (Person) applicationContext.getBean("person");
        System.out.println(person);
    }
}

byType:通过属性的数据类型自动装载

spring.xml

<!-- 当bean的id不同时根据数据类型装载 -->
	<bean id="car123" class="www.sheep.pojo.Car">
        <property name="id" value="1"/>
        <property name="name" value="宝马"/>
    </bean>
    <bean id="person" class="www.sheep.pojo.Person" autowire="byType">
        <property name="id" value="1"/>
        <property name="name" value="编程"/>
    </bean>
  • Bean的装载总结:
    byName:使用时根据Bean的id自动装载可以有多个Bean。
    byType:使用byType时只能由一个与之对应的Bean如果有多个会抛出异常。
    注解装载和自动装载:都只能对引用数据类型装载(ref)。

注解开发

springIoC中的注解

  • @Component:表示将该类描述为Bean,可以作用在任何层。
  • @Repository:与@Component注解一样将该类描述为Bean,作用于DAO层。
  • @Service:与@Component注解一样将该类描述为Bean,作用在(service)控制层。
  • @Controller:与@Component注解一样将该类描述为Bean,作用在控制层,比如SpringMVC的Controller。
  • @Autowired:用于对Bean的属性、set、构造方法进行标注,配置其他注解 完成Bean的注入,默认按类型注入。
  • @Resource:与@Autowirsd一样,不同在于@Resource默认按照名称注入,但@Resource有两个属性name、type。分别是按照名字、类型注入
  • @Qualifier:与@Autowired注解配置使用,会将默认的按Bean类型装配修改为按Bean的实例名称装配,实例名称由@Qualifier注解的参数指定。

使用Java的方式配置Spring

现在完全不使用Spring的.xml配置,全权交给Java来做!

JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能!

使用Java方式配置SPring的简单使用

  1. 创建实体类
package com.sheep.pojo;

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

//通过@Component注解将该类交给IoC容器接管,注册到容器中
@Component
public class User {
    private  String name;

    public String getName() {
        return name;
    }
    public User(){
        System.out.println("Program Sheep!!!");
    }
    @Value("Program Sheep") //属性注入值
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
  1. 创建管理类
package com.sheep.config;

import com.sheep.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

//这个也会被IoC容器接管,注册到容器中,应为他本来就是一个@Component。
//@Configuration 代表一个配置类,就相当于bean.xml,扫描com.sheep.pojo包下的所有bean
@Configuration
@ComponentScan("com.sheep.pojo")
public class SheepConfig {
    @Bean
    public User getUser(){
        return new User();//就是返回要注入到bean的对象
    }
    //注册一个bean,就相当于我之前写的一个bean标签
    //这个方法的名字,就相当于bean标签中的id属性
    //这个方法的返回值,就相当于bean标签中的class属性
}
  1. 创建测试类
import com.sheep.config.SheepConfig;
import com.sheep.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MySheepConfig {
    public static void main(String[] args) {
        //如果完全使用了配置类方式去做,就只能通过AnnotationConfigApplicationContext上下文来获取容器,通过配		  置运行时类加载
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SheepConfig.class);
        User user = (User) applicationContext.getBean("getUser");
        System.out.println(user);
    }
}

无侵入式javaapi工具 spring是一个无侵入式的框架_xml

全程并没有使用spring.xml方式配置Bean而是使用纯Java代码配置实现的。


AOP:

AOP简介

  1. 什么是AOP
  • AOP全称Aspect-Orientend Programming,面向切面编程。
  • AOP是对面向对象编程的一种补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程就是AOP。
  • 使用AOP,开发人员在编写业务逻辑时可以专心于核心业务,不会再过多的关注于其他业务逻辑的实现。
  • 目前流行的AOP框架有两个,SpringAOP、AspectJ。
  1. AOP的优点
  • 降低模块之间的耦合度
  • 使系统容易扩展
  • 更好的代码复用
  • 非业务代码更加集中,不分散,便于统一管理
  • 业务代码更加纯粹,没有其他代码的影响
  1. AOP术语
  • Aspect(切面):横切关注点被模块化的抽象对象。
  • Advice(通知):切面对象完成的工作
  • Target Object(目标对象):被通知的对象,即被横切的对象
  • Proxy(代理):切面、通知、目标混合之后的对象
  • Joinpoint(连接点):通知要插入业务代码的具体位置
  • Pointcut(切入点):AOP通过切点定位到连接点。
  • Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

JDK动态代理AOP

  1. 创建Maven工程,pom.xml添加
<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.5</version>
        </dependency>
    </dependencies>
  1. 创建一个计数器接口Cal,定义4个方法。
package com.sheep.utils;

public interface Cal {
    public int add(int num1,int num2);
    public int sud(int num1,int num2);
    public int mul(int num1,int num2);
    public int div(int num1,int num2);
}
  1. 创建接口的实现类Callmpl
package com.sheep.utils.impl;
import com.sheep.utils.Cal;

public class CalImpl implements Cal {
    public int add(int num1, int num2) {
        System.out.println("add方法的参数是["+num1+","+num2+"]");
        int result = num1+num2;
        System.out.println("add方法的结果是"+result);
        return result;
    }
    public int sub(int num1, int num2) {
        System.out.println("sud方法的参数是["+num1+","+num2+"]");
        int result = num1-num2;
        System.out.println("sub方法的结果是"+result);
        return result;
    }
    public int mul(int num1, int num2) {
        System.out.println("mul方法的参数是["+num1+","+num2+"]");
        int result = num1*num2;
        System.out.println("mul方法的结果是"+result);
        return result;
    }
    public int div(int num1, int num2) {
        System.out.println("div方法的参数是["+num1+","+num2+"]");
        int result = num1/num2;
        System.out.println("div方法的结果是"+result);
        return result;
    }
}
  1. 测试
package com.sheep.test;
import com.sheep.utils.Cal;
import com.sheep.utils.MyInvocationHandler;
import com.sheep.utils.impl.CalImpl;

public class Test1 {
    public static void main(String[] args) {
        Cal cal = new CalImpl();
        cal.add(1,1);
        cal.sub(4,2);
        cal.mul(2,3);
        cal.div(6,2);
    }
}

上述代码中,日志信息和业务逻辑的耦合性很高,不利于系统的维护,使用AOP可以进行优化,如何来实现AOP?使用动态代理的方式来实现。

  1. 使用动态代理
    创建代理类
package com.sheep.utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class MyInvocationHandler implements InvocationHandler {
    //接收委托对象
    private Object object = null;
    //返回代理对象
    public Object bind(Object object){
        this.object = object;
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName()+"方法的参数是"+ Arrays.toString(args));
        Object result = method.invoke(this.object, args);
        System.out.println(method.getName()+"结果是"+result);
        return result;
    }
}
  1. 测试
public class Test1 {
    public static void main(String[] args) {
        //原始
//        Cal cal = new CalImpl();
//        cal.add(1,1);
//        cal.sub(4,2);
//        cal.mul(2,3);
//        cal.div(6,2);
        //代理对象开发
        Cal cal = new CalImpl();  //创建委托对象
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();//创建代理对象
        Cal proxy = (Cal) myInvocationHandler.bind(cal);//将委托对象交给代理对象实现
        proxy.add(1,1);
        proxy.sub(2,1);
        proxy.mul(2,3);
        proxy.div(6,2);
    }
}

以上是通过动态代理实现AOP的过程,比较复杂,不好理解,Spring框架对AOP进行了封装,使用Spring框架可以用面向对象的思想来实现AOP。

控制台

add方法的参数是[1, 1]
add结果是2
sub方法的参数是[2, 1]
sub结果是1
mul方法的参数是[2, 3]
mul结果是6
div方法的参数是[6, 2]
div结果是3

Spring框架代理类的AOP实现三种方式

AOP是实现方式一

使用Spring的API接口(获取参数)

  1. 添加依赖
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>
  1. 定义一个接口
package com.sheep.service;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}
  1. 定义接口实现类
package com.sheep.service;

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加一个用户");
    }
    @Override
    public void delete() {
        System.out.println("删除一个用户");
    }
    @Override
    public void update() {
        System.out.println("更新一个用户");
    }
    @Override
    public void select() {
        System.out.println("查询一个用户");
    }
}
  1. 配置前置类
package com.sheep.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}
  1. 配置后置类
package com.sheep.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("执行了"+method.getName()+"方法。返回结果为:"+o);
    }
}
  1. spring.xml中配置AOP约束
<?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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
  1. 在spring.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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 注册Bean -->
    <bean id="userService" class="com.sheep.service.UserServiceImpl"/>
    <bean id="log" class="com.sheep.log.Log"/>
    <bean id="afterLog" class="com.sheep.log.AfterLog"/>
<!-- 方式一使用原生的SpringAPI接口 -->
    <!-- 配置AOP: 需要导入aop的约束-->
    <aop:config>
        <!-- 切入点 -->
        <aop:pointcut id="pointcut" expression="execution(* com.sheep.service.UserServiceImpl.*(..))"/>
        <!-- 执行环绕增强 -->
        <!-- 通知 -->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>
  1. 测试类
import com.sheep.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test01 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        //动态代理的必须是接口
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.add();
        userService.delete();
        userService.update();
        userService.select();
    }
}

控制台

com.sheep.service.UserServiceImpl的add被执行了
增加一个用户
执行了add方法。返回结果为:null
com.sheep.service.UserServiceImpl的delete被执行了
删除一个用户
执行了delete方法。返回结果为:null
com.sheep.service.UserServiceImpl的update被执行了
更新一个用户
执行了update方法。返回结果为:null
com.sheep.service.UserServiceImpl的select被执行了
查询一个用户
执行了select方法。返回结果为:null

在方式一中前置通知与后置通知被抽象为一个类,并在xml中配置该类。

AOP是实现方式二

自定义类来实现

  1. 将方法一的前后置类变成一个类前后置变成两个方法,并且不要AfterReturningAdvice接口
package com.sheep.diy;

public class DiyPointCut {
    public void before(){
        System.out.println("------方法执行前------");
    }
    public void after(){
        System.out.println("------方法执行后------");
    }
}
  1. 在spring.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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <bean id="userService" class="com.sheep.service.UserServiceImpl"/>
    <!--===================================================================================-->
    <!-- 方式一使用原生的SpringAPI接口 -->
    <!-- 注册Bean -->
    <bean id="log" class="com.sheep.log.Log"/>
    <bean id="afterLog" class="com.sheep.log.AfterLog"/>
    <!-- 配置AOP: 需要导入aop的约束-->
    <aop:config>
        <!-- 切入点 -->
        <aop:pointcut id="pointcut" expression="execution(* com.sheep.service.UserServiceImpl.*(..))"/>
        <!-- 执行环绕增强 -->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
    <!--===================================================================================-->
    <!-- 方式二:自定义类 -->
    <bean id="diy" class="com.sheep.diy.DiyPointCut"/>
    <aop:config>
        <!-- 自定义切面 -->
        <aop:aspect ref="diy">
            <!-- 切入点 -->
            <aop:pointcut id="point" expression="execution(* com.sheep.service.UserServiceImpl.*(..))"/>
            <!-- 通知点 -->
                <!-- 前置通知 -->
            <aop:before method="before" pointcut-ref="point"/>
                <!-- 后置通知 -->
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
    <!--===================================================================================-->
</beans>
  1. 测试类
import com.sheep.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test01 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        //动态代理的必须是接口
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.add();
        userService.delete();
        userService.update();
        userService.select();
    }
}

控制台

------方法执行前------
增加一个用户
------方法执行后------
------方法执行前------
删除一个用户
------方法执行后------
------方法执行前------
更新一个用户
------方法执行后------
------方法执行前------
查询一个用户
------方法执行后------

AspectJ 注解实现AOP

案例一:

  1. 自动生成动态
<!-- 让Spring容器结合切面类和目标对象自动生成动态代理对象-->
    <aop:aspectj-autoproxy/>
  1. 新建切面类使用注解配置(延续AOP三种实现方式的类)
package com.sheep.diy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect //表示这个类是一个切面
@Component //表示将该类交给IoC容器创建(注意在spring.xml中要配置扫描该该类)
public class Annotation {
    //@Before:表示前置通知的切入点
    @Before("execution(* com.sheep.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("-=-=-=方法执行前-=-=-=");
    }
    // @After:表示后置通知的切入点
    @After("execution(* com.sheep.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("-=-=-=方法执行后-=-=-=");
    }
    //@Around表示环绕通知切入点
    @Around("execution(* com.sheep.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint pj) throws Throwable {
        System.out.println("环绕前");
        Object proceed = pj.proceed();		//执行前后置方法
        System.out.println("环绕后");
    }
}
  1. 在spring.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:aop="http://www.springframework.org/schema/aop"
       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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 注册Bean -->
    <bean id="userService" class="com.sheep.service.UserServiceImpl"/>
    <bean id="log" class="com.sheep.log.Log"/>
    <bean id="afterLog" class="com.sheep.log.AfterLog"/>

<!--    <!– 方式一使用原生的SpringAPI接口 –>-->
<!--    <!– 配置AOP: 需要导入aop的约束–>-->
<!--    <aop:config>-->
<!--        <!– 切入点 –>-->
<!--        <aop:pointcut id="pointcut" expression="execution(* com.sheep.service.UserServiceImpl.*(..))"/>-->
<!--        <!– 执行环绕增强 –>-->
<!--        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>-->
<!--        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>-->
<!--    </aop:config>-->

<!--    <!– 方式二:自定义类 –>-->
<!--    <bean id="diy" class="com.sheep.diy.DiyPointCut"/>-->
<!--    <aop:config>-->
<!--        <!– 自定义切面 –>-->
<!--        <aop:aspect ref="diy">-->
<!--            <!– 切入点 –>-->
<!--            <aop:pointcut id="point" expression="execution(* com.sheep.service.UserServiceImpl.*(..))"/>-->
<!--            <!– 通知点 –>-->
<!--                <!– 前置通知 –>-->
<!--            <aop:before method="before" pointcut-ref="point"/>-->
<!--                <!– 后置通知 –>-->
<!--            <aop:after method="after" pointcut-ref="point"/>-->
<!--        </aop:aspect>-->
<!--    </aop:config>-->

    <!-- 方式三 -->
    <!-- 将com.sheep.diy包下的所有类交给IOC容器创建 -->
    <context:component-scan base-package="com.sheep.diy*"/>
<!--    <bean class="com.sheep.diy.Annotation"/>-->
    <!-- 让Spring容器结合切面类和目标对象自动生成动态代理对象-->
    <aop:aspectj-autoproxy/>
</beans>
  1. 测试类
import com.sheep.log.Log;
import com.sheep.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test01 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        //动态代理的必须是接口
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.add();
        userService.delete();
        userService.update();
        userService.select();
    }
}

控制台

环绕前
-=-=-=方法执行前-=-=-=
增加一个用户
-=-=-=方法执行后-=-=-=
环绕后
环绕前
-=-=-=方法执行前-=-=-=
删除一个用户
-=-=-=方法执行后-=-=-=
环绕后

可以看出该环绕在方法的最前和最后

案例二:(获取参数)

  1. 创建接口
package com.sheep.utils;

public interface Cal {
    public int add(int num1,int num2);
    public int sub(int num1,int num2);
    public int mul(int num1,int num2);
    public int div(int num1,int num2);
}
  1. 创建接口实习类(交给IoC容器)
package com.sheep.utils.impl;
import com.sheep.utils.Cal;
import org.springframework.stereotype.Component;

@Component	//将该类交给IoC容器
public class CalImpl implements Cal {
    public int add(int num1, int num2) {
        //System.out.println("add方法的参数是["+num1+","+num2+"]");
        int result = num1+num2;
        //System.out.println("add方法的结果是"+result);
        return result;
    }
    public int sub(int num1, int num2) {
        int result = num1-num2;
        return result;
    }
    public int mul(int num1, int num2) {
        int result = num1*num2;
        return result;
    }
    public int div(int num1, int num2) {
        int result = num1/num2;
        return result;
    }
}
  1. 使用注解创建切面类
package com.sheep.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect //声明该类为切面类
@Component //将来类交给IoC容器
public class LoggerAspect {

    //前置通知
    @Before("execution(public int com.sheep.utils.impl.CalImpl.*(..))")
    public void before(JoinPoint joinPoint){
        //获取方法名
        String name = joinPoint.getSignature().getName();
        //获取参数
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println(name+"方法的参数是"+args);
    }
    //后置通知
    @After("execution(* com.sheep.utils.impl.CalImpl.*(..)))")
    public void after(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"执行完毕");
    }
    //返回值通知
    @AfterReturning(value = "execution(* com.sheep.utils.impl.CalImpl.*(..)))",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法的结果是"+result);
    }
    //异常通知
    @AfterThrowing(value = "execution(* com.sheep.utils.impl.CalImpl.*(..)))",throwing = "exception")
    public void fterThrowing(JoinPoint joinPoint,Exception exception){
        String name = joinPoint.getSignature().getName();
        System.out.println(name+"方法抛出了"+exception+"异常");
    }
}
  1. 在spring.xml中配置注解扫描、和Spring容器结合切面类和目标对象自动生成动态代理对象
<?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:aop="http://www.springframework.org/schema/aop"
       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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 自动扫描 -->
    <context:component-scan base-package="com.sheep.aop*"/>
    <context:component-scan base-package="com.sheep.utils*"/>
    <!-- 让Spring容器结合切面类和目标对象自动生成动态代理对象-->
    <aop:aspectj-autoproxy/>
</beans>

控制台

add方法的参数是[1, 1]
add方法的结果是2
add执行完毕
sub方法的参数是[2, 1]
sub方法的结果是1
sub执行完毕
  1. 总结:注解开发中常用的注解、通知的执行先后顺序
    @Aspect :表示这个类是一个切面
    @Component :表示将该类交给IoC容器创建(注意在spring.xml中要配置扫描该该类)
    @Before:表示前置通知
    @After:表示后置通知
    @AfterReturning:表示返回值通知
    @AfterThrowing:异常通知
    @Around:表示环绕通知
    前环绕通知 >> 前置通知 >> 返回值通知 >> 后置通知 >> 后环绕通知。

还历史以真诚,还生命以过程。 ——余秋雨