文章目录
一、Spring概述与环境搭建
Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。
Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。
Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson编写的,并且于 2003 年 6 月首次在 Apache 2.0 许可下发布。
Spring环境搭建
Spring5至少要JDK8,Servlet3.1,Tomcat8.5+。请在实际开发时针对自己的 开发环境,选择Spring版本,Spring4支持JDK6/7/8。
创建工程
配置pom.xml引入Spring依赖包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
</dependencies>
测试是否正常运行
创建一个类
package pojo;
public class Master {
public void sayHello(){
System.out.println("hello world"); }
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
配置spring.xml
配置文件
<bean id="master" class="pojo.Master"></bean>
创建一个测试类
@Test
public void testSpring() {
ApplicationContext context= new ClassPathXmlApplicationContext("spring.xml");
Master master = (Master)context.getBean("master"); master.sayHello();
}
//运行结果
//hello world
运行结果成功表示环境配置成功
集成Junit
<!-- JUnit单元测试框架 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--集成Junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
测试类代码
//测试类使用@Autowired自动注入时,就需要使用@RunWith注解和@ContextConfiguration注解
@RunWith(SpringJUnit4ClassRunner.class)//让测试运行于Spring测试环境,以便在测试开始的时候自动创建Spring的应用上下文
@ContextConfiguration({"classpath:spring.xml"})//获取配置文件spring.xml中的bean
public class TestUnit {
@Resource
private Master master;
@Test
public void springtest(){
master.hell();
}
}
@runWith注解作用:
- @RunWith就是一个运行器
- @RunWith(JUnit4.class)就是指用JUnit4来运行
- @RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境,以便在测试开始的时候自动创建Spring的应用上下文
- @RunWith(Suite.class)的话就是一套测试集合
@ContextConfiguration注解作用:
- @ContextConfiguration({“classpath:spring.xml”})获取配置文件spring.xml中的bean
- @ContextConfiguration(classes = ApplicationConfig.class)获取配置文件ApplicationConfig的bean
二、控制反转IoC
在传统的方式中,如果我们要用到一个类的对象,需要自己new一个出来,如:
@Test
public void testMaster() {
Master master = new Master();
master.sayHello();
}
Master的对象master在testMaster方法中创建,这时master对象的控制权是属于testMaster的。而在Spring中,我们看不到new Master()的操作。而是通过Spring的ApplicationContext获得:
ApplicationContext context= new ClassPathXmlApplicationContext("spring.xml");
//不是自己new的对象,而是从ApplicationContext中获得 Master master = (Master) context.getBean("master");
其中:
context.getBean("master")
getBean方法的参数值master,对应的是spring.xml中的bean的id:
<bean id="master" class="pojo.Master"></bean>
可见master对象是由Spring创建的,其控制权属于Sping而不属于使用者。 这种设计叫做“控制反转(Inversion of Control,英文缩写为IoC)”。
为什么要控制反转
IoC把对象生成放在了XML里定义,所以当我们需要换一个实现子类将会变成很简单(一般这样的对象都是实现于某种接口的),只要修改XML就可以了。
IoC的缺点是,生成一个对象的步骤变复杂了,对于不习惯这种方式的人, 会觉得有些别扭和不直观。对象生成因为是使用反射编程,在效率上有些损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的, 除非某对象的生成对效率要求特别高。
三、多种方式实现依赖注入
1、属性注入
属性注入是通过属性的setter方法进行注入,需要注入的属性必须有setter 方法且命名必须符合规范。如属性名是abc,那么setter方法应为setAbc()
基本数据类型
<property name="属性名" value="基本类型的属性值"/>
示例:
<bean id="master" class="pojo.Master">
<property name="name" value="张三"/>
</bean>
相当于
Master master = new Master();
master.setName="张三";
注入对象类型
需要先创建对象bean
<property name="属性名" ref="对象的bean的id"/>
示例:
<bean id="master" class="pojo.Master">
<property name="pet" ref="pet"/>
</bean>
注入集合和数组类型
list和数组注入方式相同
<!--给数组或者List赋值-->
<property name="stringList">
<list>
<!--普通类型-->
<value>abc</value>
<value>efg</value>
<!--注入对象类型-->
<!--<ref bean="其他bean的id"/>-->
</list>
</property>
<!--注入Set类型-->
<property name="stringSet">
<set>
<!--普通类型-->
<value>abc</value>
<value>abc2</value>
<!--注入对象类型-->
<!--<ref bean="其他bean的id"/>-->
</set>
</property>
<!--注入Map-->
<property name="objectMap">
<map>
<!--注入普通类型-->
<entry key="a" value="aa"/>
<!--注入对象类型-->
<!--<entry key="b" value-ref="其它bean的id"/>-->
</map>
</property>
2、构造方法注入
Spring创建对象默认使用的是类的无参构造方法。所以在某个类添加带参构造函数的时候,记得将无参构造写出来,否则会导致某些情况下Spring无法创建对象。
按类型注入
constructor-arg
标签用于给构造方法注入参数,type
是参数类型,value
是参数值。
对于有特殊字符的属性,在value子节点中只用CDATA。如示例中,注入的 String类型参数值为“<sansan>
”,其中<>是xml中的节点符号,属于特殊字符。
注入对象类型,使用ref引用其他bean的id。
<constructor-arg type="属性类型" value="基本类型属性值"/>
<constructor-arg type="属性类型" ref="对象的bean的id"/>
示例:
<bean id="petBean" class="pojo.Cat"></bean>
<bean id="master" class="pojo.Master">
<!--注入基本数据类型-->
<constructor-arg type="java.lang.Integer" value="20"/> <!-- 对于包含特殊字符的属性值,可以在value子节点使用CDATA -->
<constructor-arg type="java.lang.String">
<value><![CDATA[<sansan>]]></value>
</constructor-arg>
<!--ref用于注入对象类型-->
<constructor-arg type="pojo.Pet" ref="petBean"/>
</bean>
按位置注入
如果构造方法里有多个参数的类型是相同的,可以使用按位置注入的方式
constructor-arg 的index属性用于指定构造方法中参数的索引,从0开始。
示例:
<bean id="petBean" class="pojo.Cat"></bean>
<bean id="master" class="pojo.Master">
<constructor-arg value="20" index="1"/>
<constructor-arg value="张三" index="0"/>
<constructor-arg ref="pet" index="2"/>
</bean>
按名称注入(最常用)
<bean id="pet" class="pojo.Cat"></bean>
<bean id="master" class="pojo.Master">
<!--注入基本数据类型-->
<constructor-arg name="age" value="20"/>
<!-- 对于包含特殊字符的属性值,可以在value子节点使用CDATA -->
<constructor-arg name="name">
<value><![CDATA[<sansan>]]></value>
</constructor-arg>
<!--ref用于注入对象类型-->
<constructor-arg name="pet" ref="pet"/>
</bean>
P命名空间注入
P命名空间注入需要先引入头文件:
xmlns:p="http://www.springframework.org/schema/p"
在标签上使用p:属性名=“属性值”进行值注入。使用p:属性名-ref=”bean的id” 进行对象注入。
示例:
<bean class="pojo.Master" id="master5" p:name="筑梦者" p:age="18" p:cat-ref="pet"/>
工厂方法注入
静态工厂注入
工厂bean的class都是工厂类型,但实际的bean的对象是通过factory- method获得的。getCat()和getDog()返回的都是Pet类型的对象。所以 catBean和dogBean都是Pet类型的。
PetShop类:
package pojo;
public class PetShop {
public static Pet getDog() {
return new Dog();
}
public static Pet getCat() {
return new Cat();
}
public static Pet getPet(String name) {
if (name == "cat") {
return new Cat();
} else {
return new Dog();
}
}
配置文件写法
<!--创建工厂bean,调用不带参数的方法-->
<bean id="catBean" class="pojo.PetShop" factory- method="getCat"/>
<!--创建工厂bean,调用带参数的方法-->
<bean id="dogBean" class="pojo.PetShop" factory- method="getPet">
<constructor-arg value="dog"/>
</bean>
<!--可以给master分别注入catBean和dogBean,看运行结果-->
<bean id="master" class="pojo.Master">
<property name="pet" ref="dogBean"/>
</bean>
测试
@Test
public void testFactory() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
Master master = (Master) context.getBean("master"); master.getPet().shout();
}
实例工厂注入
实例工厂和静态工厂的区别在于实例工厂必须先初始化工厂对象,工厂中的方法没有static
示例:
package pojo;
public class PetShop {
public Pet getDog() {
return new Dog();
}
public Pet getCat() {
return new Cat();
}
public Pet getPet(String name) {
if (name == "cat") {
return new Cat();
} else {
return new Dog();
}
}
}
配置文件
<bean id="shopBean" class="pojo.PetShop"></bean>
<bean id="petBean" factory-bean="shopBean" factory- method="getCat"></bean>
<bean id="master" class="pojo.Master">
<property name="pet" ref="petBean"/>
</bean>
3、使用注解
使用注解可以批量生成bean,扫描注解修饰的类。需要引入头文件。
xmlns:context="http://www.springframework.org/schema/context"
//不指定id,默认id是类名首字母小写
@Component("person")
public class Person {
@Qualifier("cat")//注入其它bean
private Pet pet;
//getter/setter方法略
}
@Component
注解修饰一个类,这个类会创建成bean。@Component(“bean 的id”),如果不指定id,默认的id是类名首字母小写。
@Autowired
注解是按类型注入,默认情况下它要求依赖对象必须存在,如 果允许null值,可以设置它的required属性为false。当有多个类型一样的 bean存在时,会出现异常。
@Qualifier
注解用于注入其它bean(按bean的id注入)。 一般作为@Autowired的修饰,不能单独使用注入属性
@Resource
注解是java的注解,spring提供了支持。它可以实现按名称注入 和按类型注入。
//@Resource(name = "cat")//按名称注入
@Resource(type = Cat.class)//按类型注入
private Pet pet;
@Qualifier, @Autowired, @Resource
可以用在属性上,也可以放在setter方法 上。
其它注解:
@Controller
分层开发中用于修饰web层的类。
@Service
分层开发时用于修饰service层的类。
@Repository
分层开发时用于修饰dao层的类。
这三个注解都是对于@Component更具体的实现。
@PostConstruct
依赖注入成功后被调用。构造方法->@Autowire
- >@PostConstruct
@PreDestroy
销毁前执行。
Spring4之后新增一系列注解可以代替xml文件。
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import pojo.Cat;
import pojo.Dog;
import pojo.Pet;
@Configuration//相当于代替一个xml文件
@ComponentScan(basePackages = "service",
excludeFilters =
@ComponentScan.Filter(Repository.class),
includeFilters = @ComponentScan.Filter(Service.class), useDefaultFilters = false)
public class AppConfig {
@Bean//默认单例
public Pet cat() {
return new Cat();
}
@Bean
@Scope("prototype")//每次都获得一个新的实例
public Dog dog() {
return new Dog();
}
}
注解示例:
创建一个Bird类,并使用@Component注解,默认类名首字母小写,相当于在配置文件创建了一个bean
@Component("bird")
public class Bird {
private String name;
private Integer age;
public void run(){
System.out.println("一只小小鸟,自由自在的飞啊飞~~~~");
}
//略
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Bird.class)//加载bean
public class TestUnit {
@Autowired
private Bird bird;
@Test
public void test(){
bird.run();
}
}
运行结果:
一只小小鸟,自由自在的飞啊飞~~~~
这样在类明上加@Component注解只能创建一个bean,如果想要多个bean可以使用配置文件类
@Configuration//相当于spring.xml
public class ApplicationConfig {
//创建多个bean
@Bean
public Bird bird1(){
return new Bird();
}
@Bean
public Bird bird2(){
return new Bird();
}
}
测试类:
@Autowired是按类型注入,当有多个相同类型的对象,需要注意变量名,这里的bird名要和配置文件类中的方法名一致,否则会报错,如配置文件第一个方法名是bird1,加载bean也要这个变量
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class)//加载bean
public class TestUnit {
@Autowired
private Bird bird1;
@Autowired
private Bird bird2;
@Test
public void test(){
System.out.println(bird1);
System.out.println(bird2);
}
}
运行结果:
com.booy.pojo.anon.Bird@3c419631
com.booy.pojo.anon.Bird@418e7838
可以通过@Resource注解指定id或者@Autowired配合@Qualifier指定id
@Resource注解指定id
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class)//加载bean
public class TestUnit {
@Resource(name = "bird1")
private Bird bird;
@Test
public void test(){
bird.run();
}
}
@Autowired配合@Qualifier指定id
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class)//加载bean
public class TestUnit {
@Autowired
@Qualifier("bird1")
private Bird bird;
@Test
public void test(){
bird.run();
}
}
运行结果:
一只小小鸟,自由自在的飞啊飞~~~~
@ComponentScan
:定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中
定义一个pserson类,pserson养了一只鸟
@Component
@ComponentScan(value = "com.booy.pojo.anon")//扫描bean
public class Person {
private String name;
private Integer age;
@Resource
private Bird bird;
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Person.class)//加载bean
public class TestUnit {
@Autowired
private Person person;
@Test
public void test(){
person.getBird().run();
}
}
运行结果:
一只小小鸟,自由自在的飞啊飞~~~~
四、Spring工作原理
Spring特点
a) 方便解耦,简化开发:对象之间的依赖关系交给Spring进行控制。
b) AOP切面编程:对OOP的有利补充。
c) 声明式事务支持:通过声明的方式灵活的进行事务的管理。
d) 集成各种框架
Spring容器
Spring有两个核心接口:BeanFactory
和ApplicationContext
。
ApplicationContext实例即是Spring容器,它默认会实例化所有的singleton bean(单例bean)。
ApplicationContext是BeanFactory的子接口。它们都可以代表Spring容器。 BeanFactory在需要bean实例的时候才会创建bean,而ApplicationContext在初始化容器的时候实例化所有的singleton bean。单例bean可能在初始化的时候需要更多的系统资源,一旦创建成功,应用的响应速度会很快。因此,建议使用ApplicationContext作为Spring容器。
Spring容器是生成和管理Bean示例的工厂,bean是Spring中的基本单位,在基于Spring的java工程中,所有的组件都是bean。
BeanFactory
BeanFactory:是IOC容器的核心接口, 它定义了IOC的基本功能,我们看到它主要定义了getBean方法。getBean方法是IOC容器获取bean对象和引发依赖注入的起点。方法的功能是返回特定的名称的Bean。
BeanFactory用于初始化 Bean和调用它们生命周期方法。注意, BeanFactory 只能管理单例(Singleton)Bean的生命周期。它不能管理原型(prototype,非单例)Bean 的生命周期。这是因为原型 Bean实例被创建之后便被传给了客户端,容器失去了对它们的引用。
BeanFactory接口结构图
BeanFactory的源码:
public interface BeanFactory {
//用来引用一个实例,或把它和工厂产生的Bean区分开,
//如果一个FactoryBean的名字为a,那么,&a会得到那个Factory
String FACTORY_BEAN_PREFIX = "&";
//四个不同形式的getBean方法,获取实例
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
boolean containsBean(String name); // 是否存在
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 是否为单实例
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 是否为原型(多实例)
boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException;// 名称、类型是否匹配
Class<?> getType(String name) throws NoSuchBeanDefinitionException; // 获取类型
String[] getAliases(String name);// 根据实例的名字获取实例的别名
}
使用XmlBeanfactory
Resource resource = new ClassPathResource("spring- config.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
-
容器启动阶段,首先读取bean的xml配置文件,解析xml中各种bean的定义,每一个会封装成一个BeanDefinition对象。可以参考 AbstractBeanDefinition类的代码。
-
通过BeanDefinitionRegistry将这些bean注册到beanFactory中。 DefaultListableBeanFactory是BeanDefinitionRegistry的实现类, BeanDefinition被保存到DefaultListableBeanFactory的一个 ConcurrentHashMap中,所以bean是通过key-value的形式存储在工厂中的。
-
接下来进入bean的实例化阶段,通过实现各种Aware接口,如 BeanFactoryAware, ApplicationContextAware,Spring会注入对应的 BeanFactory,ApplicationContext等。
BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。
Spring还有一个FactoryBean接口,其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是 factory这个bean,而是这个bean.getOjbect()方法的返回值。不要把 FactoryBean和BeanFactory混淆。
ApplicationContext
ApplicationContext接口作为BeanFactory的派生,因而提供BeanFactory所有的功能。而且ApplicationContext还在功能上做了扩展,相较于BeanFactorty,ApplicationContext还提供了以下的功能:
(1)MessageSource, 提供国际化的消息访问
(2)资源访问,如URL和文件
(3)事件传播特性,即支持aop特性
(4)载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个 特定的层次,比如应用的web层
ApplicationContext有多个实现类:
1) AnnotationConfigApplicationContext
:从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式;
2) ClassPathXmlApplicationContext
:从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式;
3) FileSystemXmlApplicationContext
:从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件;
4) AnnotationConfigWebApplicationContext
:专门为web应用准备的,适用于注解方式;
5) XmlWebApplicationContext
:从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。
五、Bean的生命周期和作用域
单例Bean的生命周期
1、实例化一个Bean--也就是我们常说的new;
2、按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;
3、如果这个Bean已经实现了BeanNameAware
接口,会调用它实现的 setBeanName(String)
方法,此处传递的就是Spring配置文件中Bean的 id值
4、如果这个Bean已经实现了BeanFactoryAware
接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)
传递的是Spring工厂自身
(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一 个普通的Bean就可以);
5、 如果这个Bean已经实现了ApplicationContextAware
接口,会调用setApplicationContext(ApplicationContext)
方法,传入Spring上下文
(同样这个方式也可以实现步骤4的内容,但比4更好,因为 ApplicationContext是BeanFactory的子接口,有更多的实现方法);
6、如果这个Bean关联了BeanPostProcessor
接口,将会调用postProcessBeforeInitialization(Object obj, String s)
方法, BeanPostProcessor
经常被用作是Bean内容的更改,并且由于这个是在 Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;
7、 如果Bean在Spring配置文件中配置了init-method
属性会自动调用其配置的初始化方法。
8、如果这个Bean关联了BeanPostProcessor
接口,将会调用 postProcessA
9、当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean
这个接口,会调用那个其实现的destroy()
方法;
10、最后,如果这个Bean的Spring配置中配置了destroy-method
属性,会自动调用其配置的销毁方法。
个人对生命周期的理解
主要四个部分:实例化、属性注入、初始化、销毁
首先spring容器启动时会先实例化一个bean,其次属性注入,Spring的依赖注入中所有的Bean对Spring容器的存在是没有意识的,当我们要使用到容器中的资源,就可以实现相关Aware接口获取到spring容器本身的资源,如果实现了BeanNameAware、BeanFactoryAware、ApplicationContextAware接口会调用到相关接口的方法,接下来就是初始化,如果关联了BeanPostProcessor接口会在初始化前后进行处理工作,当bean不需要时,如果实现了DisposableBean接口就会调用destroy()方法,如果配置了destroy-method属性,会自动调用其配置的销毁方法。
Bean的作用域
当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:
singleton
:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
prototype
:原型模式,每次通过容器的getBean方法获取prototype定义的 Bean时,都将产生一个新的Bean实例。Bean完全交给客户端管理,容器不在跟踪生命周期。
request
:对于每次HTTP请求,使用request
定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用 Spring时,该作用域才有效
session
:对于每次HTTP Session,使用session
定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
application
:web环境中,一个ServletContext生命周期中的单例对象。
websocket
:WebSocket环境中使用。
其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成 prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实 例,然后返回给程序。在这种情况下,Spring容器仅仅使用new关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。
如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。
六、面向切面AOP
1、核心概念
a)、Aspect
: 切面,一个模块化的关注点,它横跨多个类。事务管理是企业 Java应用程序中横切关注点的一个很好的例子。在Spring AOP中,切面通过使用常规类(基于模式的方法)或通过@Aspect注解修饰的类来实现。
b)、Join point
: 连接点。程序执行过程中的一个点,如在方法上进行统一的异 常处理。SpringAOP中,某个方法执行前、执行后、抛出异常后等一些具有 边界性质的特定点,称作连接点。
c)、Advice
: 增强。在一个特定切入点上采取的动作。
d)、Pointcut
: 切点。某个特定的连接点就是切点。
e)、Introduction
: 引介。引介是一种特殊的增强,它为类添加一些属性和方法. 这样,即使一个业务类原本没有实现某一个接口,通过AOP的引介功能,也可以 动态地为该业务类添加接口的实现逻辑.让业务类成为这个接口的实现类。
f)、Target object
: 目标对象。增强逻辑的织入目标类。如果没有AOP,那么目标 业务类需要自己实现所有逻辑,如果使用AOP可以把一些非逻辑性代码通过 AOP织入到主程序代码上。由于通过使用运行时代理来实现Spring AOP,所 以该对象始终是代理对象。
g)、AOP proxy
: AOP框架为了实现切面增强而创建的对象。在Spring框架中, AOP代理是JDK动态代理或CGLIB代理。
h)、Weaving
: 织入。织入是将增强添加到目标类具体链接点上的过程
Spring中的增强有五种:
1、前置增强(Before
): 在方法执行前调用。
2、后置增强(AfterReturning
): 在方法执行后调用(方法正常结束,没有异
常退出)。
3、异常抛出增强(AfterThrowing
): 在方法抛出异常时调用。。
4、环绕增强(Around
): 在方法执行前后都调用。。
5、最终增强(After
): 在方法执行后执行(不管是正常退出还是异常退出)
2、手写动态代理
动态代理并不存在代理类, 代理对象直接由代理生成工具动态生成
Jdk代理
JDK动态代理代理实现接口的类。
主业务接口
package jdk;
//主业务接口
public interface ActorInerface{
void play();
}
主业务具体实现
package jdk;
public class MovieActor implements ActorInerface {
@Override
public void play() {
System.out.println("一个演员正在表演");
}
}
代理类:
package jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//代理类
public class JdkProxy {
private MovieActor target;
public JdkProxy() {
this.target = new MovieActor();
}
//创建代理
public Object getProxyInstance(){
//创建target类信息对象
Class aClass = target.getClass();
//Proxy.newProxyInstance()创建动态代理方法
//loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
//interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
//h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
return Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(), new InvocationHandler() {
//代理对象要执行的方法
//proxy:代理对象
//method:被代理要执行的方法
//args:被代理对象的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("合作洽谈,签订合同");//前置增强
//返回反射调用后的返回值
Object invoke = method.invoke(target, args);
System.out.println("履行合同收款!");//后置增强
return invoke;
}
});
}
}
测试类:
package jdk;
public class Test{
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();
ActorInerface proxyInstance = (ActorInerface)jdkProxy.getProxyInstance();
proxyInstance.play();
}
}
//运行结果:
//合作洽谈,签订合同
//一个演员正在表演
//履行合同收款!
Cglib代理
cglib针对类来实现代理的,对指定目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
需要引入依赖包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
具体类:
package cglib;
import jdk.ActorInerface;
public class MovieActor implements ActorInerface {
@Override
public void play() {
System.out.println("一个演员正在表演");
}
}
代理类:
package cglib;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private MovieActor target;
public CglibProxy() {
this.target=new MovieActor();
}
//创建代理对象
public Object getProxyInstance(){
Enhancer en = new Enhancer();//工具类
en.setSuperclass(target.getClass());//设置父类
en.setCallback(this);//设置回调函数
return en.create();//设置子类
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("合作洽谈,签订合同");
Object invoke = method.invoke(target, objects);
System.out.println("履行合同收款!");
return invoke;
}
}
测试类:
package cglib;
public class Test {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
MovieActor proxyInstance = (MovieActor)cglibProxy.getProxyInstance();
proxyInstance.play();
}
}
//运行结果:
//合作洽谈,签订合同
//一个演员正在表演
//履行合同收款!
模拟切面
主业务接口
package aop;
public interface IActorService {
void play();
void run();
}
主业务实现
package aop;
public class ActorServiceImpl implements IActorService {
@Override
public void play() {
System.out.println("演员正在表演!");
}
@Override
public void run() {
System.out.println("去感受生活");
}
}
aop基础类
package aop;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class BaseAspect implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object res = null;
//开始增强
begin();
try {
if(isIntercept(method,args)){//判断是否需要增强
//前置增强
before();
res = methodProxy.invokeSuper(o, args);
//后置增强
after();
}else{
res = methodProxy.invokeSuper(o, args);
}
} catch (Exception e) {
//异常增强
error(e);
throw e;
}finally {
//最终增强
end();
}
return res;
}
//开始增强
public void begin(){
}
//前置增强
public void before(){}
//后置增强
public void after(){}
//最终增强
public void end(){}
//异常增强
public void error(Throwable e){}
//根据参数判断方法是否需要增强,默认true
public boolean isIntercept(Method method,Object[] args){
return true;
}
}
切面类
package aop;
import java.lang.reflect.Method;
public class ActorAspect extends BaseAspect {
@Override
public boolean isIntercept(Method method, Object[] args) {
return method.getName().equals("play");
}
@Override
public void before() {
System.out.println("表演前准备");
}
@Override
public void after() {
System.out.println("表演后吃大餐");
}
}
创建代理对象
package aop;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
public class ProxyFactory {
//创建代理对象
public static <T> T creatProxy(final Class<?> targetClass, final MethodInterceptor methodInterceptor){
return (T) Enhancer.create(targetClass,methodInterceptor);
}
}
测试类:
package aop;
public class Test {
public static void main(String[] args) {
//创建目标对象
IActorService target = new ActorServiceImpl();
//创建切面对象
BaseAspect accountAspect = new ActorAspect();
//创建代理对象
IActorService proxy = ProxyFactory.creatProxy(target.getClass(), accountAspect);
proxy.play();
proxy.run();
}
}
//运行结果:
//表演前准备
//演员正在表演!
//表演后吃大餐
//去感受生活
3、自动代理实现AOP
使用Spring实现aop,需要在头文件中引入aop的命名空间:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
pom.xml中引入包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
前置增强
主业务
package booy;
public class Actor {
public void play(){
System.out.println("演员正在表演");
}
}
前置增强
package aop;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeService implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("开始前置增强");
System.out.println(o+"的"+method.getName()+"方法");
System.out.println("前置增强结束");
}
}
测试类:
import booy.Actor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Actor bean = (Actor)applicationContext.getBean("actor");
bean.play();
}
}
/*运行结果
开始前置增强
booy.Actor@2ddc8ecb的play方法
前置增强结束
演员正在表演
*/
配置文件spring.xml
<bean id="beforeService" class="aop.BeforeService" />
<bean id="actor" class="booy.Actor" />
<aop:config>
<!--确定需要增强的切入点-->
<aop:pointcut id="point" expression=" execution(* booy..*(..))"/>
<!--增强的切入点增强的类型配置-->
<aop:advisor advice-ref="beforeService" pointcut-ref="point"/>
</aop:config>
后置增强
主业务
package booy;
public class Actor {
public void play(){
System.out.println("演员正在表演");
}
}
后置增强
package aop;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterService implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("====我是后置增强====");
}
}
配置文件spring.xml
<bean id="afterService" class="aop.AfterService" />
<bean id="actor" class="booy.Actor" />
<aop:config>
<!--确定需要增强的切入点-->
<aop:pointcut id="point" expression=" execution(* booy..*(..))"/>
<!--增强的切入点增强的类型配置-->
<aop:advisor advice-ref="afterService" pointcut-ref="point"/>
</aop:config>
测试类:
import booy.Actor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Actor bean = (Actor)applicationContext.getBean("actor");
bean.play();
}
}
/*运行结果
演员正在表演
====我是后置增强====
*/
异常抛出增强
主业务
package booy;
public class Actor {
public void play(){
System.out.println("演员正在表演");
}
}
异常抛出增强
package aop;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class ExceptionService implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args,
Object target, Exception e) {
System.out.println("------异常抛出增强Start------"); System.out.println(target + "的" + method.getName()
+ "方法发生异常:" + e.getMessage());
System.out.println("------异常抛出增强End--------"); }
}
配置文件spring.xml
<bean id="beforeService" class="aop.BeforeService" />
<bean id="afterService" class="aop.AfterService" />
<bean id="exceptionService" class="aop.ExceptionService" />
<bean id="actor" class="booy.Actor" />
<aop:config>
<!--确定需要增强的切入点-->
<aop:pointcut id="point" expression=" execution(* booy..*(..))"/>
<!--增强的切入点增强的类型配置-->
<aop:advisor advice-ref="afterService" pointcut-ref="point"/>
<aop:advisor advice-ref="exceptionService" pointcut-ref="point"/>
</aop:config>
测试类:
import booy.Actor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Actor bean = (Actor)applicationContext.getBean("actor");
try {
bean.play("aa");
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*运行结果:
aa演员正在表演
------异常抛出增强Start------
booy.Actor@1ffe63b9的play方法发生异常:手动异常
------异常抛出增强End--------
java.lang.Exception: 手动异常
at booy.Actor.play(Actor.java:9)
......
*/
环绕增强
主业务
package booy;
public class Actor {
public void play(){
System.out.println("演员正在表演");
}
}
环绕增强
package aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
public class AroundService implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//被代理的对象
Object target = methodInvocation.getThis();
//被代理对象的方法
Method method = methodInvocation.getMethod();
Object[] arguments = methodInvocation.getArguments();
try {
System.out.println("=====前置增强=====");
Object proceed = methodInvocation.proceed();
System.out.println(target+"的"+method.getName()+"方法");
System.out.println("=====后置增强=====");
return proceed;
} catch (Exception e) {
System.out.println("异常增强");
throw e;
} finally {
System.out.println("finally");
}
}
}
配置文件spring.xml
<bean id="beforeService" class="aop.BeforeService" />
<bean id="afterService" class="aop.AfterService" />
<bean id="exceptionService" class="aop.ExceptionService" />
<bean id="aroundService" class="aop.AroundService"/>
<bean id="actor" class="booy.Actor" />
<aop:config>
<!--确定需要增强的切入点-->
<aop:pointcut id="point" expression=" execution(* booy..*(..))"/>
<!--增强的切入点增强的类型配置-->
<aop:advisor advice-ref="aroundService" pointcut-ref="point"/>
</aop:config>
测试类
import booy.Actor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Actor bean = (Actor)applicationContext.getBean("actor");
bean.play();
}
}
/*运行结果:
=====前置增强=====
演员正在表演
booy.Actor@7c469c48的play方法
=====后置增强=====
finally
*/
最终增强
增强类同时实现后置增强和异常抛出增强。
引介增强
引介增强是类级别的增强,可以给目标类增加方法和属性。
主业务
package booy;
public class Actor {
public void play(){
System.out.println("演员正在表演");
}
}
普通javaBean
package aop;
public class MyAdvice {
public void test(){
System.out.println("这个一个普通javaBean");
}
}
配置文件spring.xml
<bean id="actor" class="booy.Actor" />
<bean class="aop.MyAdvice" id="advice"/>
<aop:config>
<!--确定需要增强的切入点-->
<aop:pointcut id="point" expression=" execution(* booy..*(..))"/>
<!--增强的切入点增强的类型配置-->
<aop:aspect ref="advice">
<aop:before method="test" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
测试类:
import booy.Actor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Actor bean = (Actor)applicationContext.getBean("actor");
bean.play();
}
}
/*运行结果:
这个一个普通javaBean
演员正在表演
*/
4、execution表达式
execution(* com.sample.service.impl.. . (..))
解释如下:
execution()
:表达式的主体;
第一个*符号:表示返回值的类型是任意类型
com.sample.service.impl
:AOP所切的服务的包名,即我们的业务部分 包名后面的…:表示当前包及子包
第二个 :表示类名,即所有类。此处可以自定义,下文有举例
.*(..)
:表示任何方法名,括号表示参数,两个点表示任何参数类型
基本语法格式:
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常 模式>?)
除了返回类型模式、方法名模式和参数模式外,其它项都是可选 的。
1)、通过方法签名定义切点
execution(public * * (..))
匹配所有目标类的public方法,第一个*
代表返回类型,第二个*
代表方法 名,而..
代表任意入参的方法;
execution(* *To(..))
匹配目标类所有以To为后缀的方法。第一个*
代表返回类型,而To代表任意 以To为后缀的方法;
2)、通过类定义切点
execution( com.baobaotao.Waiter. (..))
匹配Waiter接口的所有方法。第一个 代表返回任意类型,
com.baobaotao.Waiter.
代表Waiter接口中的所有方法;
execution( com.baobaotao.Waiter+. (..))
匹配Waiter接口及其所有实现类的方法
3)、通过类包定义切点
在类名模式串中,“.
”表示包下的所有类,而“..
”表示包、子孙包下的所有类。
execution(* com.baobaotao.*(..))
匹配com.baobaotao包下所有类的所有方法;
execution(* com.baobaotao..*(..))
匹配com.baobaotao包、子孙包下所有类的所有方法,如 com.baobaotao.dao,com.baobaotao.servier以及com.baobaotao.dao.user包下的所有类的所有方法都匹配。“..
”出现在类名中时,后面必须跟“*
”,表示 包、子孙包下的所有类;
execution(* com.. . Dao.find*(..))
匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find 为前缀。如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的方法都匹配切点。
4)、通过方法入参定义切点
切点表达式中方法入参部分比较复杂,可以使用“ ”和“..
”通配符,其中“ ”表示任意类型的参数,而“..
”表示任意类型参数且参数个数不限。
execution(* joke(String,int))
匹配joke(String,int)方法,且joke()方法的第一个入参是String,第二个入参 是int。joke(String,int)方法。如果方法中的入参类型是java.lang包下的类, 可以直接使用类名,否则必须使用全限定类名,如joke(java.util.List,int);
execution(* joke(String,*))
匹配目标类中的joke()方法,该方法第一个入参为String,第二个入参可以 是任意类型,如joke(Strings1,String s2)和joke(String s1,double d2)都匹配, 但joke(String s1,doubled2,String s3)则不匹配;
execution(* joke(String,..))
匹配目标类中的joke()方法,该方法第 一个入参为String,后面可以有任意 个入参且入参类型不限,如joke(Strings1)、joke(String s1,String s2)和 joke(String s1,double d2,Strings3)都匹配。
execution(* joke(Object+))
匹配目标类中的joke()方法,方法拥有一个入参,且入参是Object类型或该 类的子类。它匹配joke(Strings1)和joke(Client c)。如果我们定义的切点是 execution(*joke(Object))
,则只匹配joke(Object object)而不匹配joke(String cc)或joke(Client c)。
5、AspectJ实现AOP
主业务类
package aspectj;
public class AspectjAdviceTarget {
//测试方法,用于测试前置增强、后置增强、环绕增强、最终增强
public String testAdvice(String param1, Integer param2) {
System.out.println("我是测试类的方法,参数是param1="
+ param1 + ";param2=" + param2);
return "测试类返回";
}
//测试方法,用于测试异常抛出增强
public void testException() throws Exception {
throw new Exception("我是测试故意抛出的异常");
}
}
切面增强类
package aspectj;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
//@Aspect作用是把当前类标识为一个切面供容器读取
@Aspect
public class AspectjAdvice {
//定义切入点表达式
@Pointcut("execution(* aspectj.AspectjAdviceTarget.testAdvice(..))")
public void myPoint() {
}
@Before("myPoint()")
public void before(JoinPoint point) {
System.out.println("------前置增强Start--------");
System.out.println("连接点对象:" +
point.getTarget().getClass());
System.out.println("连接点方法:" +
point.getSignature());
System.out.println("连接点参数:" +
Arrays.toString(point.getArgs()));
System.out.println("------前置增强End--------");
}
@AfterReturning(pointcut = "myPoint()", returning =
"returnVal")
public void afterReturning(Object returnVal) {
System.out.println("--------后置增强Start---------");
System.out.println("返回值是" + returnVal);
System.out.println("--------后置增强End-----------");
}
//异常抛出增强
@AfterThrowing(pointcut = "execution(* aspectj.AspectjAdviceTarget.testException(..))", throwing = " ex")
public void afterThrowing(Exception ex){
System.out.println("------异常抛出增强Start--------");System.out.println("方法发生异常:"+ex.getMessage());System.out.println("------异常抛出增强End--------");
}
//最终增强,相当于后置增强和抛出增强的结合
@After("myPoint()")
public void after(){
System.out.println("------After--------");
}
@Around("myPoint()")
public void around(ProceedingJoinPoint joinPoint){
try{
System.out.println("------环绕增强Start------");System.out.println("连接点对象:"+
joinPoint.getTarget().getClass());
System.out.println("连接点方法:"+
joinPoint.getSignature());
System.out.println("连接点参数:"+
Arrays.toString(joinPoint.getArgs()));
joinPoint.proceed();
System.out.println("------环绕增强End------");
}catch(Throwable throwable){
System.out.println("------环绕增强捕获异常------");throwable.printStackTrace();
}
}
}
配置文件spring-aop.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">
<aop:aspectj-autoproxy/>
<bean id="aspectjAdviceTarget" class="aspectj.AspectjAdviceTarget"/>
<bean id="aspectjAdvice" class="aspectj.AspectjAdvice"/>
</beans>
测试类
package aspectj;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AspectjAdviceTest {
//前置增强、后置增强、环绕增强、最终增强都可以用这个方法测试
@Test
public void testBefore() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");
AspectjAdviceTarget target = (AspectjAdviceTarget)applicationContext.getBean("aspectjAdviceTarget");
target.testAdvice("a", 0);
}
// 测试异常抛出增强
@Test
public void testException() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aop.xml");
AspectjAdviceTarget target = (AspectjAdviceTarget)applicationContext.getBean("aspectjAdviceTarget");
try {
target.testException();
} catch (Exception e) {
e.printStackTrace();
}
}
}