Spring基础一(IOC、DI、容器、Bean、AOP)

Spirng

  • 1.Spring IOC(控制反转)
  • 2.DI(依赖注入)
    • 2.1 set方法注入
    • 2.2 构造器注入
  • 3.ApplicationContext(Spring容器)
    • 3.1 初始化ApplicationContext
    • 3.2 注解开发(使用注解替代applicationContext.xml)
      • 3.2.1 配置类中引入外部文件
    • 3.3 ApplicationContext总结
  • 4.Bean
    • 4.1 通过ApplicationContext获取Bean的三种方式
    • 4.2 Bean的作用域
      • 4.2.1 配置bean的作用域
    • 4.3 Bean的生命周期
      • 4.3.1 控制bean的初始化和销毁
      • 4.3.2 Bean的销毁时机
    • 4.4 实例化Bean的三种方式
    • 4.5 后处理Bean(BeanPostProcessor接口)
    • 4.6 Bean属性的依赖注入(xml配置)
  • 5.IOC容器配置Bean
    • 5.1 xml方式
    • 5.2 注解方式
      • 5.2.1 Bean属性的依赖注入(注解配置)
        • 5.2.1.1 简单数据类型依赖注入
        • 5.2.1.2 引用数据类型依赖注入
          • 5.2.1.2.1 @Value + SpEL
          • 5.2.1.2.2 @Autowired + @Qualifier
          • 5.2.1.2.3 @Resource
  • 6.Spring的web集成
    • 6.1 让Web容器来管理Spring容器的创建和销毁
    • 6.2 获取Spring容器(ApplicationContext)
  • 7.AOP(面向切面编程)
    • 7.1 SpringAOP 编程的原理
      • 7.1.1 JDK动态代理
      • 7.1.2 Cglib动态代理
      • 7.1.3 代理总结
    • 7.2 Spring AOP编程的几种方式
      • 7.2.1 传统AOP编程
        • 7.2.1.1 Advice的5种类型
        • 7.2.1.2 使用传统AOP编程实现环绕通知
      • 7.2.2 AspectJ实现AOP编程入门
        • 7.2.2.1 AspectJ提供不同的通知类型
        • 7.2.2.2 使用AspectJ实现AOP编程
        • 7.2.2.3 AspectJ方式与传统AOP的不同
  • 8.使用AspectJ实现各类通知
    • 8.1 Before前置通知
      • 8.1.1 xml方式
      • 8.1.2 注解方式
      • 8.1.3 应用场景
    • 8.2 AfterReturing后置通知
      • 8.2.1 xml方式
      • 8.2.2 注解方式
      • 8.2.3 应用场景
    • 8.3 Around 环绕通知
      • 8.3.1 xml方式
      • 8.3.2 注解方式
      • 8.3.3 应用场景
    • 8.4 AfterThrowing 抛出通知
      • 8.4.1 xml方式
      • 8.4.2 注解方式
      • 8.4.3 应用场景
    • 8.5 After 最终通知
      • 8.5.1 xml方式
      • 8.5.2 注解方式
    • 8.6 各种Advice方法可接收的参数和返回值小结
    • 8.7 如果目标对象有接口,能否只对实现类代理,而不对接口进行代理呢

1.Spring IOC(控制反转)

Inverse of Control(控制反转)

一、作用
将原来在程序中手动创建的对象,交给Spring工厂来创建管理。

二、原理
Spirng工厂通过反射的原理, 根据具体类型的类字符串生产对象的实例,
IoC底层实现:工厂(设计模式) + 反射(机制) + 配置文件(xml)

三、流程

  • 1.导入Spring依赖
<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-contextartifactId>
  <version>5.2.10.RELEASEversion>
dependency>
  • 2.定义被管理的类(接口)
// 接口
public interface BookService {
    public void save();
}

// 实现类
public class BookServiceImpl implements BookService{
    public void save() {
        System.out.println("保存书本信息");
    }
}
  • 3.applicationContext.xml

<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="bookService" class="com.itheima.ioc.BookServiceImpl">bean>

beans>

可参考:Spring_IOC


2.DI(依赖注入)

Dependency Injectio(依赖注入)

一、作用
简单的说,在运行期间, Spring根据配置文件, 通过目标bean提供的setter方法, 可以将另外一个bean的引用动态的注入到目标bean中。

二、原理
通过反射实现注入的,反射允许程序在运行的时候动态的生成对象

三、DI注入的两种方式

  • set方法注入
  • 构造器注入

2.1 set方法注入

一、为属性提供set方法

public class BookServiceImpl implements BookService {

    private BookDao bookDao;

    /**
     * 提供依赖对象对应的setter方法
     */
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    public void save() {
        bookDao.save();
    }
}

二、aapplicationContext.xml

<bean id="bookDao" class="com.itheima.ioc.BookDaoImpl"/>


<bean id="bookService" class="com.itheima.ioc.BookServiceImpl">
	
	
	
	
    <property name="bookDao" ref="bookDao" />
bean>

2.2 构造器注入

一、为属性提供set方法

public class BookServiceImpl implements BookService {
	
    private BookDao bookDao;

    /**
     * 提供依赖对象对应的setter方法
     */
    public BookServiceImpl (BookDao bookDao) {
        this.bookDao = bookDao;
    }

    public void save() {
        bookDao.save();
    }
}

二、aapplicationContext.xml

<bean id="bookDao" class="com.itheima.ioc.BookDaoImpl"/>


<bean id="bookService" class="com.itheima.ioc.BookServiceImpl">
	
     <constructor-arg ref="bookDao" index="0">constructor-arg>
bean>

IOC和DI小结

  • IOC:控制反转,将对象创建管理的权利交给spring容器,获取对象通过spring工厂创建
  • DI:在spring容器中创建管理多个对象,通过 property标签将对象注入到需要依赖的对象中

3.ApplicationContext(Spring容器)

ApplicationContext直译为应用上下文,是用来加载Spring框架配置文件,来构建Spring的工厂对象,它也称之为Spring容器的上下文对象,也称之为Spring的容器。

ApplicationContext 只是BeanFactory(Bean工厂,Bean就是一个java对象)一个子接口:
Spring基础一(IOC、DI、容器、Bean、AOP)_第1张图片

3.1 初始化ApplicationContext

通过加载Spring核心配置文件(applicationContext.xml)来初始化ApplicationContext(Spring容器)

有以下几种加载方式:

  1. 类路径加载配置文件
  2. 文件路径加载配置文件
  3. 加载多个配置文件
// 1.类路径加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

// 2.文件路径加载配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");

// 3.加载多个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");

3.2 注解开发(使用注解替代applicationContext.xml)

传统的开发, 是要手动编写Spring核心配置文件(applicationContext.xml), 而Spring 3.0开启了纯注解的开发模式, 使用java类替代配置文件, 开启了Spring快速开发的赛道

一、Java类替代Spring核心配置文件
Spring基础一(IOC、DI、容器、Bean、AOP)_第2张图片

  • @Configuration 注解用于设定当前类为配置类
  • @ComponentScan 用于批量注册bean, 类上有@Repository、@Service、@Controller、@Component这几个注解中的任何一个,这个类就会被作为bean注册到spring容器中

二、动手实现
1.新增Book, 添加@Component注解

package cn.itcast.pojo;
import org.springframework.stereotype.Component;

@Component
public class Book {

    public void say() {
        System.out.println("这是一本书");
    }
}

2.创建SpringConfig(Spring配置类)替代Spring核心配置文件(applicationContext.xml)

@Configuration
@ComponentScan("cn.itcast")
public class SpringConfig {
}

3.初始化Spring容器, 并从容器中获取对应的bean, 注意此处使用AnnotationConfigApplicationContext

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    Book book = (Book)ctx.getBean("book");
    book.say();
}

注意:使用注解开发初始化Spring容器时, 使用的是AnnotationConfigApplicationContext, 它是ApplicationContext的子类, 可以通过Ctrl+Alt+Shift+U查看继承关系图
Spring基础一(IOC、DI、容器、Bean、AOP)_第3张图片


3.2.1 配置类中引入外部文件

  1. 通过@PropertySource注解引入外部文件
  2. 通过@Value(“${}”)注入配置文件里面的信息

一、创建外部文件:jdbc.properties

name=zhangsan

二、通过@PropertySource注解引入配置类
Spring基础一(IOC、DI、容器、Bean、AOP)_第4张图片

三.此处可以在Spring容器管理的所有对象中, 通过@Value(“${}”)来注入配置文件里面的信息
Spring基础一(IOC、DI、容器、Bean、AOP)_第5张图片


3.3 ApplicationContext总结

  • BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载
  • ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载
  • ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
  • ApplicationContext接口常用初始化类
    • ClassPathXmlApplicationContext
    • FileSystemXmlApplicationContext

4.Bean

4.1 通过ApplicationContext获取Bean的三种方式

  1. 使用bean名称获取
  2. 使用bean类型获取
  3. 使用bean名称获取并指定类型

一、使用bean名称获取

BookDao bookDao = (BookDao) ctx.getBean("bookDao");

注意点:通过@Component注解扫描到spring容器中的bean会默认将小写开头的类名作为bean的name,供其他地方引用.
例如:BookDao的name则为bookDao

二、 使用bean类型获取

BookDao bookDao = ctx.getBean(BookDao.class);

使用这种方式,如果配置了多个类型的话,会抛出异常
Spring基础一(IOC、DI、容器、Bean、AOP)_第6张图片
Spring基础一(IOC、DI、容器、Bean、AOP)_第7张图片

三、 使用bean名称获取并指定类型

BookDao bookDao = ctx.getBean("bookDao", BookDao.class);

4.2 Bean的作用域

Spring基础一(IOC、DI、容器、Bean、AOP)_第8张图片

单例是默认值,如果需要单例对象,则不需要配置scope。

4.2.1 配置bean的作用域

一、xml方式

<bean id="bookDao" class="com.itheima.ioc.BookDaoImpl" scope="prototype"/> 

二、注解方式

//测试生命周期过程中的初始化和销毁bean
@Component("lifeCycleBean")
//@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope("prototype")//默认是单例(singleton),更改为多例(prototype)
public class LifeCycleBean {

}

开启了prototype后,每次getBean 返回一个新的实例
Spring基础一(IOC、DI、容器、Bean、AOP)_第9张图片
打印结果
在这里插入图片描述


4.3 Bean的生命周期

bean的生命周期有4个阶段:实例化 -> 属性赋值 -> 初始化 -> 销毁
我们可以通过下图来了解这4个阶段分别在什么时候

这里我们说的 Spring Bean 的生命周期主要指的是singleton bean, 对于prototype的bean, Spring容器在创建好交给使用者之后则不会再管理后续的生命周期。
Spring基础一(IOC、DI、容器、Bean、AOP)_第10张图片
我们如果想让bean在这四个阶段执行指定的代码, 该如何做呢?

  • 实例化:我们可以在构造函数中(一般是无参构造)添加指定代码
  • 属性填充:在填充属性时(set方法), 添加指定代码
  • 初始化 ???
  • 销毁 ???

这一章节就是来告诉你, 如果在bean初始化和销毁时,去执行指定代码


4.3.1 控制bean的初始化和销毁

  1. xml配置
  2. 实现接口方法
  3. 注解方式

一、xml配置
1.提供初始化方法、销毁方法

//测试生命周期过程中的初始化和销毁bean
public class LifeCycleBean {

	//定义构造方法
	public LifeCycleBean() {
		System.out.println("LifeCycleBean构造器调用了");		
	}
	
	//初始化后自动调用方法:方法名随意,但也不能太随便,一会要配置
	public void init(){
		System.out.println("LifeCycleBean-init初始化时调用");
	}
	
	//bean销毁时调用的方法
	public void destroy(){
		System.out.println("LifeCycleBean-destroy销毁时调用");
	}

}

2.applicationContext.xml配置

  • init-method: 设置bean初始化生命周期回调函数
  • destroy-method: 设置bean销毁生命周期回调函数,仅适用于单例对象

<bean id="lifeCycleBean" class="cn.itcast.spring.LifeCycleBean" init-method="init" destroy-method="destroy" />

3.测试

@Test
public void test(){
	//先获取spring的容器,工厂,上下文
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
	//对于单例此时已经被初始化
	//获取bean
	LifeCycleBean lifeCycleBean=(LifeCycleBean) applicationContext.getBean("lifeCycleBean");
	System.out.println(lifeCycleBean);
	//为什么没有销毁方法调用。
	//原因是:使用debug模式jvm直接就关了,spring容器还没有来得及销毁对象。
	//解决:手动关闭销毁spring容器,自动销毁单例的对象
	((ClassPathXmlApplicationContext)applicationContext).close();
}

销毁方法的执行必须满足两个条件:

  • 单例(singleton)的bean才会可以手动销毁。
  • 必须手动关闭容器(调用close的方法)时,才会执行手动销毁的方法。

二、实现接口方法
实现InitializingBean, DisposableBean接口

public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    private BookDao bookDao;
    public void setBookDao(BookDao bookDao) {
        System.out.println("set .....");
        this.bookDao = bookDao;
    }
    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
    public void destroy() throws Exception {
        System.out.println("service destroy");
    }
    public void afterPropertiesSet() throws Exception {
        System.out.println("service init");
    }
}

三、注解方式

  • 使用 @PostConstruct 注解, 标明初始化方法 —相当于 init-method 指定初始化方法
  • 使用 @PreDestroy 注解, 标明销毁方法 ----相当于 destroy-method 指定对象销毁方法
//测试生命周期过程中的初始化和销毁bean
@Component("lifeCycleBean")
public class LifeCycleBean {
	
	public LifeCycleBean() {
		System.out.println("LifeCycleBean构造器调用了");
	}
	
	//初始化后自动调用方法:方法名随意,但也不能太随便,一会要配置
	@PostConstruct//初始化的方法
	public void init(){
		System.out.println("LifeCycleBean-init初始化时调用");
	}
	
	//bean销毁时调用的方法
	@PreDestroy
	public void destroy(){
		System.out.println("LifeCycleBean-destroy销毁时调用");
	}

}

4.3.2 Bean的销毁时机

容器关闭前触发bean的销毁

关闭容器方式:

  • 手工关闭容器
    • ConfigurableApplicationContext接口close()操作
  • 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
    • ConfigurableApplicationContext接口registerShutdownHook()操作
public class AppForLifeCycle {
    public static void main( String[] args ) {
        //此处需要使用实现类类型,接口类型没有close方法
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
        //注册关闭钩子函数,在虚拟机退出之前回调此函数,关闭容器
        ctx.registerShutdownHook();
        //关闭容器
        //ctx.close();
    }
}

4.4 实例化Bean的三种方式

  1. 构造方法方式(最常用)
  2. 静态工厂方式
  3. 实例工厂方式

一、构造方法方式
默认使用无参构造方法, 无参构造方法如果不存在,将抛出异常BeanCreationException

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

二、 静态工厂方式

1.创建工厂类, 提供静态方法

//静态工厂创建对象
public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        System.out.println("factory setup....");
        return new OrderDaoImpl();
    }
}

2.applicationContext.xml配置


<bean id="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>

Spring基础一(IOC、DI、容器、Bean、AOP)_第11张图片


三、实例工厂方式

1.创建工厂类, 提供实例方法

//实例工厂创建对象
public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}

2.applicationContext.xml配置

<!--方式三:使用实例工厂实例化bean-->
<bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

Spring基础一(IOC、DI、容器、Bean、AOP)_第12张图片

总结:

  • 静态工厂方式,调用静态方法实例化bean, 所以不需要提前实例化工厂类
  • 实例工厂方式,调用非静态方法实例化bean, 所以需要提前实例化工厂类

4.5 后处理Bean(BeanPostProcessor接口)

作用:在Bean初始化的前后,对Bean对象进行增强; 它既可以增强一个指定的Bean,也可以增强所有的Bean
底层很多功能(如AOP等)的实现都是基于它的,Spring可以在容器中直接识别调用。

一、动手实践

  • 1.创建后处理类, 需要实现BeanPostProcessor(后置处理器)
public class MyBeanPostProcessor implements BeanPostProcessor{

	/**
	 * 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务
	 * 注意:方法返回值不能为null
	 * 如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到bena实例对象
	 * 因为后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中
	 */
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("初始化 before--实例化的bean对象:"+bean+"\t"+beanName);
		// 可以根据beanName不同执行不同的处理操作
		return bean;
	}

	/**
	 * 实例化、依赖注入、初始化完毕时执行 
	 * 注意:方法返回值不能为null
	 * 如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到bena实例对象
	 * 因为后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中
	 */
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("初始化 after...实例化的bean对象:"+bean+"\t"+beanName);
		// 可以根据beanName不同执行不同的处理操作
		return bean;
	}
}

如果要增强一个指定的bean,则只要在方法中根据bean的名称进行处理

  • 2.注册后处理器
    spring在初始化MyBeanPostProcessor的时候,判断是否实现了BeanPostProcessor,如果实现了,就采用动态代理的方式,对所有的bean对象增强

<bean class="com.dpb.processor.MyBeanPostProcessor">bean>

二、后处理bean的执行时机

方法 说明
postProcessBeforeInitialization 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务
postProcessAfterInitialization 实例化、依赖注入、初始化完毕时执行

Spring基础一(IOC、DI、容器、Bean、AOP)_第13张图片

三、使用场景:
1、处理自定义注解。bean可以添加我们自定义的注解,自定义的注解处理方式在该类中实现,如通过注解识别一组或几组bean,在后续的业务处理中根据组bean进行逻辑。
2、打印日志,将每个bean的初始化情况打印出来;打印初始化时间等。
BeanPostProcessor


4.6 Bean属性的依赖注入(xml配置)

  1. 构造方法注入
  2. set方法注入

一、构造器参数注入 constructor-arg

//目标,构造器参数注入,new car直接将参数的值直接赋值
public class Car {
	private Integer id;
	private String name;
	private Double price;
	//有参构造
	public Car(Integer id, String name, Double price) {
		this.id = id;
		this.name = name;
		this.price = price;
	}
	
	@Override
	public String toString() {
		return "Car [id=" + id + ", name=" + name + ", price=" + price + "]";
	}
}

注入简单类型的值


<bean id="car" class="cn.itcast.spring.Car">
	
	
	<constructor-arg index="0" name="id" value="1"/>
	
	<constructor-arg name="name" >
		<value>宝马2代value>
	constructor-arg>
	<constructor-arg type="java.lang.Double" value="99999d"/>
	
bean>

注入引用类型的值(参数第一组不变, 用于定位属性, 参数第二组改为ref来指向对象)
Spring基础一(IOC、DI、容器、Bean、AOP)_第14张图片

二、setter方法属性注入 property

/**
 * 定义人类
 * setter方法属性注入
 * 相当于new Person();
 */
public class Person {
	private Integer id;
	private String name;
	private Car car;
	//必须提供setter属性方法
	public void setId(Integer id) {
		this.id = id;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void setCar(Car car) {
		this.car = car;
	}
	
	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", car=" + car + "]";
	}

}
  • 简单类型使value
  • 引用类型使用ref

<bean id="person" class="cn.itcast.spring.Person">
	
	<property name="id" value="1001"/>
	<property name="name" value="Tom"/>
	
	<property name="car">
		<ref bean="car"/>
	property>
bean>

三、p名称空间的使用
Spring2.5版本开始引入了一个新的p名称空间。简单的说,它的作用是为了简化setter方法属性依赖注入配置的,它不是真正的名称空间。

xmlns:p="http://www.springframework.org/schema/p"


<bean id="person" class="cn.itcast.spring.Person" p:id="1002" p:name="关羽" p:car-ref="car"/>

四、spEL表达式的使用

  • EL:操作servlet相关的一些对象和相关的值
  • OGNL:主要操作struts2值栈
  • spEL:操作bean相关的

语法: #{…} , 引用另一个Bean 、属性、 方法 , 运算
SpEL表达式的使用功能比较多,Bean操作相关的通常有:

  • #{beanid} 引用Bean(具体对象)
  • #{beanId.属性} 引用Bean的属性
  • #{beanId.方法(参数)} 调用Bean的方法


<bean id="person3" class="cn.itcast.spring.e_xmlpropertydi.Person" 
p:id="#{1+1}" p:name="#{person.name.toUpperCase()}" p:car="#{car}">bean>

5.IOC容器配置Bean

5.1 xml方式

  1. 构造方法方式(最常用)
  2. 静态工厂方式
  3. 实例工厂方式

一、构造方法方式
spring在创建bean的时候自动调用无参构造器来实例化,相当于new BookServiceImpl()

<bean id="bookService" class="com.itheima.ioc.BookServiceImpl" />

前提:如果类中有其他构造函数,需要提供显式的无参构造函数


二、静态工厂方式
1.创建工厂, 提供静态方法

//静态工厂
public class BeanFactory {
	//静态方法,用来返回对象的实例
	public static Bean getBean(){
		//在做实例化的时候,可以做其他的事情,即可以在这里写初始化其他对象的代码
		//Connection conn....
		return new Bean();
	}
}

2.直接调用静态方法, 不需要实例化工厂类

<bean id="bean" class="com.itheima.ioc.BeanFactory" factory-method="getBean"/>

三、实例工厂方法
1.创建工厂,提供非静态方法

实例工厂(生产实例的方法不是静态)
public class BeanFactory {
	public Bean getBean(){
		return new Bean();
	}
}

2.需要提前实例化工厂类


<bean id="beanFactory" class="com.itheima.ioc.BeanFactory"/>

<bean id="bean" factory-bean="beanFactory" factory-method="getBean"/>

总结:

  • 静态工厂方式,调用静态方法实例化bean, 所以不需要提前实例化工厂类
  • 实例工厂方式,调用非静态方法实例化bean, 所以需要提前实例化工厂类

四、FactoryBean方式(了解)
1.定义UserDaoFactoryBean实现FactoryBean

UserDaoFactoryBean中实例化什么类型的对象泛型就是该类型。

//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

2.applicationContext.xml配置


<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

使用之前的AppForInstanceUser测试类去运行看结果就行了。注意配置文件中id="userDao"是否重复。


5.2 注解方式

  • 开启以及注解扫描
  • 在类上添加@Component注解

前期准备:导入Spring核心包

<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-contextartifactId>
  <version>5.2.10.RELEASEversion>
dependency>

第一步:配置注解开启以及注解扫描
引入context 名称空间,使标签生效, 开启注解扫描component-scan
Spring基础一(IOC、DI、容器、Bean、AOP)_第15张图片

第二步:在类上添加@Component

@Component(value="customerService")
public class CustomerService {
	//保存业务方法
	public void save(){
		System.out.println("CustomerService业务层被调用了。。。");
	}
}

在配置包扫描的时候,spring会自动开启注解功能,所以,注解开启功能可以不配置。
即去掉: ,因为 具有 作用 !


<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:component-scan base-package="cn.itcast.spring"/>
	
beans>

@Componen的子注解

  • @Controller用于标注控制层组件
  • @Service用于标注业务层组件
  • @Repository用于标注数据访问组件

5.2.1 Bean属性的依赖注入(注解配置)

之前我们学习了bean属性的依赖注入(xml配置), 使用xml配置时, 我们可以通过构造方法、set方法来注入值,接下来我们学一下怎么使用配置来完成bean属性的依赖注入

5.2.1.1 简单数据类型依赖注入

简单数据类型, 我们可以使用@Value来注入值
Spring基础一(IOC、DI、容器、Bean、AOP)_第16张图片


5.2.1.2 引用数据类型依赖注入

5.2.1.2.1 @Value + SpEL

在属性声明上面注入,底层自动还是生成set方法, 其中customerDao表示节点id的属性值

//@Component(value="customer")
@Service(value="customer")
public class CustomerService {
	//在属性声明上面注入,底层自动还是生成setCustomerDao()
	//第一种: 使用@Value 结合SpEL  ---- spring3.0 后用
    //其中customerDao表示节点id的属性值
	@Value("#{customerDao}")
	private CustomerDao customerDao;
	
	//保存业务方法
	public void save(){
		System.out.println("CustomerService业务层被调用了。。。");
		System.out.println("name:"+name);
		customerDao.save();
	}
}

5.2.1.2.2 @Autowired + @Qualifier

一、单独使用@Autowired
单独使用@Autowired ,表示按照类型注入,会到spring容器中查找CustomerDao的类型,对应,class的属性值,如果找到,可以匹配。

//使用spring的@Autowired
@Autowired//默认按照类型注入
private CustomerDao customerDao;

按照类型注入会遇到的问题:
如果IOC容器中同类的Bean有多个,那么默认按照变量名和Bean的名称匹配,建议使用@Qualifier注解指定要装配的bean名称
Spring基础一(IOC、DI、容器、Bean、AOP)_第17张图片

这时候会发现注入的地方报错 不清楚要注入哪个bean 错误如下图
Spring基础一(IOC、DI、容器、Bean、AOP)_第18张图片

这时候我们就可以使用@Qualifier来配合@Autowire进行注入了
首先在实例化bean的时候指定名字 注入时使用对应的名字注入 如下图
Spring基础一(IOC、DI、容器、Bean、AOP)_第19张图片

指定名称为user1的bean
Spring基础一(IOC、DI、容器、Bean、AOP)_第20张图片

二、使用@Autowired + @Qualifier
使用@Autowired + @Qualifier表示按照名称注入,回到spring容器中查找customerDao的名称,对应,id的属性值,如果找到,可以匹配。

@Autowired//默认按照类型注入的
@Qualifier("customerDao") //必须配合@Autowired注解使用,根据名字注入
private CustomerDao customerDao;

5.2.1.2.3 @Resource

使用@Resource注解,表示先按照名称注入,会到spring容器中查找customerDao的名称,对应,id的属性值,如果找到,可以匹配。

如果没有找到,则会按照类型注入,会到spring容器中查找CustomerDao的类型,对应,class的属性值,如果找到,可以匹配,如果没有找到会抛出异常。

@Resource//默认先按照名称进行匹配,再按照类型进行匹配
private CustomerDao customerDao;

如果@Resource注解上添加name名称,只能按照customerDao名称进行匹配

 //第三种: JSR-250标准(jdk) 提供@Resource 
@Resource(name="customerDao")//只能按照customerDao名称进行匹配
private CustomerDao customerDao;

四、@Inject
需要先导入 javax.inject 的 jar ,在课前资料中查找。
Spring基础一(IOC、DI、容器、Bean、AOP)_第21张图片
使用@Inject注解,则按照类型注入

//第四种: JSR-330标准(jdk) 提供 @Inject ,配合@name注解
@Inject//默认按照类型注入
private CustomerDao customerDao;

使用@inject和@Named注解,则按照名称注入

//第四种: JSR-330标准(jdk) 提供 @Inject ,配合@name注解
@Inject//默认按照类型注入
@Named("customerDao")//按照名字注入,必须配合@Inject使用
private CustomerDao customerDao;

6.Spring的web集成

@Service
public class HelloService {
    public void sayHello(){
        System.out.println("嘿,传智播客。。。。");
    }
}
@WebServlet("hello")
public class HelloServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //传统方式:
        //new service
        //spring方式:只要看到new,你就想到spring容器中的
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        HelloService helloService=(HelloService)applicationContext.getBean("helloService");
        helloService.sayHello();
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doGet(request, response);
    }
}

启动项目,并访问
Spring基础一(IOC、DI、容器、Bean、AOP)_第22张图片
一、手动创建Spring容器的缺点
直接new ClassPathXmlApplicationContext()有什么缺点?
如果每次访问都要初始化Spring容器,消耗了资源,降低了性能

二、解决思路:保证容器对象只有一个。
解决方案:将Spring容器绑定到Web Servlet容器上,让Web容器来管理Spring容器的创建和销毁。


6.1 让Web容器来管理Spring容器的创建和销毁

ServletContext在Web服务运行过程中是唯一.它是在WEB服务器启动时,会为每一个WEB应用程序(webapps下的每个目录就是一个应用程序)创建一块共享的存储区域,是所有的Servlet和JSP都可以共享同一个区域。

具体操作:
ServletContext在Web服务运行过程中是唯一的, 其初始化的时候,会自动执行ServletContextListener 监听器

编写一个ServletContextListener监听器,在监听ServletContext到创建的时候,创建Spring容器,并将其放到ServletContext的属性中保存(setAttribute(Spring容器名字,Spring容器对象) )。

我们无需手动创建该监听器,因为Spring提供了一个叫ContextLoaderListener的监听器,它位于spring-web-4.2.4.RELEASE.jar中。

一、导入依赖包

<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-webartifactId>
  <version>5.2.10.RELEASEversion>
dependency>

二、配置监听器

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>

ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。
Spring基础一(IOC、DI、容器、Bean、AOP)_第23张图片

ContextLoaderListener默认加载的是WEB-INF/applicationContext.xml,如果applicationContext.xml不在这个文件夹下,就会报
在这里插入图片描述

解决方案:需要在web.xml中配置,加载spring容器applicationContext.xml文件的路径


<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>

<context-param>
	<param-name>contextConfigLocationparam-name>
	
	<param-value>classpath:applicationContext.xmlparam-value>
context-param>

ContextLoaderListener详解


6.2 获取Spring容器(ApplicationContext)

方式一:使用getAttribute

//每次获取的都是一个spring容器
ApplicationContext applicationContext = 
(ApplicationContext)this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

方式二:使用WebApplicationContextUtils (推荐)

//每次获取的都是一个spring容器
WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());

7.AOP(面向切面编程)

AOP 思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 !

一、核心理解
Spring基础一(IOC、DI、容器、Bean、AOP)_第24张图片

二、专业名词

  • 连接点(JoinPoint):正在执行的方法,例如:update()、delete()、select()等都是连接点。
  • 切入点(Pointcut):进行功能增强了的方法,例如:update()、delete()方法,而select()方法没有被增强所以不是切入点,但是是连接点。
    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
      • 一个具体方法:com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法
      • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
  • 通知(Advice):在切入点前后执行的操作,也就是增强的共性功能
    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:通知方法所在的类叫做通知类
  • 切面(Aspect):描述通知与切入点的对应关系,也就是哪些通知方法对应哪些切入点方法。

7.1 SpringAOP 编程的原理

一、底层实现
Spring AOP是基于动态代理的,基于两种动态代理机制:

  • JDK动态代理
  • CGLIB动态代理

二、动态代理和静态代理区别?

  • 动态代理
    代理类在程序运行时创建的代理方式被成为动态代理。并不是真正存在的类,一般格式:Proxy$$ (Proxy$$Customer)
  • 静态代理
    实际存在代理类 (例如:struts2 Action的代理类 ActionProxy,struts2的拦截器)

7.1.1 JDK动态代理

JDK动态代理,针对目标对象的接口进行代理 ,动态生成接口的实现类 !(必须有接口)

  1. 定义接口以及接口实现类
  2. 通过动态代理的方式, 生成代理对象
    • 动态代理需要三个参数:类加载器、目标对象实现的接口、 回调方法对象
//接口(表示代理的目标接口)
public interface ICustomerService {
	//保存
	public void save();
	//查询
	public int find();
}

//实现类
public class CustomerServiceImpl implements ICustomerService{

	public void save() {
		System.out.println("客户保存了。。。。。");
	}

	public int find() {
		System.out.println("客户查询数量了。。。。。");
		return 100;
	}
}

//专门用来生成jdk的动态代理对象的-通用
public class JdkProxyFactory{
	//成员变量
	private Object target;
	//注入target目标对象
	public JdkProxyFactory(Object target) {
		this.target = target;
	}
	
	public Object getProxyObject(){
		//参数1:目标对象的类加载器
		//参数2:目标对象实现的接口
		//参数3:回调方法对象
		/**方案一:在内部实现new InvocationHandler(),指定匿名类*/
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){

			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				//如果是保存的方法,执行记录日志操作
				if(method.getName().equals("save")){
					writeLog();
				}
				//目标对象原来的方法执行
				Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象方法的返回值
				return object;
			}
			
		});
	}
	
	//记录日志
	private static void writeLog(){
		System.out.println("增强代码:写日志了。。。");
	}

}

public static void main(String[] args) {
        //target(目标对象)
        ICustomerService target = new CustomerServiceImpl();
        //实例化注入目标对象
        JdkProxyFactory jdkProxyFactory = new JdkProxyFactory(target);
        //获取Proxy Object代理对象:基于目标对象类型的接口的类型的子类型的对象
        ICustomerService proxy = (ICustomerService) jdkProxyFactory.getProxyObject();
        //调用目标对象的方法
        proxy.save();
        System.out.println("————————————————————————————————————————");
        proxy.find();
    }

从结果上看出:在保存方法的前面,输入了日志增强。
Spring基础一(IOC、DI、容器、Bean、AOP)_第25张图片
JDK动态代理的缺点:只能面向接口代理,不能直接对目标类进行代理 ,如果没有接口,则不能使用JDK代理。


7.1.2 Cglib动态代理

Cglib的引入为了解决类的直接代理问题(生成代理子类),不需要接口也可以代理 !

将spring的核心jar(spring-core)导入进来,因为spring的包,包含了cglib的包

//没有接口的类
public class ProductService {
	public void save() {
		System.out.println("商品保存了。。。。。");
		
	}

	public int find() {
		System.out.println("商品查询数量了。。。。。");
		return 99;
	}
}
//cglib动态代理工厂:用来生成cglib代理对象
public class CglibProxyFactory implements MethodInterceptor{
	//声明一个代理对象引用
	private Object target;
	//注入代理对象
	public CglibProxyFactory(Object target) {
		this.target = target;
	}
	
	//获取代理对象
	public Object getProxyObject(){
		//1.代理对象生成器(工厂思想)
		Enhancer enhancer = new Enhancer();
		//2.在增强器上设置两个属性
		//设置要生成代理对象的目标对象:生成的目标对象类型的子类型
		enhancer.setSuperclass(target.getClass());
		//设置回调方法
		enhancer.setCallback(this);
//		Callback
		//3.创建获取对象
		return enhancer.create();
	}

	
	//回调方法(代理对象的方法)
	//参数1:代理对象
	//参数2:目标对象的方法对象
	//参数3:目标对象的方法的参数的值
	//参数4:代理对象的方法对象
	public Object intercept(Object proxy, Method method, Object[] args,
			MethodProxy methodProxy) throws Throwable {
		//如果是保存的方法,执行记录日志操作
		if(method.getName().equals("save")){
			writeLog();
		}
		//目标对象原来的方法执行
		Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象方法的执行结果
		return object;
	}
	
	//写日志的增强功能
	//Advice通知、增强
	//记录日志
	private static void writeLog(){
		System.out.println("增强代码:写日志了。。。");
	}
	
}

//cglib动态代理:可以基于类(无需实现接口)生成代理对象
@Test
public void testCglibProxy(){
	//target目标:
	ProductService target = new ProductService();
	//weave织入,生成proxy代理对象
	//代理工厂对象,注入目标
	CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(target);
	//获取proxy:思考:对象的类型
	//代理对象,其实是目标对象类型的子类型
	ProductService proxy=(ProductService) cglibProxyFactory.getProxyObject();
	//调用代理对象的方法
	proxy.save();
	System.out.println("————————————————————————————————————————");
	proxy.find();
}

控制台输出结果
Spring基础一(IOC、DI、容器、Bean、AOP)_第26张图片
最后,使用断点查看cglib代理,生成的代理对象
在这里插入图片描述


7.1.3 代理总结

区别:

  • Jdk代理:基于接口的代理,一定是基于接口,会生成目标对象的接口类型的子对象。
  • Cglib代理:基于类的代理,不需要基于接口,会生成目标对象类型的子对象。

如图:
CustomerServiceImpl(基于接口)的代理对象是$Proxy
ProductService(基于一般类)的代理对象是ProductService的子类
Spring基础一(IOC、DI、容器、Bean、AOP)_第27张图片

代理知识总结:

  • spring在运行期,生成动态代理对象,不需要特殊的编译器.
  • spring有两种代理方式:
    • 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
    • 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
  • 使用该方式时需要注意:
    • 1.对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,所以spring默认是使用JDK代理。
    • 2.对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案。
    • 3.标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的。
    • 4.spring只支持方法连接点:不提供属性接入点,spring的观点是属性拦截破坏了封装。
    • 5.面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果。

7.2 Spring AOP编程的几种方式

7.2.1 传统AOP编程

7.2.1.1 Advice的5种类型

Advice通知就是增强的方式方法

  • 前置通知 org.springframework.aop.MethodBeforeAdvice
    在目标方法执行前实施增强
  • 后置通知 org.springframework.aop.AfterReturningAdvice
    在目标方法执行后实施增强
  • 环绕通知 org.aopalliance.intercept.MethodInterceptor
    在目标方法执行前后实施增强
  • 异常抛出通知 org.springframework.aop.ThrowsAdvice
    在方法抛出异常后实施增强
  • 引介通知 org.springframework.aop.IntroductionInterceptor
    在目标类中添加一些新的方法和属性

7.2.1.2 使用传统AOP编程实现环绕通知

传统AOP编程配置过于麻烦,所以这里使用AspectJ的切入点语法(xml配置)来讲解

  1. 确定切入点

  2. 编写增强方法(通知)

  3. 配置切面(对哪些切入点,进行怎样的通知)

前提、引入导入依赖包

<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-aopartifactId>
  <version>5.2.10.RELEASEversion>
dependency>

<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-aspectsartifactId>
  <version>5.2.10.RELEASEversion>
dependency>

一、编写环绕通知
编写传统aop的Advice通知类。
传统aop的通知,必须实现上面的这5类接口,例如我们编写一个环绕通知,就必须实现MethodInterceptor接口

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;
//传统的aop的advice通知,增强类,必须实现org.aopalliance.intercept.MethodInterceptor接口(注意和cglib代理接口区分开)
public class TimeLogInterceptor implements MethodInterceptor {
	//log4j记录器
	private static Logger LOG=Logger.getLogger(TimeLogInterceptor.class);

	//回调方法
	//参数:目标方法回调函数的包装类,获取调用方法的相关属性、方法名、调用该方法的对象(即封装方法、目标对象,方法的参数)
	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		//业务:记录目标的方法的运行时间
		//方法调用之前记录时间
		long beginTime = System.currentTimeMillis();
		
		//目标对象原来的方法的调用,返回目标对象方法的返回值。
		Object object = methodInvocation.proceed();//类似于invoke
		
		//方法调用之后记录时间
		long endTime = System.currentTimeMillis();
		
		//计算运行时间
		long runTime=endTime-beginTime;
		
		//写日志:
		/**
		 * 1:记录日志到数据库(优势:便于查询;劣势:要占用数据库空间,日志一般都非常庞大)
		 * 2:记录日志到log4j(优势:文件存储,可以记录非常大的日志数据,而且还有日志级别的特点;劣势:不便于查询)
		 */
		LOG.info("方法名为:"+methodInvocation.getMethod().getName()+"的运行时间为:"+runTime+"毫秒");
		
		return object;
	}

}

二、确定切入点
在确定切入点之前, 我们先要在applicationContext.xml中引入aop的文件约束,
Spring基础一(IOC、DI、容器、Bean、AOP)_第28张图片

<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">
<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">
	
	
	
	<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>
	
	<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>
	
	
	<bean id="timeLogAdvice" class="cn.itcast.spring.b_oldaop.TimeLogInterceptor"/>
	
	
	<aop:config>
		
		
		
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<aop:advisor advice-ref="timeLogAdvice" pointcut-ref="myPointcut"/>
	aop:config>
	
		
beans>

7.2.2 AspectJ实现AOP编程入门

7.2.2.1 AspectJ提供不同的通知类型

  • Before 前置通知,相当于BeforeAdvice
  • AfterReturning 后置通知,相当于AfterReturningAdvice
  • Around 环绕通知,相当于MethodInterceptor
  • AfterThrowing抛出通知,相当于ThrowAdvice
  • After 最终final通知,不管是否异常,该通知都会执行
  • DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)

相比传统Spring AOP通知类型多了 After最终通知 (类似 finally )。


7.2.2.2 使用AspectJ实现AOP编程

一、确定切入点
我们这把目标对象是name以Service结尾的bean

1.我们创建两个bean, 一个基于接口的实现类, 另一个是一般类, 验证Jdk和Cglib的使用

接口实现类(CustomerServiceImpl)

public class CustomerServiceImpl implements ICustomerService {

    public void save() {
        System.out.println("客户保存了。。。。。");
    }

    public int find() {
        System.out.println("客户查询数量了。。。。。");
        return 100;
    }

}

一般类(ProductService)

public class ProductService {
    public void save() {
        System.out.println("商品保存了。。。。。");

    }

    public int find() {
        System.out.println("商品查询数量了。。。。。");
        return 99;
    }
}

2.注册到Spring容器中, 取名时都以Service结尾




<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>

<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>

二、编写advice增强
与传统AOP的不同在于, 不用去实现对应的接口(aspectj的advice通知增强类,无需实现任何接口)

//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
	
	//前置通知
	//普通的方法。方法名随便,但也不能太随便,一会要配置
	public void firstbefore(){
		System.out.println("------------第一个个前置通知执行了。。。");
	}
}

同样将前置通知配置到spring的容器中


<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>

三、配置切面


<aop:config>

	
	<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
	
	
	<aop:aspect ref="myAspectAdvice">
		
		
	    <aop:before method="firstbefore" pointcut-ref="myPointcut"/>
	aop:aspect>
aop:config>

7.2.2.3 AspectJ方式与传统AOP的不同

  • 传统AOP的advice需要实现对应接口(通知种类),而AspectJ的AOP不需要
  • 传统AOP是通过标签来配置切面(因为advice已经实现了对应的通知种类,所以不用在切面中配置),而AspectJ是通过等标签来标识通知种类

8.使用AspectJ实现各类通知

8.1 Before前置通知

8.1.1 xml方式

通过JoinPoint 连接点对象,获取目标对象信息 !(org.aspectj.lang.JoinPoint)
Spring基础一(IOC、DI、容器、Bean、AOP)_第29张图片
一、编写通知
我们可以将所有的通知都写到同一个类中,
配置MyAspect类(切面),配置before方法(通知)

//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {	

	//前置通知的:方法运行之前增强
	//应用: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志 
	//参数:org.aspectj.lang.JoinPoint
	//参数:连接点对象(方法的包装对象:方法,参数,目标对象)
	public void before(JoinPoint joinPoint){
		//分析:抛出异常拦截的
		//当前登录用户
		String loginName = "Rose";
		System.out.println("方法名称:"+joinPoint.getSignature().getName());
		System.out.println("目标对象:"+joinPoint.getTarget().getClass().getName());
		System.out.println("代理对象:"+joinPoint.getThis().getClass().getName());
		//判断当前用户有没有执行方法权限
		if(joinPoint.getSignature().getName().equals("save")){
			if(!loginName.equals("admin")){
				//只有超级管理员admin有权限,其他人不能执行某个方法,比如查询方法
				throw new RuntimeException("您没有权限执行方法:"+joinPoint.getSignature().getName()+",类型为:"+joinPoint.getTarget().getClass().getName());
			}
		}
		
	}
}

二、配置切面(关联切入点和通知)


<bean id="customerService" class="com.itheima.jdk.CustomerServiceImpl"/>

<bean id="productService" class="com.itheima.cglib.ProductService"/>


<bean id="myAspectAdvice" class="com.itheima.aop.MyAspect"/>

<aop:config>
    
    <aop:aspect ref="myAspectAdvice">
        
        <aop:pointcut expression="bean(*Service)" id="myPointcut"/>
        
        <aop:before method="before" pointcut-ref="myPointcut"/>
    aop:aspect>
aop:config>

简约写法

<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>	
<aop:config>
	<aop:aspect ref="myAspectAdvice">
		
		<aop:before method="before" pointcut="bean(*Service)" />
	aop:aspect>
aop:config>

测试

//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext-aspectj.xml")
public class SpringTest {
	//注入要测试bean
	@Autowired
	private ICustomerService customerService;
	@Autowired
	private ProductService productService;
	
	//测试
	@Test
	public void test(){
		//基于接口
		customerService.save();
		customerService.find();
		//基于类的
		productService.save();
		productService.find();
	}

}

8.1.2 注解方式

一、确定目标对象
确定目标对象(目标对象需要被Spring管理),此处给目标对象设置了id标识()

//接口
public interface CustomerService {
	//保存
	public void save();
	
	//查询
	public int find();

}

//实现类
/**
 * @Service("customerService")
 * 相当于spring容器中定义:
 * 
 */
@Service("customerService")
public class CustomerServiceImpl implements CustomerService{

	public void save() {
		System.out.println("客户保存了。。。。。");
		
	}

	public int find() {
		System.out.println("客户查询数量了。。。。。");
		return 100;
	}

}

//没有接口的类
/**
 * @Service("productService")
 * 相当于spring容器中定义:
 * 
 */
@Service("productService")
public class ProductService {
	public void save() {
		System.out.println("商品保存了。。。。。");
		
	}

	public int find() {
		System.out.println("商品查询数量了。。。。。");
		return 99;
	}
}

二、开启aop的aspectj的自动代理
在applicationContext.xml中开启aop的aspectj的自动代理

  • 1.要引入aop的命名空间
  • 2.配置aop的AspectJ注解自动代理

<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
                           https://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">

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

    
    <aop:aspectj-autoproxy/>
beans>

三、编写通知,并加上注解
需要保证通知类被Spring管理, 其次添加@Aspect注解, 当我们开启AspectJ注解自动代理机制后(), Sping能自动扫描带有@Aspect的bean,将其作为增强aop的配置,有点相当于:

之后只需要在通知方法上添加对应的注解即可, 例如@Before(前置通知)

// aspectj的advice通知增强类,无需实现任何接口
@Component("myAspect")
@Aspect
public class MyAspect {

    //前置通知的:方法运行之前增强
    //应用: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志
    //参数:org.aspectj.lang.JoinPoint
    //参数:连接点对象(方法的包装对象:方法,参数,目标对象)
    @Before("bean(*Service)")
    public void before(JoinPoint joinPoint) {
        //分析:抛出异常拦截的
        //当前登录用户
        String loginName = "Rose";
        System.out.println("方法名称:" + joinPoint.getSignature().getName());
        System.out.println("目标对象:" + joinPoint.getTarget().getClass().getName());
        System.out.println("代理对象:" + joinPoint.getThis().getClass().getName());
        System.out.println("-----------------------------------------------------");
        //判断当前用户有没有执行方法权限
        if (joinPoint.getSignature().getName().equals("save")) {
            if (!loginName.equals("admin")) {
                //只有超级管理员admin有权限,其他人不能执行某个方法,比如查询方法
                throw new RuntimeException("您没有权限执行方法:" + joinPoint.getSignature().getName() + ",类型为:" + joinPoint.getTarget().getClass().getName());
            }
        }
    }
}

四、切入点表达式的两种写法

  • 方式一:可以直接将切入点的表达式写到@Before()中
//前置通知
//相当于:
   //@Before("bean(*Service)"):参数值:自动支持切入点表达式或切入点名字
@Before("bean(*Service)")
public void before(JoinPoint joinPoint){
	System.out.println("=======前置通知。。。。。");
}
  • 方式二:可以使用自定义方法,使用@Pointcut 定义切入点
    切入点方法的语法要求:private void 无参数、无方法体的方法,方法名为切入点的名称
//自定义切入点
//方法名就是切入点的名字
//相当于
@Pointcut("bean(*Service)")
private void myPointcut(){}
//自定义切入点
//方法名就是切入点的名字
//相当于
@Pointcut("bean(*Service)")
private void myPointcut2(){}


//前置通知
//相当于:
//相当于:
@Before("myPointcut()||myPointcut2()")
public void before(JoinPoint joinPoint){
	System.out.println("=======前置通知。。。。。");
}

8.1.3 应用场景

  • 实现权限控制 (即:权限不足的时候,抛出异常)
  • 记录方法调用信息日志

8.2 AfterReturing后置通知

特点:在目标方法运行后,返回值后执行通知增强代码逻辑。
分析: 后置通知可以获取到目标方法返回值,如果想对返回值进行操作,使用后置通知(但不能修改目标方法返回 )

8.2.1 xml方式

一、配置通知
配置afterReturing方法(通知)

//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {	
	//应用场景:与业务相关的,如网上营业厅查询余额后,自动下发短信。
	//后置通知:会在目标方法执行之后调用通知方法增强。
	//参数1:连接点对象(方法的包装对象:方法,参数,目标对象)
	//参数2:目标方法执行后的返回值,类型是object,“参数名”随便,但也不能太随便,一会要配置
	public void afterReturing(JoinPoint joinPoint,Object returnVal){
		//下发短信:调用运行商的接口,短信猫。。。
		System.out.println("-++++++++-后置通知-当前下发短信的方法"+"-尊敬的用户,您调用的方法返回余额为:"+returnVal);
		 
	}
}

二、配置切面




<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>

<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>

<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>


<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		
		<aop:after-returning method="afterReturing" returning="returnVal" pointcut-ref="myPointcut"/>
	aop:aspect>

aop:config>

8.2.2 注解方式

需要开启aop的aspectj的自动代理,

@Component("myAspect")
@Aspect
public class MyAspect {

    @AfterReturning(value="bean(*Service)",returning="returnVal")
    public void afterReturing(JoinPoint joinPoint, Object returnVal) {
        //下发短信:调用运行商的接口,短信猫。。。
        System.out.println("-++++++++-后置通知-当前下发短信的方法" + "-尊敬的用户,您调用的方法返回余额为:" + returnVal);

    }
}

8.2.3 应用场景

  • 网上营业厅查询余额后
  • 自动下发短信功能

8.3 Around 环绕通知

特点:目标执行前后,都进行增强(控制目标方法执行)

增强代码的方法要求:

  • 接受的参数:ProceedingJoinPoint(可执行的连接点)
  • 返回值:Object返回值(即目标对象方法的返回值)
  • 抛出Throwable异常。

8.3.1 xml方式

第一步:配置MyAspect类(切面),配置around方法(通知)

//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {	
	//应用场景:日志、缓存、权限、性能监控、事务管理
	//环绕通知:在目标对象方法的执行前+后,可以增强
	//参数:可以执行的连接点对象ProceedingJoinPoint(方法),特点是调用proceed()方法可以随时随地执行目标对象的方法(相当于目标对象的方法执行了)
	//必须抛出一个Throwable
	public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
		//目标:事务的控制:
		//开启事务:
		System.out.println("-----开启了事务。。。。。。。。。");
		//执行了目标对象的方法
		Object resultObject = proceedingJoinPoint.proceed();
		//结束事务
		System.out.println("-----提交了事务。。。。。。。。。");
		return resultObject;//目标对象执行的结果
	}
}

第二步:Spring容器中配置,配置applicationContext-aspectJ.xml




<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>

<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>

<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>


<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		
		<aop:around method="around" pointcut-ref="myPointcut"/>
	aop:aspect>

aop:config>

8.3.2 注解方式

@Component("myAspect")
@Aspect
public class MyAspect {

    @Around("bean(*Service)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("---环绕通知-----前");
        Object object = proceedingJoinPoint.proceed();
        System.out.println("---环绕通知-----后");
        return object;
    }
}

8.3.3 应用场景

  • 日志
  • 缓存
  • 权限
  • 性能监控
  • 事务管理

8.4 AfterThrowing 抛出通知

作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)

8.4.1 xml方式

第一步:配置MyAspect类(切面),配置aterThrowing方法(通知)

//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {	
	//作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
	//只有目标对象方法抛出异常,通知才会执行
	//参数1:静态连接点(方法对象)
	//参数2:目标方法抛出的异常,参数名随便,但也不能太随便
	public void afterThrowing(JoinPoint joinPoint,Throwable ex){
		//一旦发生异常,发送邮件或者短信给管理员
		System.out.println("++管理员您好,"+joinPoint.getTarget().getClass().getName()+"的方法:"
		+joinPoint.getSignature().getName()+"发生了异常,异常为:"+ex.getMessage());
	}
}

第二步:Spring容器中配置,配置applicationContext-aspectJ.xml




<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>

<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>

<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>


<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		
		<aop:after-throwing method="aterThrowing" throwing="ex" pointcut-ref="myPointcut"/>		
       aop:aspect>

aop:config>

在ProductService.java中save的方法中,制造异常:

//没有接口的类
public class ProductService {
	public void save() {
		System.out.println("商品保存了。。。。。");
		//故意制造异常
		int d = 1/0;
	}

	public int find() {
		System.out.println("商品查询数量了。。。。。");
		return 99;
	}
}

只有当切入点发生异常时,才会执行


8.4.2 注解方式

@Component("myAspect")
@Aspect
public class MyAspect {

    @AfterThrowing(value="bean(*Service)",throwing="ex")
    public void afterThrowing(JoinPoint joinPoint , Throwable ex){
        System.out.println("---抛出通知。。。。。。"+"抛出的异常信息:"+ex.getMessage());
    }
}

8.4.3 应用场景

  • 处理异常(一般不可预知)
  • 记录日志

8.5 After 最终通知

8.5.1 xml方式

作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)

//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {	
	//应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
	//最终通知:不管是否有异常都会执行
	public void after(JoinPoint joinPoint){
		//释放数据库连接
		System.out.println("数据库的connection被释放了。。。。。,执行的方法是:"+joinPoint.getSignature().getName());
	}
}

第二步:Spring容器中配置,配置applicationContext-aspectJ.xml




<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>

<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>

<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectjaop.MyAspect"/>


<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		
		<aop:after method="after" pointcut-ref="myPointcut"/>
           
	aop:aspect>

aop:config>

8.5.2 注解方式

//最终通知
//拦截所有以ice结尾的bean
@After("bean(*ice)")
public void after(JoinPoint joinPoint){
	System.out.println("+++++++++最终通知。。。。。。。");
}

8.6 各种Advice方法可接收的参数和返回值小结

Spring基础一(IOC、DI、容器、Bean、AOP)_第30张图片


8.7 如果目标对象有接口,能否只对实现类代理,而不对接口进行代理呢

第一步:在CustomerServiceImpl的子类中添加一个新的方法update(),而接口中不要定义update()的方法:

@Service("customerService")
public class CustomerServiceImpl implements CustomerService{

	public void save() {
		System.out.println("客户保存了。。。。。");
		
	}

	public int find() {
		System.out.println("客户查询数量了。。。。。");
		return 100;
	}
	
	//子类扩展方法
	public void update(){
		System.out.println("客户更新了。。。新增方法。。。");
	}

}

第二步:在测试类中调用子类的扩展方法:

//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
	//注入要测试bean
	@Autowired
	private CustomerService customerService;
	@Autowired
	private ProductService productService;
	
	//测试
	@Test
	public void test(){
		//基于接口
		customerService.save();
		customerService.find();
		
		//基于类的
		productService.save();
		productService.find();
		
		//扩展方法执行:customerService是一个动态代理对象,原因,该对象是接口的子类型的对象
		((CustomerServiceImpl)customerService).update();
	}

}

结果发现异常:
在这里插入图片描述
为什么会抛出异常呢?原因是代理的目标对象是接口,无法转换为子类。
Spring基础一(IOC、DI、容器、Bean、AOP)_第31张图片
Spring基础一(IOC、DI、容器、Bean、AOP)_第32张图片
结局方式:设置 proxy-target-class = true

  • 方案一:注解方式

<aop:aspectj-autoproxy proxy-target-class="true"/>
  • 方案二:配置文件XML的方式

<aop:config proxy-target-class="true">
aop:config>

你可能感兴趣的:(AOP,java,spring)