什么是spring
是一款轻量级开发框架,为了解决JAVAEE企业应用开发的复杂性,整合了现有的技术框架。
优点及缺点
优点:
- 轻量级框架,非浸入式框架(引入框架对其他代码无影响);
- **控制反转(IOC)**——<font style="color:rgb(44, 62, 80);">指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架。</font>;
- **依赖注入(DI)**——<font style="color:rgb(44, 62, 80);">指依赖的对象不需要手动调用 setXX 方法去设置,而是通过配置赋值。</font>
- 方便进行面向 **切面编程(AOP)**;
- 可以选择spring中的某部分功能或全部进行使用;
- 支持事务管理,对所有框架进行整合。
缺点:
- 由于整合所有框架,导致配置过于繁琐。
核心思想
控制反转(IOC)、依赖注入(DI)、面向切面编程(AOP)
maven地址
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>spring七大模块

Spring Core
核心容器提供 Spring 框架的基本功能。核心容器的主要组件是由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,它是工厂模式的实现。BeanFactory 使用_控制反转_(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- **bean模块**:提供框架的基础部分,包括控制反转和依赖注入;
- **core模块**:封装spring框架的底层部分,包括资源访问和类型转换以及工具类;
- **<font style="color:rgb(44, 62, 80);">Context 上下文模块</font>**<font style="color:rgb(44, 62, 80);">:</font>建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点。
- **SpEL 模块**:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。
Spring DAO
JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
Spring ORM
Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring AOP
通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
Spring Web
Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
Spring Web MVC
MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
IOC理论
控制反转:是一种设计思想,DI是实现IOC的一种方式,将主动创建对象的权限交友第三方控制,即为控制反转(对象由Spring来创建,管理,装配)。

控制反转(IOC)和依赖注入(DI)
IOC控制反转
Spring框架管理这些Bean的创建工作,即由用户管理Bean转变为框架管理Bean,这个就叫控制反转 - Inversion of Control (IoC)
IOC是是面相对象编程中的一种设计原则,主要是为了降低系统代码的耦合度,让系统利于维护和扩展。
spring ioc容器是如何知道需要管理哪些对象呢?
需要我们给ioc容器提供一个配置清单,这个配置支持xml格式和java注解的方式,在配置文件中列出需要让ioc容器管理的对象,以及可以指定让ioc容器如何构建这些对象,当spring容器启动的时候,就会去加载这个配置文件,然后将这些对象给组装好以供外部访问者使用。这里所说的IOC容器也叫spring容器。、
Bean概念
由spring容器管理的对象统称为Bean对象。Bean就是普通的java对象,和我们自己new的对象其实是一样的,只是这些对象是由spring去创建和管理的,我们需要在配置文件中告诉spring容器需要创建哪些bean对象,所以需要先在配置文件中定义好需要创建的bean对象,这些配置统称为bean定义配置元数据信息,spring容器通过读取这些bean配置元数据信息来构建和组装我们需要的对象。
DI依赖注入
依赖注入是spring容器中创建对象时给其设置依赖对象的方式,比如给spring一个清单,清单中列出了需要创建B对象以及其他的一些对象(可能包含了B类型中需要依赖对象),此时spring在创建B对象的时候,会看B对象需要依赖于哪些对象,然后去查找一下清单中有没有包含这些被依赖的对象,如果有就去将其创建好,然后将其传递给B对象;可能B需要依赖于很多对象,B创建之前完全不需要知道其他对象是否存在或者其他对象在哪里以及被他们是如何创建,而spring容器会将B依赖对象主动创建好并将其注入到B中去。
Spring中常见的接口及对象
BeanFactory接口
spring容器的顶层接口,提供了容器最基本的功能。
//按bean的id或者别名查找容器中的bean
Object getBean(String name) throws BeansException
//这个是一个泛型方法,按照bean的id或者别名查找指定类型的bean,返回指定类型的bean对象
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
//返回容器中指定类型的bean对象
<T> T getBean(Class<T> requiredType) throws BeansException;
//获取指定类型bean对象的获取器
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);ApplicationContext接口
继承了BeanFactory接口,所以内部包含了BeanFactory所有的功能,并且在其上进行了扩展,增加了很多企业级功能,比如AOP、国际化、事件支持等等
ClassPathXmlApplicationContext类
实现了ApplicationContext接口,这个容器类可以从classpath中加载bean xml配置文件,然后创建xml中配置的bean对象。
AnnotationConfigApplicationContext类
实现了ApplicationContext接口,当使用注解方式方式定义bena时,可以使用此方法从容器内部会解析注解来构建构建和管理需要的bean。
XML配置
使用XML配置需要先创建BEAN对象,然后创建bean.xml文件,在XML中配置bean相关的参数,提供给spring,spring在启动时读取XML文件,根据文件中javabean配置查找对应的数据。
<?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-4.3.xsd">
<import resource="引入其他bean xml配置文件" />
<bean id="bean标识" class="玩转类型名称"/>
<alias name="bean标识" alias="别名" />
</beans>beans是根元素,下面可以包含任意数量的import、bean、alias元素,下面我们对每个元素进行详解。
XML属性
Bean元素
<bean id="bean唯一标识" name="bean名称" class="完整类型名称" factory-bean="工厂bean名称" factory-method="工厂方法"/>bean名称
在spring中,每个bean都要有唯一的名称,否则会报错,通过bean名称可以从容器中找到bena对象。
bean别名名称定义规则
- 当id存在的时候,不管name有没有,取id为bean的名称;
- 当id不存在,此时需要看name,name的值可以通过 ,;或者空格 分割,最后会按照分隔符得到一个String数组,数组的第一个元素作为bean的名称,其他的作为bean的别名;
- 当id和name都存在的时候,id为bean名称,name用来定义多个别名;
- 当id和name都不指定的时候,bean名称自动生成,生成规则下面详细。
<!-- 通过id定义bean名称:user1 --><bean id="user1" class="com.javacode2018.lesson001.demo2.UserModel"/>
<!-- 通过name定义bean名称:user2 --><bean name="user2" class="com.javacode2018.lesson001.demo2.UserModel"/>
<!-- id为名称,name为别名;bean名称:user3,1个别名:[user3_1] -->
<bean id="user3" name="user3_1"class="com.javacode2018.lesson001.demo2.UserModel"/>
<!-- bean名称:user4,多个别名:[user4_1,user4_2,user4_3,user4_4] -->
<bean id="user4" name="user4_1,user4_2;user4_3 user4_4"class="com.javacode2018.lesson001.demo2.UserModel"/>
<!-- bean名称:user5,别名:[user5_1,user5_2,user5_3,user5_4] -->
<bean name="user5,user5_1,user5_2;user5_3 user5_4"class="com.javacode2018.lesson001.demo2.UserModel"id和name都未绑定
当id和name都未绑定时,由spring自动生成bena名称,为bean的class属性的完整类名#编号;
com.zd.demo2.UserModel#0alias元素
alias元素用来给bena定义别名
<alias name = '需要别名的bena' alias = '别名'>import元素
将其他xml配置引入到本xml文件中,可以访问本文件读取到其他文件的配置;
<import resource = '其他文件的位置'/>创建bean实例
通过反射的构造方法创建bean对象
在类的构造方法中,通过参数初始化对象。在xml配置中,可通过构造方法的索引获取。
<bean id="bean名称" name="bean名称或者别名" class="bean的完整类型名称">
<constructor-arg index="0" value="bean的值" ref="引用的bean名称" />
<constructor-arg index="1" value="bean的值" ref="引用的bean名称" />
<constructor-arg index="2" value="bean的值" ref="引用的bean名称" />
<constructor-arg index="n" value="bean的值" ref="引用的bean名称" />
</bean>通过静态工厂方法创建bean对象
通过对象中的静态方法实例化对象;
<bean id="bean名称" name="" class="静态工厂完整类名" factory-method="静态工厂的方法">
<constructor-arg index="0" value="bean的值" ref="引用的bean名称" />
<constructor-arg index="1" value="bean的值" ref="引用的bean名称" />
<constructor-arg index="2" value="bean的值" ref="引用的bean名称" />
<constructor-arg index="n" value="bean的值" ref="引用的bean名称" />
</bean>/**
* 静态有参方法创建UserModel
*
* @param name 名称
* @param age 年龄
* @return
*/
public static UserModel buildUser2(String name, int age) {
System.out.println(UserStaticFactory.class + ".buildUser2()");
UserModel userModel = new UserModel();
userModel.setName(name);
userModel.setAge(age);
return userModel;
}通过实例工厂方法创建bean对象
spring容器去调用某些对象中的非静态方法生成bena对象饼放在容器中。
<bean id="bean名称" factory-bean="需要调用的实例对象bean名称" factory-method="bean对
象中的方法">
<constructor-arg index="0" value="bean的值" ref="引用的bean名称" />
<constructor-arg index="1" value="bean的值" ref="引用的bean名称" />
<constructor-arg index="2" value="bean的值" ref="引用的bean名称" />
<constructor-arg index="n" value="bean的值" ref="引用的bean名称" />
</bean>public UserModel buildUser1() {
System.out.println("----------------------1");
UserModel userModel = new UserModel();
userModel.setName("bean实例方法创建的对象!");
return userModel;
}通过FactoryBean来创建bean对象
FactoryBean可以让spring容器通过这个接口的实现来创建我们需要的bean对象,FactoryBean接口中有3个方法,前面2个方法需要我们去实现,getObject方法内部由开发者自己去实现对象的创建,然后将创建好的对象返回给Spring容器,getObjectType需要指定我们创建的bean的类型;最后一个方法isSingleton表示通过这个接口创建的对象是否是单例的,如果返回false,那么每次从容器中获取对象的时候都会调用这个接口的getObject() 去生成bean对象。
<bean id = "bean名称" class="FactoryBean接口实现类">public class UserFactoryBean implements FactoryBean<User> {
int count = 1;
@Nullable
@Override
public User getObject() throws Exception {
User user = new User().setAge(12).setName("李四");
System.out.println("通过FactoryBean创建对象"+user);
return user;
}
@NotNull
@Override
public Class<?> getObjectType() {
return User.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
@Test
public void test2(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml");
System.out.println("spring容器中所有bean如下");
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name+":"+context.getBean(name));
}
}
//输出:
//通过FactoryBean创建对象User{name='李四', age=12}
//creatByFactoryBean:User{name='李四', age=12}bean作用域scope详解
在spring中,可通过配置修改某个bean的生命周期,如自始至终只有getBean返回的都是同一个对象或每次调用获取bean时都返回新的对象。
<!-- 单例 -->
<bean id="user" class="com.zd.model.User" scope="singleton"/>
<!-- 多例 -->
<bean id="user" class="com.zd.model.User" scope="prototype"/>@Test
public void test3(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml");
User user = (User) context.getBean("user");
System.out.println(user);
user = (User) context.getBean("user");
System.out.println(user);
}singleton和prototype
单例bean使用注意
单例bean是整个应用共享的,所以需要考虑到线程安全问题,在springmvc中controller默认是单例的,有些开发者在controller中创建了一些变量,那么这些变量实际上就变成共享的了,controller可能会被很多线程同时访问,这些线程并发去修改controller中的共享变量,可能会出现数据错乱的问题;所以使用的时候需要特别注意。单例模式会存在线程安全的问题,可以使用synchronize锁解决。
多例bean使用注意
多例bean每次获取的时候都会重新创建,如果这个bean比较复杂,创建时间比较长,会影响系统的性能,这个地方需要注意。
request、session、application都是在spring web容器环境中才会有的
request
当一个bean的作用域为request,表示在一次http请求中,一个bean对应一个实例;对每个http请求都会创建一个bean实例,request结束的时候,这个bean也就结束了,request作用域用在spring容器的web环境中,这个以后讲springmvc的时候会说,spring中有个web容器接口WebApplicationContext,这个里面对request作用域提供了支持。
<bean id="user" class="com.zd.model.User" scope="request"/>session
这个和request类似,也是用在web环境中,session级别共享的bean,每个会话会对应一个bean实例,不同的session对应不同的bean实例
<bean id="user" class="com.zd.model.User" scope="session"/>application
全局web应用级别的作用于,也是在web环境中使用的,一个web应用程序对应一个bean实例,通常情况下和singleton效果类似的,不过也有不一样的地方,singleton是每个spring容器中只有一个bean实例,一般我们的程序只有一个spring容器,但是,一个应用程序中可以创建多个spring容器,不同的容器中可以存在同名的bean,但是sope=aplication的时候,不管应用中有多少个spring容器,这个应用中同名的bean只有一个。
<bean id="user" class="com.zd.model.User" scope="application"/>自定义scope
第1步:实现Scope
/**
* 自定义注解: 线程级别的bean作用域,同一个线程中同名的bean是同一个实例,不同的线程中的bean是不同的实例。
*/
public class ThreadScope implements Scope {
//定义了作用域的名称为一个常量thread,可以在定义bean的时候给scope使用
public static final String THREAD_SCOPE = "thread";
private ThreadLocal<Map<String, Object>> beanMap = new ThreadLocal() {
@Override
protected Object initialValue() {
return new HashMap<>();
}
};
/**
* 返回当前作用域中name对应的bean对象
* name:需要检索的bean的名称
* objectFactory:如果name对应的bean在当前作用域中没有找到,那么可以调用这个ObjectFactory来创建这个对象
**/
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Object bean = beanMap.get().get(name);
if (Objects.isNull(bean)) {
bean = objectFactory.getObject();
beanMap.get().put(name, bean);
}
return bean;
}
/**
* 将name对应的bean从当前作用域中移除
**/
@Nullable
@Override
public Object remove(String name) {
return this.beanMap.get().remove(name);
}
/**
* 用于注册销毁回调,如果想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象
*/
@Override
public void registerDestructionCallback(String name, Runnable runnable) {
//bean作用域范围结束的时候调用的方法,用于bean清理
System.out.println(name);
}
/**
* 用于解析相应的上下文数据,比如request作用域将返回request中的属性。
*/
@Nullable
@Override
public Object resolveContextualObject(String key) {
return null;
}
/**
* 作用域的会话标识,比如session作用域将是sessionId
*/
@Nullable
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
//测试对象
public class BeanScopeModel {
public BeanScopeModel(String beanScope) {
System.out.println(String.format("线程:%s,create BeanScopeModel,{sope=%s},{this=%s}", Thread.currentThread(), beanScope, this));
}
}第2步:将自定义的scope注入到容器
需要调用org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope的方法注入
/**
* 向容器中注册自定义的Scope
*scopeName:作用域名称
* scope:作用域对象
**/
void registerScope(String scopeName, Scope scope);第3步:使用自定义的作用域
<?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-4.3.xsd">
<!-- 自定义scope的bean -->
<bean id="threadBean" class="com.javacode2018.lesson001.demo4.BeanScopeModel" scope="thread">
<constructor-arg index="0" value="thread"/>
</bean>
</beans>测试
public class ThreadScopeCilent {
public static void main(String[] args) throws InterruptedException {
String beanXml = "classpath:/org/spring/lession1/threadDemo.xml";
//手动创建容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext() {
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
//向容器中注册自定义的scope
beanFactory.registerScope(ThreadScope.THREAD_SCOPE, new ThreadScope());//@1
super.postProcessBeanFactory(beanFactory);
}
};
//设置配置文件位置
context.setConfigLocation(beanXml);
//启动容器 此为步骤2将自定义scope注入容器
context.refresh();
//使用容器获取bean
for (int i = 0; i < 2; i++) {//@2
new Thread(() -> {
System.out.println(Thread.currentThread() + "," + context.getBean("threadBean"));
System.out.println(Thread.currentThread() + "," + context.getBean("threadBean"));
}).start();
TimeUnit.SECONDS.sleep(1);
}
}
}依赖注入之手动注入
java代码注入
将被依赖方注入到依赖方,通常有2种方式:构造函数的方式和set属性的方式
通过构造方法注入
public class UserService {
public void insert(){
System.out.println("新增方法!");
}
}
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
public void insert(){
userService.insert();
}
}
@Test
public void demo32(){
UserService userService = new UserService();
UserController userController = new UserController(userService);
userController.insert();
}通过set方法注入
public class UserService {
public void insert(){
System.out.println("新增方法!");
}
}
public class UserController {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void insert(){
userService.insert();
}
}
@Test
public void demo32(){
UserService userService = new UserService();
UserController userController = new UserController();
userController.setUserService(userService);
userController.insert();
}spring依赖注入
通过构造器注入
构造器对应的是实体类中的构造方法,构造器注入方式有3种:
public class SpringUser {
/**
* 姓名
*/
private String name;
/**
* 地址
*/
private String address;
/**
* 年龄
*/
private int age;
public SpringUser(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public SpringUser() {
}
@Override
public String toString() {
return "SpringUser{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", age=" + age +
'}';
}
}
根据构造参数索引注入
参数位置的注入对参数顺序有很强的依赖性,若构造函数参数位置被人调整过,会导致注入出错。如果需要新增参数的,可以新增一个构造函数来实现,这算是一种扩展,不会影响目前已有的功能。
<bean id="springUserByIndex" class="com.spring.model.SpringUser">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="渭南"/>
<constructor-arg index="2" value="12"/>
</bean>根据构造参数类型注入
<bean id="springUserByType" class="com.spring.model.SpringUser">
<constructor-arg type="java.lang.String" value="李四"/>
<constructor-arg type="java.lang.String" value="西安"/>
<constructor-arg type="int" value="13"/>
</bean>根据构造参数名称注入
java通过反射的方式可以获取到方法的参数名称,不过源码中的参数通过编译之后会变成class对象,通常情况下源码变成class文件之后,参数的真实名称会丢失,spring提供了解决方案,通过ConstructorProperties注解来定义参数的名称,将这个注解加在构造方法上面。
@Data
@ToString
public class User {
private String name;
private Integer age;
private String address;
//解决依赖注入按名称注入,编译代码导致class文件中参数名称丢失情况
@ConstructorProperties({"name", "age", "address"})
public User(String name, Integer age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
}<bean id="springUserByName" class="com.spring.model.SpringUser">
<constructor-arg name="name" value="王五"/>
<constructor-arg name="address" value="安康"/>
<constructor-arg name="age" value="14"/>
</bean>set注入
通常情况下。我们类的标准是javabean,javabean类的特点:
- 属性都是private访问级别的
- 属性通常情况下通过一组setter和getter方法来访问
- set方法,以set开头,后跟首字母大写的属性名,如:setName
- get方法,一般以get开头,对于boolean类型一般以is开头,后跟首字母大写的属性名,如:getName,isOk;
spring对符合javabean特点类,提供setter方法的注入,会调用对应的set方法将依赖的对象注入。
注入常用类型对象:
<bean id="xmlSetModel" class="com.zd.model.XmlSetModel">
<property name="name" value="张三"/>
<property name="address" value="西安"/>
<property name="check" value="true"/>
</bean>public class XmlSetModel {
private String name;
private String address;
private Boolean check;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Boolean getCheck() {
return check;
}
public void setCheck(Boolean check) {
this.check = check;
}
@Override
public String toString() {
return "XmlSetModel{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", check=" + check +
'}';
}
}
@Test
public void test4(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml");
XmlSetModel xmlSetModel = (XmlSetModel)context.getBean("xmlSetModel");
System.out.println(xmlSetModel);
}优缺点
setter注入相对于构造函数注入要灵活一些,构造函数需要指定对应构造函数中所有参数的值,而setter注入的方式没有这种限制,不需要对所有属性都进行注入,可以按需进行注入。
注入容器中的bean
ref属性方式
ref属性的值为容器中其他bean的名称
构造器方式,将value替换为ref:
<constructor-arg ref="需要注入的bean的名称"/>
setter方式,将value替换为ref:
<property name="属性名称" ref="需要注入的bean的名称" /><bean id="xmlSetModel" class="com.zd.model.XmlSetOftenModel">
<property name="name" value="张三"/>
<property name="address" value="西安"/>
<property name="check" value="true"/>
</bean>
<bean id="xmlSetCustomModel" class="com.zd.model.XmlSetCustomModel">
<property name="xmlSetOftenModel" ref="xmlSetModel"/>
</bean>
<bean id="xmlSetCustomModel1" class="com.zd.model.XmlSetCustomModel">
<constructor-arg ref="xmlSetModel"/>
</bean>public class XmlSetCustomModel {
private XmlSetOftenModel xmlSetOftenModel;
private XmlSetCustomModel XmlSetCustomModel(){
}
private XmlSetCustomModel XmlSetCustomModel(XmlSetOftenModel xmlSetOftenModel){
this.xmlSetOftenModel = xmlSetOftenModel;
}
public XmlSetOftenModel getXmlSetOftenModel() {
return xmlSetOftenModel;
}
public void setXmlSetOftenModel(XmlSetOftenModel xmlSetOftenModel) {
this.xmlSetOftenModel = xmlSetOftenModel;
}
@Override
public String toString() {
return "XmlSetCustomModel{" +
"xmlSetOftenModel=" + xmlSetOftenModel +
'}';
}
}
@Test
public void test5() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml");
XmlSetCustomModel xmlSetCustomModel = (XmlSetCustomModel) context.getBean("xmlSetCustomModel");
System.out.println(xmlSetCustomModel);
}内置bean的方式
构造器的方式:
<constructor-arg>
<bean class=""/>
</constructor-arg>
setter方式:
<property name="属性名称">
<bean class=""/>
</property>
<bean id="refUser" class="org.spring.model.OtherBeanModel">
<!--构造器方式注入-->
<constructor-arg>
<bean class="org.spring.model.User">
<property name="name" value="张东"/>
<property name="age" value="23"/>
<property name="address" value="ref构造器方式注入自定义类"/>
</bean>
</constructor-arg>
</bean>
<bean id="refUser1" class="org.spring.model.OtherBeanModel">
<!--set方式注入-->
<property name="user">
<bean class="org.spring.model.User">
<property name="name" value="张东"/>
<property name="age" value="23"/>
<property name="address" value="ref set方式注入自定义类"/>
</bean>
</property>
</bean>其他类型的注入
注入list元素
注入set元素
注入map元素
注入数组array元素
注入java.util.Properties(props元素)
<bean id="xmlSetOtherModel" class="com.zd.model.XmlSetOtherModel">
<!--注入list-->
<property name="list">
<list>
<value>spring</value>
<value>springboot</value>
</list>
</property>
<!--注入set-->
<property name="set">
<set>
<ref bean="user"></ref>
</set>
</property>
<!--注入map对象-->
<property name="map">
<map>
<entry key="zd" value="20"/>
<entry key="mm" value="26"/>
</map>
</property>
<!--注入数组-->
<property name="array">
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
<!-- 注入java.util.Properties对象 -->
<property name="properties">
<props>
<prop key="name">root</prop>
<prop key="password">admin</prop>
</props>
</property>
</bean>拓展注入方式
使用P命名和C命名注入时需要在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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--spring容器注入-->
</beans>P命名注入
P命名,可以通过set属性进行注入;
C命名注入
C命名,可以通过有参构造函数进行注入;
依赖注入之自动(autowrite)注入
手动注入的不足
- 如果类中依赖对象较多,比如A类中有几十个属性,那么上面property属行就需要几十种;
- 如果A类中新增或删除了一些依赖,需要手动调整XML中的依赖配置信息,否则会报错;
- 不利于拓展和维护
为了解决以上问题,spring提供了更强大的功能:自动注入
Class.isAssignableFrom方法
用来判断两个类是否相等或是否属于子类
@Test
public void isAssignableFrom(){
System.out.println(Object.class.isAssignableFrom(Integer.class)); //true
System.out.println(Object.class.isAssignableFrom(int.class)); //false
System.out.println(Object.class.isAssignableFrom(List.class)); //true
System.out.println(Collection.class.isAssignableFrom(List.class)); //true
System.out.println(List.class.isAssignableFrom(Collection.class)); //false
}自动注入(autowrite)
自动注入是采用约定大于配置的方式实现的,程序和spring容器之间约定好,遵守某一种都认同的规则,来实现自动注入。
- byteName:按照名称进行注入
- byType:按类型进行
- constructor:按照构造方法进行注入
- default:默认注入方式
按照名称进行注入(byName)
按名称进行注入的时候,要求名称和set属性的名称必须相同,节约了手动引入的代码;
若autowrite属性设置为按名称注入后,同时添加了手动引入,则手动引入的优先级高。
<!--自动注入-->
<bean id="user" class="com.zd.model.User">
<property name="name" value="张三"/>
<property name="age" value="12"/>
</bean>
<bean id="user1" class="com.zd.model.User">
<property name="name" value="张三"/>
<property name="age" value="13"/>
</bean>
<bean id="desc" class="java.lang.String"/>
<bean id="xmlAutoByNameModel" class="com.zd.model.XmlAutoByNameModel" autowire="byName"/>
<!-- 当配置了自动注入,还可以使用手动的方式自动注入进行覆盖,手动的优先级更高一些 -->
<bean id="xmlAutoByNameModel1" class="com.zd.model.XmlAutoByNameModel" autowire="byName">
<property name="user" ref="user1"/>
</bean>public class XmlAutoByNameModel {
private String desc;
private User user;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "XmlAutoByNameModel{" +
"desc='" + desc + '\'' +
", user=" + user +
'}';
}
}
@Test
public void test6() {
//根据名称自动注解
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("autowrite.xml");
XmlAutoByNameModel xmlAutoByNameModel = (XmlAutoByNameModel) context.getBean("xmlAutoByNameModel");
System.out.println(xmlAutoByNameModel);
//根据名称自动注解,且手动注入
context = new ClassPathXmlApplicationContext("autowrite.xml");
xmlAutoByNameModel = (XmlAutoByNameModel) context.getBean("xmlAutoByNameModel1");
System.out.println(xmlAutoByNameModel);
}按照类型进行自动注入(byType)
spring容器会遍历x类中所有的set方法,会在容器中查找和set参数类型相同的bean对象,将其通过set方法进行注入,未找到对应类型的bean对象则set方法不进行注入;
set方法的参数如果是下面的类型或者下面类型的数组的时候,这个set方法会被跳过注入:Object,Boolean,boolean,Byte,byte,Character,char,Double,double,Float,float,Integer,int,Long,Short,shot,Enum,CharSequence,Number,Date,java.time.temporal.Temporal,java.net.URI,java.net.URI,java.util.Locale,java.lang.Class;
**需要注入的set属性的类型和被注入的bean的类型需要满足isAssignableFrom关系。**按照类型自动装配的时候,如果按照类型找到了多个符合条件的bean,系统会报错;
若XML中存在多个符合条件的bean时,需要给对应的bean属性中添加**primary=“true”**;
<!--按类型自动注入-->
<bean id="userType" class="com.zd.model.User" autowire="byType" primary="true">
<property name="name" value="类型张三"/>
<property name="age" value="12"/>
</bean>
<bean id="personType" class="com.zd.model.Person" autowire="byType">
<property name="name" value="类型李四"/>
<property name="address" value="13"/>
</bean>
<bean id="xmlAutoByTypeModel" class="com.zd.model.XmlAutoByTypeModel" autowire="byType"/>public class XmlAutoByTypeModel {
private User user;
private Person person;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
public String toString() {
return "XmlAutoByTypeModel{" +
"user=" + user +
", person=" + person +
'}';
}
}
@Test
public void test7() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("autowrite.xml");
XmlAutoByTypeModel xmlAutoByTypeModel = (XmlAutoByTypeModel) context.getBean("xmlAutoByTypeModel");
System.out.println(xmlAutoByTypeModel);
}相对于手动注入,节省了不少代码,新增或者删除属性,只需要增减对应的set方法就可以了,更容易
扩展了。
注入类型匹配的所有bean
- 一个容器中满足某种类型的bean可以有很多个,将容器中某种类型中的所有bean,通过set方法注入给一个java.util.List<需要注入的Bean的类型或者其父类型或者其接口>对象
- 将容器中某种类型中的所有bean,通过set方法注入给一个java.util.Map<String,需要注入的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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<bean id="service1-1" class="org.spring.lession2.DiAutowireByTypeExtend.Service1">
<property name="desc" value="service1-1"/>
</bean>
<bean id="service1-2" class="org.spring.lession2.DiAutowireByTypeExtend.Service1">
<property name="desc" value="service1-2"/>
</bean>
<bean id="service2-1" class="org.spring.lession2.DiAutowireByTypeExtend.Service2">
<property name="desc" value="service2-1"/>
</bean>
<bean id="diAutowireByTypeExtend" class="org.spring.lession2.DiAutowireByTypeExtend" autowire="byType" />
</beans>/**
* 满足条件的bean注入到集合中(重点)
*/
public class DiAutowireByTypeExtend {
//定义了一个接口
public interface IService1 {
}
public static class BaseServie {
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "BaseServie{" +
"desc='" + desc + '\'' +
'}';
}
}
//Service1实现了IService1接口
public static class Service1 extends BaseServie implements IService1 {
}
//Service1实现了IService1接口
public static class Service2 extends BaseServie implements IService1 {
}
//定义了4个属性,都是泛型类型的,都有对应的set方法。
private List<IService1> serviceList;
private List<BaseServie> baseServieList;
private Map<String, IService1> service1Map;
private Map<String, BaseServie> baseServieMap;
public List<IService1> getServiceList() {
return serviceList;
}
//参数类型是List<BaseServie>,这个集合集合中元素的类型是BaseServie,
// spring会找到容器中所有满足BaseServie.isAssignableFrom(bean的类型)的bean列表,将其通过set方法进行注入
public void setServiceList(List<IService1> serviceList) {
this.serviceList = serviceList;
}
public List<BaseServie> getBaseServieList() {
return baseServieList;
}
public void setBaseServieList(List<BaseServie> baseServieList) {
this.baseServieList = baseServieList;
}
public Map<String, IService1> getService1Map() {
return service1Map;
}
//这个参数类型是一个map,map的key是string类型,value是IService1类型,spring容器会将所有满足IService1类型的bean找到,
// 按照name->bean对象这种方式丢到一个map中,然后调用的set方法进行注入,最后注入的这个map就是bean的名称和bean对象进行映射的一个map对象。
public void setService1Map(Map<String, IService1> service1Map) {
this.service1Map = service1Map;
}
public Map<String, BaseServie> getBaseServieMap() {
return baseServieMap;
}
public void setBaseServieMap(Map<String, BaseServie> baseServieMap) {
this.baseServieMap = baseServieMap;
}
@Override
public String toString() { //9
return "DiAutowireByTypeExtend{" +
"serviceList=" + serviceList +
", baseServieList=" + baseServieList +
", service1Map=" + service1Map +
", baseServieMap=" + baseServieMap +
'}';
}
}public static void main(String[] args) {
String beanXml = "classpath:/org/spring/lession2/diAutowireByTypeExtend.xml";
ClassPathXmlApplicationContext context = IocUtil.context(beanXml);
//从容器中获取DiAutowireByTypeExtend
DiAutowireByTypeExtend diAutowireByTypeExtend = context.getBean(DiAutowireByTypeExtend.class);
//输出diAutowireByTypeExtend中的属性看一下
System.out.println("serviceList:" + diAutowireByTypeExtend.getServiceList());
System.out.println("baseServieList:" + diAutowireByTypeExtend.getBaseServieList());
System.out.println("service1Map:" + diAutowireByTypeExtend.getService1Map());
System.out.println("baseServieMap:" + diAutowireByTypeExtend.getBaseServieMap());
}
//输出
serviceList:[BaseServie{desc='service1-1'}, BaseServie{desc='service1-2'}, BaseServie{desc='service2-1'}]
baseServieList:[BaseServie{desc='service1-1'}, BaseServie{desc='service1-2'}, BaseServie{desc='service2-1'}]
service1Map:{service1-1=BaseServie{desc='service1-1'}, service1-2=BaseServie{desc='service1-2'}, service2-1=BaseServie{desc='service2-1'}}
baseServieMap:{service1-1=BaseServie{desc='service1-1'}, service1-2=BaseServie{desc='service1-2'}, service2-1=BaseServie{desc='service2-1'}}按照构造函数进行自动注入
spring会找到x类中所有的构造方法(一个类可能有多个构造方法),然后将这些构造方法进行排序(先按修饰符进行排序,public的在前面,其他的在后面,如果修饰符一样的,会按照构造函数参数数量倒叙,也就是采用贪婪的模式进行匹配,spring容器会尽量多注入一些需要的对象)得到一个构造函数列表,会轮询这个构造器列表,判断当前构造器所有参数是否在容器中都可以找到匹配的bean对象,如果可以找到就使用这个构造器进行注入,如果不能找到,那么就会跳过这个构造器,继续采用同样的方式匹配下一个构造器,直到找到一个合适的为止。
<!--按构造方法自动注入-->
<bean id="userConstructor" class="com.zd.model.User" autowire="byType">
<property name="name" value="构造张三"/>
<property name="age" value="12"/>
</bean>
<bean id="personConstructor" class="com.zd.model.Person" autowire="byType">
<property name="name" value="构造李四"/>
<property name="address" value="13"/>
</bean>
<bean id="xmlAutoByConstructorModel" class="com.zd.model.XmlAutoByConstructorModel" autowire="constructor"/>public XmlAutoByConstructorModel(User user, Person person) {
this.user = user;
this.person = person;
}
@Test
public void test8() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("autowrite.xml");
XmlAutoByConstructorModel xmlAutoByConstructorModel = (XmlAutoByConstructorModel) context.getBean("xmlAutoByConstructorModel");
System.out.println(xmlAutoByConstructorModel);
}构造函数匹配采用贪婪匹配,多个构造函数结合容器找到一个合适的构造函数,最匹配的就是第一个有参构造函数,而第二个有参构造函数的第二个参数在spring容器中找不到匹配的bean对象,所以被跳过了。
autowire=“default”
bean xml的根元素为beans,注意根元素有个 default-autowire 属性,这个属性可选值有(no|byName|byType|constructor|default),这个属性可以批量设置当前文件中所有bean的自动注入的方式,bean元素中如果省略了autowire属性,那么会取 default-autowire 的值作为其 autowire 的值,而每个bean元素还可以单独设置自己的 autowire 覆盖 default-autowire 的配置,如下:
<?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-4.3.xsd"
default-autowire="byName">
<bean id="user" class="com.zd.model.User" autowire="default"></bean>
</beans>当autowire="default"时,会根据XML中的autowrite属性进行自动注入。
bean的创建及销毁顺序
无依赖bean的创建及销毁顺序
- bean对象的创建顺序和bean xml中定义的顺序一致;
- bean销毁的顺序和bean xml中定义的顺序相反;
<bean id="bean3" class="com.zd.model.XmlBeanCycleModel.Bean3"/>
<bean id="bean2" class="com.zd.model.XmlBeanCycleModel.Bean2"/>
<bean id="bean1" class="com.zd.model.XmlBeanCycleModel.Bean1"/>public class XmlBeanCycleModel {
public static class Bean1 implements DisposableBean{
public Bean1() {
System.out.println(this.getClass()+" constructor!");
}
@Override
public void destroy() throws Exception {
System.out.println(this.getClass()+" destroy!");
}
}
public static class Bean2 implements DisposableBean{
public Bean2() {
System.out.println(this.getClass()+" constructor!");
}
@Override
public void destroy() throws Exception {
System.out.println(this.getClass()+" destroy!");
}
}
public static class Bean3 implements DisposableBean{
public Bean3() {
System.out.println(this.getClass()+" constructor!");
}
@Override
public void destroy() throws Exception {
System.out.println(this.getClass()+" destroy!");
}
}
}
/*class com.zd.model.XmlBeanCycleModel$Bean2 constructor!
class com.zd.model.XmlBeanCycleModel$Bean1 constructor!
容器启动完毕,准备关闭spring容器!
class com.zd.model.XmlBeanCycleModel$Bean1 destroy!
class com.zd.model.XmlBeanCycleModel$Bean2 destroy!
class com.zd.model.XmlBeanCycleModel$Bean3 destroy!
spring容器已关闭!
*/通过构造器强依赖bean创建和销毁顺序
<bean id="beanConstruct3" class="com.zd.model.XmlConstructBeanCycleModel.BeanConstruct3">
<constructor-arg index="0" ref="beanConstruct2"/>
</bean>
<bean id="beanConstruct2" class="com.zd.model.XmlConstructBeanCycleModel.BeanConstruct2">
<constructor-arg index="0" ref="beanConstruct1"/>
</bean>
<bean id="beanConstruct1" class="com.zd.model.XmlConstructBeanCycleModel.BeanConstruct1"/>public class XmlConstructBeanCycleModel {
public static class BeanConstruct1 implements DisposableBean{
public BeanConstruct1() {
System.out.println(this.getClass()+" constructor!");
}
@Override
public void destroy() throws Exception {
System.out.println(this.getClass()+" destroy!");
}
}
public static class BeanConstruct2 implements DisposableBean{
private BeanConstruct1 beanConstruct1;
public BeanConstruct2(BeanConstruct1 beanConstruct1) {
this.beanConstruct1 = beanConstruct1;
System.out.println(this.getClass()+" constructor!");
}
@Override
public void destroy() throws Exception {
System.out.println(this.getClass()+" destroy!");
}
}
public static class BeanConstruct3 implements DisposableBean{
private BeanConstruct2 beanConstruct2;
public BeanConstruct3(BeanConstruct2 beanConstruct2) {
this.beanConstruct2 = beanConstruct2;
System.out.println(this.getClass()+" constructor!");
}
@Override
public void destroy() throws Exception {
System.out.println(this.getClass()+" destroy!");
}
}
}
@Test
public void test9() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("daos.xml");
System.out.println("容器启动完毕,准备关闭spring容器!");
//关闭容器
context.close();
System.out.println("spring容器已关闭!");
}
/*
class com.zd.model.XmlConstructBeanCycleModel$BeanConstruct1 constructor!
class com.zd.model.XmlConstructBeanCycleModel$BeanConstruct2 constructor!
class com.zd.model.XmlConstructBeanCycleModel$BeanConstruct3 constructor!
容器启动完毕,准备关闭spring容器!
class com.zd.model.XmlConstructBeanCycleModel$BeanConstruct3 destroy!
class com.zd.model.XmlConstructBeanCycleModel$BeanConstruct2 destroy!
class com.zd.model.XmlConstructBeanCycleModel$BeanConstruct1 destroy!
spring容器已关闭!
*/- bean对象的创建顺序和bean依赖的顺序一致;
- bean销毁的顺序和bean创建的顺序相反;
通过depend-on干预bean创建和销毁顺序
对于无依赖的bean,通过定义的顺序确实可以干预bean的创建顺序,通过强依赖也可以干预bean的创建顺序。如果xml中定义的bean特别多,而有些bean之间也没有强依赖关系,此时如果想去调整bean的创建和销毁的顺序,得去调整xml中bean的定义顺序,或者去加强依赖,这样是非常不好的,spring中可以通过depend-on来解决这些问题,在不调整bean的定义顺序和强加依赖的情况下,可以通过depend-on属性来设置当前bean的依赖于哪些bean,那么可以保证depend-on指定的bean在当前bean之前先创建好,销毁的时候在当前bean之后进行销毁。
<bean id="bean1" class="" depend-on="bean2,bean3; bean4" depend-on:设置当前bean依赖的bean名称,可以指定多个,多个之间可以用”,;空格”进行分割上面不管bean2,bean2,bean4在任何地方定义,都可以确保在bean1创建之前,会先将bean2,bean3,bean4创建好,表示bean1依赖于这3个bean,可能bean1需要用到bean2、bean3、bean4中生成的一些资源或者其他的功能等,但是又没有强制去在bean1类中通过属性定义强依赖的方式去依赖于bean2、bean3、bean4;当然销毁的时候也会先销毁当前bean,再去销毁被依赖的bean,即先销毁bean1,再去销毁depend-on指定的bean。
primay参数
当一个容器中,类的子类或接口的实现类有多个时,注入父类或接口时会因为有多个候选者报错,需要使用primary参数设置主要候选者。
<?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 class="org.spring.model.PrimaryDemo.ServiceA"/>
<bean class="org.spring.model.PrimaryDemo.ServiceB" primary="true"/>
<bean class="org.spring.model.PrimaryDemo" autowire="byType"/>
</beans>public class PrimaryDemo {
public interface IService{
void doSomething();
}
public static class ServiceA implements IService{
@Override
public void doSomething() {
System.out.println("ServiceA");
}
}
public static class ServiceB implements IService{
@Override
public void doSomething(){
System.out.println("ServiceB");
}
}
}
public static void main(String[] args) {
String path = "classpath:/org/spring/lession3/primaryDemo.xml";
ClassPathXmlApplicationContext context = IocUtil.context(path);
PrimaryDemo.IService service = context.getBean(PrimaryDemo.IService.class);
service.doSomething();
}bean中的autowire-candidate属性
spring还有一种方法也可以解决这个问题,可以设置某个bean是否在自动注入的时候是否为作为候选
bean,通过bean元素的autowire-candidate属性类配置,如下:
<bean id="beanConstruct1" class="com.zd.model.XmlConstructBeanCycleModel.BeanConstruct1" autowire-candidate="false"/>autowire-candidate:设置当前bean在被其他对象作为自动注入对象的时候,是否作为候选bean,默认值是true。
容器在创建setterBean的时候,发现其autowire为byType,即按类型自动注入,此时会在setterBean类中查找所有setter方法列表,其中就包含了setService方法,setService方法参数类型是IService,然后就会去容器中按照IService类型查找所有符合条件的bean列表,此时容器中会返回满足IService这种类型并且autowire-candidate=“true”的bean,刚才有说过bean元素的autowire-candidate的默认值是true,所以容器中符合条件的候选bean有2个:serviceA和serviceB,setService方法只需要一个满足条件的bean,此时会再去看这个列表中是否只有一个主要的bean(即bean元素的primary=“ture”的bean),而bean元素的primary默认值都是false,所以没有primary为true的bean,此时spring容器懵了,不知道选哪个了,此时就报错了,抛出NoUniqueBeanDefinitionException异常。
从上面过程中可以看出将某个候选bean的primary置为true就可以解决问题了。或者只保留一个bean的autowire-candidate为true,将其余的满足条件的bean的autowire-candidate置为false。
lazy-init:bean延迟初始化
bean初始化方式
- 实时初始化
- 延迟初始化
bean实时初始化
在容器启动过程中被创建组装好的bean,称为实时初始化的bean,spring中默认定义的bean都是实时初始化的bean,这些bean默认都是单例的,在容器启动过程中会被创建好,然后放在spring容器中以供使用。
实时初始化bean的优点
- 更早发现bean定义的错误:实时初始化的bean如果定义有问题,会在容器启动过程中会抛出异常,让开发者快速发现问题。
- 查找bean更快:容器启动完毕之后,实时初始化的bean已经完全创建好了,此时被缓存在spring容器中,当我们需要使用的时候,容器直接返回就可以了,速度是非常快的。
延迟初始化的bean
如果程序中定义的bean非常多,并且有些bean创建的过程中比较耗时的时候,会导致系统消耗的资源比较多,并且会让整个启动时间比较长。
延迟初始化,就是和实时初始化刚好相反,延迟初始化的bean在容器启动过程中不会创建,而是需要使用的时候才会去创建。以下情况会导致延迟初始化的bean被创建:
- 被其他bean作为依赖进行注入的时候,比如通过property元素的ref属性进行引用,通过构造器注入、通过set注入、通过自动注入,这些都会导致被依赖bean的创建
- 开发者自己写代码向容器中查找bean的时候,如调用容器的getBean方法获取bean。
延迟初始化bean的配置
在bean定义的时候通过 lazy-init 属性来配置bean是否是延迟加载,true:延迟初始化,false:实时初始化
<bean lazy-init="是否是延迟初始化" 当延迟初始化的对象被实时初始化的对象引入时,延迟初始化的对象也会在初始化时被加载。
<?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 id ='lazyInitBean' class="org.spring.model.LazyInitBean" lazy-init="true"/>
<bean id="lazyInitDemo" class="org.spring.model.LazyInitModel">
<property name="lazyInitBean" ref="lazyInitBean"/>
</bean>
</beans>public class LazyInitBean {
public LazyInitBean() {
System.out.println("我是延迟初始化的bean!");
}
}
public class LazyInitModel {
public LazyInitModel() {
System.out.println("LazyInitModel实例化!");
}
private LazyInitBean lazyInitBean;
public LazyInitBean getLazyInitBean() {
return lazyInitBean;
}
public void setLazyInitBean(LazyInitBean lazyInitBean) {
this.lazyInitBean = lazyInitBean;
System.out.println("LazyInitModel.setLazyInitBean方法!");
}
}
public static void main(String[] args) {
String path = "classpath:/org/spring/lession3/lazyInitDemo.xml";
ClassPathXmlApplicationContext context = IocUtil.context(path);
System.out.println(context.getBean("lazyInitDemo"));
}总结:延迟初始化的bean无法在程序启动过程中迅速发现bean定义的问题,第一次获取的时候可能耗时会比较长。
使用继承简化bean配置(abstract &parent)
案例
<bean id="xmlAbstractA" class="com.zd.model.XmlAbstractA"/>
<bean id="baseAbstrant" abstract="true">
<property name="name" value="张三"/>
<property name="xmlAbstractA" ref="xmlAbstractA"/>
</bean>
<bean id="xmlAbstractB" class="com.zd.model.XmlAbstractB" parent="baseAbstrant"/>
<bean id="xmlAbstractC" class="com.zd.model.XmlAbstractC" parent="baseAbstrant"/>public class XmlAbstractA {
}
public class XmlAbstractB {
private String name;
private XmlAbstractA xmlAbstractA;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public XmlAbstractA getXmlAbstractA() {
return xmlAbstractA;
}
public void setXmlAbstractA(XmlAbstractA xmlAbstractA) {
this.xmlAbstractA = xmlAbstractA;
}
@Override
public String toString() {
return "XmlAbstractB{" +
"name='" + name + '\'' +
", xmlAbstractA=" + xmlAbstractA +
'}';
}
}
public class XmlAbstractC {
private String name;
private XmlAbstractA xmlAbstractA;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public XmlAbstractA getXmlAbstractA() {
return xmlAbstractA;
}
public void setXmlAbstractA(XmlAbstractA xmlAbstractA) {
this.xmlAbstractA = xmlAbstractA;
}
@Override
public String toString() {
return "XmlAbstractB{" +
"name='" + name + '\'' +
", xmlAbstractA=" + xmlAbstractA +
'}';
}
}
@Test
public void test11() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("autowrite.xml");
XmlAbstractB xmlAbstractB = (XmlAbstractB)context.getBean("xmlAbstractB");
XmlAbstractC xmlAbstractC = (XmlAbstractC)context.getBean("xmlAbstractC");
System.out.println(xmlAbstractB);
System.out.println(xmlAbstractC);
}**abstract="true"的属性,表示这个bean是抽象的,abstract为true的bean在spring容器中不会被创建,只是会将其当做bean定义的模板,而serviceB和serviceC的定义中多了一个属性parent,用来指定当前bean的父bean名称,此处是baseService,此时serviceB和serviceC会继承baseService中定义的配置信息。**
总结:
- bean元素的abstract属性为true的时候可以定义某个bean为一个抽象的bean,相当于定义了一个bean模板,spring容器并不会创建这个bean,从容器中查找abstract为true的bean的时候,会报错BeanIsAbstractException异常
- bean元素的parent属性可以指定当前bean的父bean,子bean可以继承父bean中配置信息,也可以自定义配置信息,这样可以覆盖父bean中的配置。
lookup-method和replaced-method属性
lookup-method:单例对象中需要获取多例对象,一般情况下获取的仍是单例对象,使用此属性后可以获取多例对象;
replace-method:单例对象中获取其他对象,可以拦截替换为其他对象。
代理(java动态带&cglib代理)
为什么需要代理
开发中,接口或父类存在多个实现类,当要对所有实现类进行统一需求时,添加代码比价繁琐,就需要进行代理。
Jdk动态代理
- jdk中的Proxy只能为接口生成代理类,如果你想给某个类创建代理类,需要我们用到cglib了。
- 通过Proxy创建代理对象,当调用代理对象任意方法时候,会被InvocationHandler接口中的invoke方法进行处理。
java.lang.reflect.Proxy常用方法
public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces)
/**
*参数说明:
*loader:定义代理类的类加载器
*interfaces:指定需要实现的接口列表,创建的代理默认会按顺序实现interfaces指定的接口
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
/**
*这个方法先为指定的接口创建代理类,然后会生成代理类的一个实例,最后一个参数比较特殊,
* 是InvocationHandler类型的,这个是个接口如下:
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
/**
*上面方法会返回一个代理对象,当调用代理对象的任何方法的时候,会就被InvocationHandler接口的invoke方法处理,所以主要代码需要写在invoke方法中
*/
//判断指定的类是否是一个代理类
public static boolean isProxyClass(Class<?> cl) {
return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}
//获取代理对象的InvocationHandler对象
public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentExceptionpublic interface IJdkProxyService {
void m1();
void m2();
void m3();
}
public class JdkProxyClient {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//方法1
//获取接口对应的代理类
Class<IJdkProxyService> proxyClass = (Class<IJdkProxyService>) Proxy.getProxyClass(IJdkProxyService.class.getClassLoader(), IJdkProxyService.class);
//创建代理类的处理器
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke--"+method.getName());
return null;
}
};
//创建代理实例
IJdkProxyService newedInstance = proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);
newedInstance.m1();
newedInstance.m2();
newedInstance.m3();
//方法2
InvocationHandler handler1 = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke2--" + method.getName());
return null;
}
};
IJdkProxyService newedProxyInstance = (IJdkProxyService) Proxy.newProxyInstance(IJdkProxyService.class.getClassLoader(), new Class[]{IJdkProxyService.class}, handler1);
newedProxyInstance.m1();
newedProxyInstance.m2();
newedProxyInstance.m3();
}
}
//输出:
invoke--m1
invoke--m2
invoke--m3cglib代理详解
jdk代理只能代理接口,要对类进行代理需要使用cglib实现,本质上它是通过动态的生成一个子类去覆盖所要代理的类(非final修饰的类和方法)。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,Enhancer也不能对final类进行代理操作。
public class CglibServiceDemo {
public void m1(){
System.out.println("m1");
}
public void m2(){
System.out.println("m2");
}
}
public class CglibServiceDemo1 {
public void m1(){
System.out.println("m1");
this.m2();
}
public void m2(){
System.out.println("m2");
}
}
public class CglibServiceDemo2 {
public String m1() {
System.out.println("m1");
return "m1";
}
public String m2() {
System.out.println("m2");
return "m2";
}
}
public class CglibServiceDemo4 {
public void insert1() {
System.out.println("我是insert1");
}
public void insert2() {
System.out.println("我是insert2");
}
public String get1() {
System.out.println("我是get1");
return "get1";
}
public String get2() {
System.out.println("我是get2");
return "get2";
}
}
public class CglibClient {
public static void main(String[] args) {
//创建Enhancer对象
Enhancer enhancer = new Enhancer();
//2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类
enhancer.setSuperclass(CglibServiceDemo.class);
/*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口,
此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接口,实现了Callback接口,
当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
enhancer.setCallback(new MethodInterceptor() {
/**
* 代理对象方法拦截器
* @param o 代理对象
* @param method 被代理的类的方法,即Service1中的方法
* @param objects 调用方法传递的参数
* @param methodProxy 方法代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用方法:"+method);
//用MethodProxy的invokeSuper调用被代理类的方法
Object object = methodProxy.invokeSuper(o, objects);
return object;
}
});
//4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型的,所以需要强转一下
CglibServiceDemo serviceDemo = (CglibServiceDemo) enhancer.create();
//5.调用代理对象的方法
serviceDemo.m1();
serviceDemo.m2();
Enhancer enhancer1 = new Enhancer();
enhancer1.setSuperclass(CglibServiceDemo1.class);
enhancer1.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用方法:" + method);
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
});
CglibServiceDemo1 proxy = (CglibServiceDemo1) enhancer1.create();
proxy.m1();
//通过代理返回同意值
Enhancer enhancer2 = new Enhancer();
enhancer2.setSuperclass(CglibServiceDemo2.class);
enhancer2.setCallback(new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "代理返回";
}
});
CglibServiceDemo2 proxy2 = (CglibServiceDemo2) enhancer2.create();
System.out.println(proxy2.m2());
//直接放行,不做任何操作(NoOp.INSTANCE)
enhancer2 = new Enhancer();
enhancer2.setSuperclass(CglibServiceDemo2.class);
enhancer2.setCallback(NoOp.INSTANCE);
proxy2 = (CglibServiceDemo2) enhancer2.create();
System.out.println(proxy2.m2());
//不同的方法使用不同的拦截器(CallbackFilter)
Enhancer enhancer4 = new Enhancer();
enhancer4.setSuperclass(CglibServiceDemo4.class);
Callback[] callbacks = new Callback[]{
//拦截insert开头的方法统计耗时
new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long starTime = System.nanoTime();
Object result = methodProxy.invokeSuper(o, objects);
long endTime = System.nanoTime();
System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
return result;
}
},
//拦截get开头的方法返回固定值
new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "固定值";
}
},
//拦截其他方法不做任何操作
NoOp.INSTANCE
};
enhancer4.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
return 0;
}
});
//调用enhancer的setCallbacks传递Callback数组
enhancer4.setCallbacks(callbacks);
/**
* 设置过滤器CallbackFilter
* CallbackFilter用来判断调用方法的时候使用callbacks数组中的哪个Callback来处理当前方法
* 返回的是callbacks数组的下标
*/
enhancer4.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
//获取当前调用的方法的名称
String methodName = method.getName();
/**
* 方法名称以insert开头,
* 返回callbacks中的第1个Callback对象来处理当前方法,
* 否则使用第二个Callback处理被调用的方法
*/
return methodName.startsWith("insert") ? 0 : 1;
}
});
CglibServiceDemo4 serviceDemo4 = (CglibServiceDemo4) enhancer4.create();
serviceDemo4.insert1();
serviceDemo4.insert2();
System.out.println(serviceDemo4.get1());
System.out.println(serviceDemo4.get2());
//对案例CallbackFilter的优化(CallbackHelper)
enhancer4 = new Enhancer();
//创建2个Callback
Callback costTimeCallback = (MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) -> {
long starTime = System.nanoTime();
Object result = methodProxy.invokeSuper(o, objects);
long endTime = System.nanoTime();
System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
return result;
};
//下面这个用来拦截所有get开头的方法,返回固定值的
Callback fixdValueCallback = (FixedValue) () -> "固定值";
CallbackHelper callbackHelper = new CallbackHelper(CglibServiceDemo4.class, null) {
@Override
protected Object getCallback(Method method) {
return method.getName().startsWith("insert") ? costTimeCallback : fixdValueCallback;
}
};
enhancer4.setSuperclass(CglibServiceDemo4.class);
//调用enhancer的setCallbacks传递Callback数组
enhancer4.setCallbacks(callbackHelper.getCallbacks());
/**
* 设置CallbackFilter,用来判断某个方法具体走哪个Callback
*/
enhancer4.setCallbackFilter(callbackHelper);
CglibServiceDemo4 proxy5 = (CglibServiceDemo4) enhancer4.create();
System.out.println("---------------");
proxy5.insert1();
System.out.println("---------------");
proxy5.insert2();
System.out.println("---------------");
System.out.println(proxy5.get1());
System.out.println("---------------");
System.out.println(proxy5.get2());
}
}CGLIB和Java动态代理的区别
- Java动态代理只能对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
- Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效
注解
注解是对代码的一种增强,可以在代码编译或者程序运行期间获取注解的信息
1.定义注解
public @interface 注解名称{
[public] 参数类型 参数名称1() [default 参数默认值];
[public] 参数类型 参数名称2() [default 参数默认值];
[public] 参数类型 参数名称n() [default 参数默认值];
}- 访问修饰符必须为public,不写默认为public;
- 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
- 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);
- 参数名称后面的()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
- default代表默认值,值必须和第2点定义的类型一致;
- 如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
指定注解使用范围:@Target
可以用在类、接口、注解类型、枚举类型以及方法上面,自定义注解上也可以不使用@Target注解,如果不使用,表示自定义注解可以用在任何地方。
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface MyAnnotation {
}
/*注解的使用范围*/
public enum ElementType {
/*类、接口、枚举、注解上面*/
TYPE,
/*字段上*/
FIELD,
/*方法上*/
METHOD,
/*方法的参数上*/
PARAMETER,
/*构造函数上*/
CONSTRUCTOR,
/*本地变量上*/
LOCAL_VARIABLE,
/*注解上*/
ANNOTATION_TYPE,
/*包上*/
PACKAGE,
/*类型参数上*/
TYPE_PARAMETER,
/*类型名称上*/
TYPE_USE
}指定注解的保留策略:@Retention
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}
public enum RetentionPolicy {
/*注解只保留在源码中,编译为字节码之后就丢失了,也就是class文件中就不存在了*/
SOURCE,
/*注解只保留在源码和字节码中,运行阶段会丢失*/
CLASS,
/*源码、字节码、运行期间都存在*/
RUNTIME
}2.使用注解
@Ann1 //无参注解
@Ann2(name = "我是张三) //一个参数注解
@Ann3("我是张三) //一个参数为value的注解,可以省略参数名称
@Ann4(name = {"张三", "李四"}) //数组类型参数的注解
public class UseAnnotation1 {
}注解开发
在spring4之后要是用注解开发必须引入spring-aop的包。
使用注解须知:
- 导入约束,context约束;
- 配置注解支持;
- 添加自动扫描指定包下所有类自动装配。
<?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容器中-->
<context:component-scan base-package="com.zd.annoation"/>
</beans>使用注解实现自动装配
@Autowired(自动注入)
可直接在属性上使用也可以在set方法上使用,使用@Autowired可以不用编写set方法,前提上自动装配的属性在IOC容器中存在,切符合byType命名规范。
拓展:
在字段前使用**@Nullable**注解可以指定该字段可以为空,而在@Autowired中,使用注解参数require=false时,表示该注入bean可以为空,如果不写则默认不可为空。
查找过程
@Autowired标注在字段上面:假定字段类型为一个自定义的普通的类型,候选者查找过程如下

@Autowired标注在方法上或者方法参数上面:假定参数类型为为一个自定义的普通的类型,候选者查找过程如下

@Autowired查找候选者可以简化为下面这样
:::info 按类型找→通过限定符@Qualifier过滤→@Primary→@Priority→根据名称找(字段名称或者参数名称)
:::
概括为:先按类型找,然后按名称找
@Qualifier(value=“XXX”)
限定符
可以在依赖注入查找候选者的过程中对候选者进行过滤
@Autowired根据类型查找IOC容器中的bean对象,当需要引入的属性对象命名不符合byType命名规范或存在多个IOC对象时,需要结合@Qualifier注解指定需要的对象名。
<bean id="cat" class="com.zd.annoation.autowired.Cat"/>
<bean id="cat111" class="com.zd.annoation.autowired.Cat"/>
<bean id="tom" class="com.zd.annoation.autowired.Cat"/>public class Person {
@Autowired(required = false)
@Qualifier("tom")
private Cat cat;
public void run(){
cat.shut();
}
}案例1:用在类上
//接口
public interface IServiceQualifier {
}
//实现类
@Component
@Qualifier("tag1")
public class ServiceQualifier1 implements IServiceQualifier {
}
@Component
@Qualifier("tag1")
public class ServiceQualifier2 implements IServiceQualifier {
}
@Component
@Qualifier("tag2")
public class ServiceQualifier3 implements IServiceQualifier {
}
//扫描包配置,要在需要被注册类的上一层
@ComponentScan
public class MyScan {
}
//获取注入信息
@Component
public class InjectServiceQualifier {
@Autowired
@Qualifier("tag1")
private Map<String, IServiceQualifier> iServiceQualifierMap1;
@Autowired
@Qualifier("tag2")
private Map<String, IServiceQualifier> iServiceQualifierMap2;
@Override
public String toString() {
return "InjectServiceQualifier{" +
"iServiceQualifierMap1=" + iServiceQualifierMap1 +"\n"+
"iServiceQualifierMap2=" + iServiceQualifierMap2 +
'}';
}
}
//测试类
@Test
public void demo2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyScan.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s",name,context.getBean(name)));
}
}
//输出
injectServiceQualifier->InjectServiceQualifier{iServiceQualifierMap1={serviceQualifier1=com.zd.annoation.autowired.serviceimpl.ServiceQualifier1@534df152, serviceQualifier2=com.zd.annoation.autowired.serviceimpl.ServiceQualifier2@52e677af}
iServiceQualifierMap2={serviceQualifier3=com.zd.annoation.autowired.serviceimpl.ServiceQualifier3@35083305}}案例2:@Autowired结合@Qulifier指定注入的bean
被注入的类型有多个的时候,可以使用@Qulifier来指定需要注入那个bean,将@Qulifier的value设置为需要注入bean的名称。
//接口
public interface IServiceQualifier {
}
//实现类
@Component
public class ServiceQualifier1 implements IServiceQualifier {
}
@Component
public class ServiceQualifier2 implements IServiceQualifier {
}
//扫描包配置,要在需要被注册类的上一层
@ComponentScan
public class MyScan {
}
//获取注入信息
@Component
public class InjectServiceQualifier {
@Autowired
@Qualifier("serviceQualifier1")
private IServiceQualifier iServiceQualifier;
@Override
public String toString() {
return "InjectServiceQualifier{" +
"iServiceQualifier=" + iServiceQualifier +
'}';
}
}
//测试类
@Test
public void demo2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyScan.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s",name,context.getBean(name)));
}
}
//输出
injectServiceQualifier->InjectServiceQualifier{iServiceQualifier=com.zd.annoation.autowired.serviceimpl.ServiceQualifier1@4de5031f}容器中IService类型的bean有2个[service1和service2],当类上没有标注@Qualifier注解的时候,可以理解为:bean的名称就是限定符的值,若存在@Qualifier注解则根据注解的value值注入指定名称的bean。
案例3:用在方法参数上
//获取注入信息
@Component
public class InjectServiceQualifier {
private IServiceQualifier iServiceQualifier1;
private IServiceQualifier iServiceQualifier2;
@Autowired
public void injectBean(@Qualifier("serviceQualifier1") IServiceQualifier iServiceQualifier1,@Qualifier("serviceQualifier3")IServiceQualifier iServiceQualifier2){
this.iServiceQualifier1 = iServiceQualifier1;
this.iServiceQualifier2 = iServiceQualifier2;
}
@Override
public String toString() {
return "InjectServiceQualifier{" +
"iServiceQualifier1=" + iServiceQualifier1 +"\n"+
", iServiceQualifier2=" + iServiceQualifier2 +
'}';
}
}
//测试类
@Test
public void demo2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyScan.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s",name,context.getBean(name)));
}
}
//输出
injectServiceQualifier->InjectServiceQualifier{iServiceQualifier1=com.zd.annoation.autowired.serviceimpl.ServiceQualifier1@5d740a0f
, iServiceQualifier2=com.zd.annoation.autowired.serviceimpl.ServiceQualifier3@214b199c}方法上标注了@Autowired注解,说明会被注入依赖,2个参数上分别使用了限定符来指定具体需要注入哪个bean
案例4:用在setter方法上
不管是用在setter方法还是普通方法上面,都是一样的效果
@Primary:设置为主要候选者
注入依赖的过程中,当有多个候选者的时候,可以指定哪个候选者为主要的候选者
使用方法:
可以用在类上或者方法上面。
通常定义bean常见的有2种方式:
方式1:在类上标注@Component注解,此时可以配合@Primary,标注这个bean为主要候选者
方式2:在配置文件中使用@Bean注解标注方法,来注册bean,可以在@Bean标注的方法上加上@Primary,标注这个bean为主要候选bean。
案例1:用在类上
//接口
public interface IServicePrimary {
}
//实现类
@Component
public class ServiceParimary1 implements IServicePrimary {
}
@Component
@Primary
public class ServiceParimary2 implements IServicePrimary {
}
//获取注入信息
@Component
public class InjectServicePrimary {
@Autowired
private IServicePrimary iServicePrimary;
@Override
public String toString() {
return "InjectServicePrimary{" +
"iServicePrimary=" + iServicePrimary +
'}';
}
}
//测试类
@Test
public void demo2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyScan.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s",name,context.getBean(name)));
}
}
//输出
injectServicePrimary->InjectServicePrimary{iServicePrimary=com.zd.annoation.autowired.serviceimpl.ServiceParimary2@14a2f921}案例2:用在方法上,结合@Bean
//接口
public interface IServicePrimary {
}
//实现类
public class ServiceParimary1 implements IServicePrimary {
}
public class ServiceParimary2 implements IServicePrimary {
}
//配置类
@Configuration
public class MainConfigParimary {
@Bean
public IServicePrimary servicePrimary1() {
return new ServiceParimary1();
}
@Bean
@Primary
public IServicePrimary servicePrimary2(){
return new ServiceParimary2();
}
@Bean
public InjectServicePrimary injectServicePrimary(){
return new InjectServicePrimary();
}
}
//获取注入信息
@Component
public class InjectServicePrimary {
@Autowired
private IServicePrimary iServicePrimary;
@Override
public String toString() {
return "InjectServicePrimary{" +
"iServicePrimary=" + iServicePrimary +
'}';
}
}
//测试类
@Test
public void demo2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyScan.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s",name,context.getBean(name)));
}
}
//输出
injectServicePrimary->InjectServicePrimary{iServicePrimary=com.zd.annoation.autowired.serviceimpl.ServiceParimary2@3daa422a}@Resource注解
默认通过byName的方式寻找容器中需要注入的对象,若通过byName找不到则通过byType方式寻找,都找不到则报错。
查找过程
@Resource标注在字段上面:假定字段类型为一个自定义的普通的类型,候选者查找过程如下

@Autowired标注在方法上或者方法参数上面:假定参数类型为为一个自定义的普通的类型,候选者查找过程如下:

public class Person {
@Resource
private Cat cat;
public void run(){
cat.shut();
}
}@Resource查找候选者可以简化为
:::info 先按Resource的name值作为bean名称找→按名称(字段名称、方法名称、set属性名称)找→按类型找→
通过限定符@Qualifier过滤→@Primary→@Priority→根据名称找(字段名称或者方法参数名称)
概括为:先按名称找,然后按类型找
:::
区别:
- 都是用来自动装配的,都可以放在属性字段上;
- @Autowired通过byType的方式实现,而且必须要求这个对象存在;
- @Resource 默认通过byName的方式,如果找不到名字则通过byType实现,都找不到的情况下报错;
- 执行顺讯:@Autowired 通过byType的方式实现@Resource默认通过byname的方式实现。
@Value(“xxx”)注解
等价于
<bean id="dog" class="com.zd.annoation.autowired.Dog">
<property name="name" value="baud"/>
</bean>@Scope注解(作用域)
使用:单例,多里以及web请求作用域
@Scope("prototype")
@Scope("singleton")
@Scope("request")
@Scope("session")使用JAVA的方式配置spring
完全不是用XML配置,全权交给JAVA操作
@Configuration和@Bean注解
@Confuguration注解
@Confuguration注解可以加载类上,让这类的功能类似于XML的配置文件
@Configuration
public class ConfigBean {
}上面代码类似于下面的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-4.3.xsd">
</beans>获取bean的方式变为 AnnotationConfigApplicationContext 来加载@Configuration修饰的类:
此时ConfigBean类中没有任何内容,相当于一个空的xml配置文件,此时我们要在ConfigBean类中注册bean,那么我们就要用到@Bean注解了。
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyScan.class);@Bean注解
@Bean注解等同于bean xml中的
@Bean用在方法上,表示通过方法来定义一个bean,默认将方法名称作为bean名称,将方法返回最为bean对象,注册到spring容器中。
@Configuration
public class ConfigBean {
//bean名称默认为 user1
@Bean
public User user1(){
return new User();
}
//bean名称别名为 user2Bean
@Bean("user2Bean")
public User user2(){
return new User();
}
//bean名称为 user3Bean 别名为user3Bean_1,user3Bean_alias
@Bean({"user3Bean","user3Bean_1","user3Bean_alias"})
public User user3(){
return new User();
}
}
@Test
public void demo3() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);
for (String name : context.getBeanDefinitionNames()) {
String[] aliases = context.getAliases(name);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s",name, Arrays.asList(aliases),context.getBean(name)));
}
}
//输出
bean名称:configBean,别名:[],bean对象:com.zd.annoation.javaconfig.serviceimpl.ConfigBean$$EnhancerBySpringCGLIB$$5a23e019@5276e6b0
bean名称:user1,别名:[],bean对象:com.zd.annoation.javaconfig.model.User@71b1176b
bean名称:user2Bean,别名:[],bean对象:com.zd.annoation.javaconfig.model.User@6193932a
bean名称:user3Bean,别名:[user3Bean_1, user3Bean_alias],bean对象:com.zd.annoation.javaconfig.model.User@647fd8ce当不使用@Configuration注解时
//输出
bean名称:configBean,别名:[],bean对象:com.zd.annoation.javaconfig.serviceimpl.ConfigBean@442675e1
bean名称:user1,别名:[],bean对象:com.zd.annoation.javaconfig.model.User@71b1176b
bean名称:user2Bean,别名:[],bean对象:com.zd.annoation.javaconfig.model.User@6193932a
bean名称:user3Bean,别名:[user3Bean_1, user3Bean_alias],bean对象:com.zd.annoation.javaconfig.model.User@647fd8ce- 有没有@Configuration注解,@Bean都可以生效被加载到容器中;
- 被@Configuration修饰的类,bean对象输出后没有带EnhancerBySpringCGLIB的字样,说明没有被cglib处理过,不是一个代理对象。
区别:
public class ServiceNotConfigA {
}
public class ServiceNotConfigB {
private ServiceNotConfigA serviceNotConfigA;
public ServiceNotConfigB(ServiceNotConfigA serviceNotConfigA) {
this.serviceNotConfigA = serviceNotConfigA;
}
@Override
public String toString() {
return "ServiceNotConfigB{" +
"serviceNotConfigA=" + serviceNotConfigA +
'}';
}
}
//@Configuration
public class MainConfigNotConfig {
@Bean
public ServiceNotConfigA serviceNotConfigA(){
System.out.println("调用serviceNotConfigA()方法");
return new ServiceNotConfigA();
}
@Bean
public ServiceNotConfigB serviceNotConfig1(){
System.out.println("调用serviceNotConfig1()方法");
ServiceNotConfigA serviceNotConfigA = this.serviceNotConfigA();
return new ServiceNotConfigB(serviceNotConfigA);
}
@Bean
public ServiceNotConfigB serviceNotConfig2(){
System.out.println("调用serviceNotConfig2()方法");
ServiceNotConfigA serviceNotConfigA = this.serviceNotConfigA();
return new ServiceNotConfigB(serviceNotConfigA);
}
}
@Test
public void demo5() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigNotConfig.class);
for (String name : context.getBeanDefinitionNames()) {
String[] aliases = context.getAliases(name);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s", name, Arrays.asList(aliases), context.getBean(name)));
}
}
//输出添加@Configuration注解
调用serviceNotConfigA()方法
调用serviceNotConfig1()方法
调用serviceNotConfig2()方法
//输出不添加@Configuration注解
调用serviceNotConfigA()方法
调用serviceNotConfig1()方法
调用serviceNotConfigA()方法
调用serviceNotConfig2()方法
调用serviceNotConfigA()方法被@Configuration修饰的类,spring容器中会通过cglib给这个类创建一个代理,代理会拦截所有被@Bean 修饰的方法,默认情况(bean为单例)下确保这些方法只被调用一次,从而确保这些bean是同一个bean,即单例的。
- @Configuration注解修饰的类,会被spring通过cglib做增强处理,通过cglib会生成一个代理对象,代理会拦截所有被@Bean注解修饰的方法,可以确保一些bean是单例的;
- 不管@Bean所在的类上是否有@Configuration注解,都可以将@Bean修饰的方法作为一个bean注册到spring容器中。
@Bean定义bean时注入依赖的几种方式
常用方式
- 硬编码方式
//接口
public interface IServiceBean {
}
//实现类
public class ServiceBean1 implements IServiceBean {
}
public class ServiceBean2 implements IServiceBean {
}
public class ServiceBean3 implements IServiceBean {
private ServiceBean1 serviceBean1;
private ServiceBean2 serviceBean2;
public ServiceBean1 getServiceBean1() {
return serviceBean1;
}
public void setServiceBean1(ServiceBean1 serviceBean1) {
this.serviceBean1 = serviceBean1;
}
public ServiceBean2 getServiceBean2() {
return serviceBean2;
}
public void setServiceBean2(ServiceBean2 serviceBean2) {
this.serviceBean2 = serviceBean2;
}
@Override
public String toString() {
return "ServiceBean3{" +
"serviceBean1=" + serviceBean1 +"\n"+
", serviceBean2=" + serviceBean2 +
'}';
}
}
//配置类
@Configuration
public class MainConifgBean {
@Bean
public ServiceBean1 serviceBean1(){
return new ServiceBean1();
}
@Bean
public ServiceBean2 serviceBean2(){
return new ServiceBean2();
}
@Bean
public ServiceBean3 serviceBean3(){
ServiceBean3 serviceBean3 = new ServiceBean3();
serviceBean3.setServiceBean1(this.serviceBean1());
serviceBean3.setServiceBean2(this.serviceBean2());
return serviceBean3;
}
}
//测试类
@Test
public void demo2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyScan.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s",name,context.getBean(name)));
}
}
//输出
serviceBean3->ServiceBean3{serviceBean1=com.zd.annoation.autowired.serviceimpl.ServiceBean1@68999068, serviceBean2=com.zd.annoation.autowired.serviceimpl.ServiceBean2@7722c3c3}- @Autowired、@Resource的方式
- @Bean标注的方法参数的方式
//接口
public interface IServiceBean {
}
//实现类
public class ServiceBean1 implements IServiceBean {
}
public class ServiceBean2 implements IServiceBean {
}
public class ServiceBean3 implements IServiceBean {
private ServiceBean1 serviceBean1;
private ServiceBean2 serviceBean2;
public ServiceBean1 getServiceBean1() {
return serviceBean1;
}
public void setServiceBean1(ServiceBean1 serviceBean1) {
this.serviceBean1 = serviceBean1;
}
public ServiceBean2 getServiceBean2() {
return serviceBean2;
}
public void setServiceBean2(ServiceBean2 serviceBean2) {
this.serviceBean2 = serviceBean2;
}
@Override
public String toString() {
return "ServiceBean3{" +
"serviceBean1=" + serviceBean1 +"\n"+
", serviceBean2=" + serviceBean2 +
'}';
}
}
//配置类
@Configuration
public class MainConifgBean {
@Bean
public ServiceBean1 serviceBean1(){
return new ServiceBean1();
}
@Bean
public ServiceBean2 serviceBean2(){
return new ServiceBean2();
}
@Bean
public ServiceBean3 serviceBean3(ServiceBean1 serviceBean1,ServiceBean2 serviceBean2){
ServiceBean3 serviceBean3 = new ServiceBean3();
/*serviceBean3.setServiceBean1(this.serviceBean1());
serviceBean3.setServiceBean2(this.serviceBean2());*/
serviceBean3.setServiceBean1(serviceBean1);
serviceBean3.setServiceBean2(serviceBean2);
return serviceBean3;
}
}
//测试类
@Test
public void demo2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyScan.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s",name,context.getBean(name)));
}
}
//输出
serviceBean3->ServiceBean3{serviceBean1=com.zd.annoation.autowired.serviceimpl.ServiceBean1@68999068, serviceBean2=com.zd.annoation.autowired.serviceimpl.ServiceBean2@7722c3c3}- @Bean标注的方法参数上使用@Autowired注解
@Bean
public ServiceBean3 serviceBean3(ServiceBean1 serviceBean1,@Autowired(require = false) ServiceBean2 serviceBean2){
ServiceBean3 serviceBean3 = new ServiceBean3();
serviceBean3.setServiceBean1(serviceBean1);
serviceBean3.setServiceBean2(serviceBean2);
return serviceBean3;
}在方法的参数上添加@Autowired注解说明第二个参数不是必须的,第一个参数是必须的,找不到会抛出异常。
- @Bean结合@Qualifier
//接口
public interface IServiceBean {
}
//实现类
public class ServiceBean1 implements IServiceBean {
}
public class ServiceBean2 implements IServiceBean {
}
public class ServiceBean3 implements IServiceBean {
private ServiceBean1 serviceBean1;
private ServiceBean2 serviceBean2;
public ServiceBean1 getServiceBean1() {
return serviceBean1;
}
public void setServiceBean1(ServiceBean1 serviceBean1) {
this.serviceBean1 = serviceBean1;
}
public ServiceBean2 getServiceBean2() {
return serviceBean2;
}
public void setServiceBean2(ServiceBean2 serviceBean2) {
this.serviceBean2 = serviceBean2;
}
@Override
public String toString() {
return "ServiceBean3{" +
"serviceBean1=" + serviceBean1 +"\n"+
", serviceBean2=" + serviceBean2 +
'}';
}
}
@Component
public class InjectServiceBean {
@Autowired
@Qualifier("tag1")
private Map<String, IServiceBean> IServiceBeanMap1;
public InjectServiceBean() {
}
public InjectServiceBean(Map<String, IServiceBean> IServiceBeanMap1) {
this.IServiceBeanMap1 = IServiceBeanMap1;
}
public Map<String, IServiceBean> getIServiceBeanMap1() {
return IServiceBeanMap1;
}
public void setIServiceBeanMap1(Map<String, IServiceBean> IServiceBeanMap1) {
this.IServiceBeanMap1 = IServiceBeanMap1;
}
@Override
public String toString() {
return "InjectServiceBean{" +
"IServiceBeanMap1=" + IServiceBeanMap1 +
'}';
}
}
//配置类
@Configuration
public class MainConifgBean {
@Bean
@Qualifier("tag1")
public ServiceBean1 serviceBean1(){
return new ServiceBean1();
}
@Bean
@Qualifier("tag1")
public ServiceBean2 serviceBean2(){
return new ServiceBean2();
}
@Bean
@Qualifier("tag2")
public ServiceBean3 serviceBean3(){
return new ServiceBean3();
}
@Bean
public InjectServiceBean injectServiceBean(@Qualifier("tag1")Map<String, IServiceBean> map1){
InjectServiceBean injectServiceBean = new InjectServiceBean();
injectServiceBean.setIServiceBeanMap1(map1);
return injectServiceBean;
}
}
//测试类
@Test
public void demo2() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyScan.class);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(String.format("%s->%s",name,context.getBean(name)));
}
}
//输出
serviceBean3->ServiceBean3{serviceBean1=com.zd.annoation.autowired.serviceimpl.ServiceBean1@68999068, serviceBean2=com.zd.annoation.autowired.serviceimpl.ServiceBean2@7722c3c3}@ComponentScan、@ComponentScans注解
在spring中将对象注册为bean的方式有两种:XML注册、@Bean注解注册。但在项目中使用任意一种都较为繁琐,为了方便bean的注册,Spring提供了批量的注册bean,方便大量bean批量注册。
@ComponentScan
@ComponentScan用于批量注册bean,这个注解会让spring去扫描某次包及其子包中的所有类,然后将满足条件的类作为bean注册到spring容器中。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {}; //指定需要扫描的包
@AliasFor("value")
String[] basePackages() default {}; //作用同value;value和basePackages不能同时存在设置,可二选一
Class<?>[] basePackageClasses() default {}; //指定一些类,spring容器会扫描这些类所在的包及其子包中的类
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; //自定义bean名称生成器
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class"; //需要扫描包中的那些资源,默认是:**/*.class,即会扫描指定包中所有的class文件
boolean useDefaultFilters() default true; //对扫描的类是否启用默认过滤器,默认为true
ComponentScan.Filter[] includeFilters() default {}; //过滤器:用来配置被扫描出来的那些类会被作为组件注册到容器中
ComponentScan.Filter[] excludeFilters() default {}; //过滤器,和includeFilters作用刚好相反,用来对扫描的类进行排除的,被排除的类不会被注册到容器中
boolean lazyInit() default false; //是否延迟初始化被注册的bean
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}@ComponentScan工作流程:
使用@ComponentScan注解修饰的类需要放在其他注解所在包的同级才可被扫描。
1. **Spring会扫描指定的包,且会递归下面的子包,得到一批类的数组;**
2. **这些类会经过上面的过滤器,最后剩下的类会被注册到容器中。**
扫描包相关参数:value、basePackages、basePackageClasses这三个参数控制;
过滤器相关参数:useDefaultFilters、includeFilters、excludeFilters这三个参数控制器过滤。
默认情况下,任何参数都不设置的情况下,此时,会将@ComponentScan修饰的类所在的包作为扫描
包;默认情况下useDefaultFilters为true,这个为true的时候,spring容器内部会使用默认过滤器,
规则是:凡是类上有 @Repository、@Service、@Controller、@Component 这几个注解中的任何一
个的,那么这个类就会被作为bean注册到spring容器中,所以默认情况下,只需在类上加上这几个注
解中的任何一个,这些类就会自动交给spring容器来管理了。
@Component、@Repository、@Service、@Controller注解
组件,放在类上,说明该类被spring管理,创建Bean,等价于
- 这4个注解本质上是没有任何差别,都可以用在类上面,表示这个类被spring容器扫描的时候,可以作为一个bean组件注册到spring容器中;
- spring提供这4个注解,是为了让系统更清晰,通常情况下,系统是分层结构的,多数系统一般分为controller层、service层、dao层;
- @controller通常用来标注controller层组件;
- @service注解标注service层的组件;
- @Repository标注dao层的组件;
- 这样可以让整个系统的结构更清晰,当看到这些注解的时候,会和清晰的知道属于哪个层,对于spring来说,将这3个注解替换成@Component注解,对系统没有任何影响,产生的效果是一样的。
任何参数未设置
//@Component注解
@Component
public class UserModel {
}
//@Repository注解
@Repository
public class UserDao {
}
//@Service注解
@Service
public class UserService {
}
//@Controller注解
@Controller
public class UserController {
}
//@ComponentScan注解
@ComponentScan
public class ComponentScanConfig {
}
//测试案例
@Test
public void demo6() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentScanConfig.class);
for (String name : context.getBeanDefinitionNames()) {
String[] aliases = context.getAliases(name);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s", name, Arrays.asList(aliases), context.getBean(name)));
}
}
//输出
bean名称:componentScanConfig,别名:[],bean对象:com.zd.annoation.javaconfig.ComponentScanConfig@57175e74
bean名称:userController,别名:[],bean对象:com.zd.annoation.javaconfig.controller.UserController@7bb58ca3
bean名称:userDao,别名:[],bean对象:com.zd.annoation.javaconfig.dao.UserDao@c540f5a
bean名称:userModel,别名:[],bean对象:com.zd.annoation.javaconfig.model.UserModel@770c2e6b
bean名称:userService,别名:[],bean对象:com.zd.annoation.javaconfig.serviceimpl.UserService@1a052a00指定需要扫描的包
指定需要扫描的包,可以用过value或者basePackage来配置,二者选其一,都配置运行会报错。
@ComponentScan({
"com.zd.annoation.javaconfig.serviceimpl",
"com.zd.annoation.javaconfig.model"
})
public class ComponentScanConfig {
}
//输出
bean名称:componentScanConfig,别名:[],bean对象:com.zd.annoation.javaconfig.ComponentScanConfig@971d0d8
bean名称:userService,别名:[],bean对象:com.zd.annoation.javaconfig.serviceimpl.UserService@51931956
bean名称:userModel,别名:[],bean对象:com.zd.annoation.javaconfig.model.UserModel@2b4a2ec7注意:
指定包名的方式扫描存在的一个隐患,若包被重名了,会导致扫描会失效,一般情况下面我们使用basePackageClasses的方式来指定需要扫描的包,这个参数可以指定一些类型,默认会扫描这些类所在的包及其子包中所有的类,这种方式可以有效避免这种问题。
basePackageClasses指定扫描范围
在需要被扫描的包中定义一个标记的接口或者类,他们的唯一作用是作为basePackageClasses的值。
public interface ScanBasePackageClass {
}
@Component
public class Service1 {
}
@Component
public class Service2 {
}
@ComponentScan(basePackageClasses = ScanBasePackageClass.class)
public class ComponentScanBasePackageClassesConfig {
}
//测试
@Test
public void demo7() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentScanBasePackageClassesConfig.class);
for (String name : context.getBeanDefinitionNames()) {
String[] aliases = context.getAliases(name);
System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s", name, Arrays.asList(aliases), context.getBean(name)));
}
}
//输出
bean名称:componentScanBasePackageClassesConfig,别名:[],bean对象:com.zd.annoation.javaconfig.ComponentScanBasePackageClassesConfig@279ad2e3
bean名称:service1,别名:[],bean对象:com.zd.annoation.javaconfig.service.Service1@58134517
bean名称:service2,别名:[],bean对象:com.zd.annoation.javaconfig.service.Service2@4450d156includeFilters的使用
Filter[] includeFilters() default {};是一个 Filter 类型的数组,多个Filter之间为或者关系,即满足任意一个就可以了。
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
/**
* 过滤器中的类型,是个枚举类型,5种类型
* ANNOTATION:通过注解的方式来筛选候选者,即判断候选者是否有指定的注解
* ASSIGNABLE_TYPE:通过指定的类型来筛选候选者,即判断候选者是否是指定的类型
* ASPECTJ:ASPECTJ表达式方式,即判断候选者是否匹配ASPECTJ表达式
* REGEX:正则表达式方式,即判断候选者的完整名称是否和正则表达式匹配
* CUSTOM:用户自定义过滤器来筛选候选者,对候选者的筛选交给用户自己来判断
*/
FilterType type() default FilterType.ANNOTATION;
/**
当type=FilterType.ANNOTATION时,通过classes参数可以指定一些注解,用来判断被扫描的类
上是否有classes参数指定的注解
当type=FilterType.ASSIGNABLE_TYPE时,通过classes参数可以指定一些类型,用来判断被扫描
的类是否是classes参数指定的类型
当type=FilterType.CUSTOM时,表示这个过滤器是用户自定义的,classes参数就是用来指定用
户自定义的过滤器,自定义的过滤器需要实现org.springframework.core.type.filter.TypeFilter接
口
*/
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
/**
* 当type=FilterType.ASPECTJ时,通过pattern来指定需要匹配的ASPECTJ表达式的值
* 当type=FilterType.REGEX时,通过pattern来自正则表达式的值
*/
String[] pattern() default {};
}扫描包含注解的类
自定义一个注解,让标注有这些注解的类自动注入到容器中;
注解及XML配置理解
- xml用来管理bena;
- 注解只负责完成属性的注入;
- 在使用过程中只需注意让注解生效:导入注解依赖。
AOP
代理模式:JDK动态代理和CGLIB代理
SpringAop的底层就是代理模式,代理模式分为静态代理和动态代理;
代理的作用
- 在原有代码逻辑上,新增日志或耗时统计等需求时,若修改源码则难以维护,使用代理可以减少此类操作;
- 在使用第三方jar包时,没有源码,可以在接口上做代理,拓展业务。
public interface IService1 {
void m1();
}
public class JdkProxyServiceImpl implements IService1 {
@Override
public void m1() {
System.out.println("IService1的m1方法!");
}
}
public class JavaProxyServiceImpl implements IService1 {
private IService1 target;
public JavaProxyServiceImpl(IService1 target) {
this.target = target;
}
@Override
public void m1() {
long starTime = System.nanoTime();
this.target.m1();
long endTime = System.nanoTime();
System.out.println(target.getClass()+"m1方法耗时:"+(endTime-starTime));
}
}
@Test
public void demo11(){
//代理类代理实现类
JavaProxyServiceImpl javaProxy = new JavaProxyServiceImpl(new JdkProxyServiceImpl());
javaProxy.m1();
}
//输出
IService1的m1方法!
class com.zd.aop.service.impl.JdkProxyServiceImplm1方法耗时:55100JDK动态代理
JDK实现代理类主要使用两个类:
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler- 只能为接口创建代理对象;
- 创建出来的代理都是java.lang.reflect.Proxy的子类。
java.lang.reflect.Proxy类
| 方法名 | 参数说明 | 返回及说明 |
|---|---|---|
| public static Class_<?> getProxyClass(ClassLoader loader, Class<?>… interfaces)_ | loader:定义代理类的类加载器; interfaces:指定需要实现的接口,创建的代理默认会按照顺序实现interfaces指定的接口。 | 返回代理类的Class对象; |
| public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) | loader:定义代理类的类加载器; interfaces:指定需要实现的接口,创建的代理默认会按照顺序实现interfaces指定的接口; h:InvocationHandler接口; | 返回一个代理对象,当调用代理对象的方法时,会被InvocationHandler 接口的invoke方法处理。 |
| public static boolean isProxyClass(Class<?> cl) | cl:判断是否代理类的calss | 判断该类是否使用getProxyClass方法或newProxyInstance方法动态生成为代理类 |
| public static InvocationHandler getInvocationHandler(Object proxy) | proxy:需要返回InvocationHandler 的类 | 返回指定代理实例的调用处理程序InvocationHandler。 |
实现代理的方法一
1.调用Proxy.getProxyClass方法获取代理类的Class对象
2.使用InvocationHandler接口创建代理类的处理器
3.通过代理类和InvocationHandler创建代理对象
4.上面已经创建好代理对象了,接着我们就可以使用代理对象了
public interface IService1 {
void m1();
}
public class JdkProxyServiceImpl implements IService1{
@Override
public void m1() {
System.out.println("IService1的m1方法!");
}
}
@Test
public void demo12() throws Exception {
//创建代理类
Class<IService1> iService1Class = (Class<IService1>) Proxy.getProxyClass(IService1.class.getClassLoader(), IService1.class);
//创建代理类的处理器
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invocationHanler处理器,背调调用的方法时:" + method.getName());
return null;
}
};
//创建代理实例
IService1 iService1 = iService1Class.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
//调用代理的方法
iService1.m1();
}
//输出
invocationHanler处理器,背调调用的方法时:m1代理方法二
1.使用InvocationHandler接口创建代理类的处理器
2.使用Proxy类的静态方法newProxyInstance直接创建代理对象
3.使用代理对象
public interface IService1 {
void m1();
}
public class JdkProxyServiceImpl implements IService1{
@Override
public void m1() {
System.out.println("IService1的m1方法!");
}
}
@Test
public void demo13() throws Exception {
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invocationHanlder处理器,被调用的方法:" + method.getName());
return null;
}
};
IService3 proxyInstance = (IService3)Proxy.newProxyInstance(IService3.class.getClassLoader(), new Class[]{IService3.class}, invocationHandler);
proxyInstance.m3();
}任意接口中的方法耗时统计
public interface IService1 {
void m1();
}
public interface IService2 {
void m2();
}
public class JdkProxyServiceImpl implements IService1, IService2 {
@Override
public void m1() {
System.out.println("m1");
}
@Override
public void m2() {
System.out.println("m2");
}
}
/**
*下面通过jdk动态代理创建一个代理对象,实现上面定义的2个接口,将代理对象所有的请求转发给Service去处理,需要在代理中统计2个接口中所有方法的耗时
*/
public class JdkProxyConfig implements InvocationHandler {
private Object target;
public JdkProxyConfig(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long nanoTime = System.nanoTime();
Object invoke = method.invoke(this.target, args);
System.out.println(method+",耗时:"+(System.nanoTime()-nanoTime));
return invoke;
}
}
//测试
@Test
public void demo8(){
JdkProxyServiceImpl target = new JdkProxyServiceImpl();
JdkProxyConfig jdkProxyConfig = new JdkProxyConfig(target);
Object newProxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class[]{IService1.class, IService2.class}, jdkProxyConfig);
//判断代理对象是否是Service类型的,肯定是false咯
System.out.println(String.format("proxyObject instanceof Service = %s",newProxyInstance instanceof JdkProxyServiceImpl));
//判断代理对象是否是IService1类型的,肯定是true
System.out.println(String.format("proxyObject instanceof IService1 = %s",newProxyInstance instanceof IService1));
//判断代理对象是否是IService2类型的,肯定是true
System.out.println(String.format("proxyObject instanceof IService2 = %s",newProxyInstance instanceof IService2));
//将代理转换为IService1类型
IService1 service1 = (IService1) newProxyInstance;
//调用IService2的m1方法
service1.m1();
//将代理转换为IService2类型
IService2 service2 = (IService2) newProxyInstance;
//调用IService2的m2方法
service2.m2();
//输出代理的类型
System.out.println("代理对象的类型:" + newProxyInstance.getClass());
}
//输出
proxyObject instanceof Service = false
proxyObject instanceof IService1 = true
proxyObject instanceof IService2 = true
m1
public abstract void com.zd.aop.service.IService1.m1(),耗时:120400
m2
public abstract void com.zd.aop.service.IService2.m2(),耗时:27300
代理对象的类型:class com.sun.proxy.$Proxy2m1方法和m2方法被 JdkProxyConfig#invoke 给增强了,调用目标方法的过程中统计了耗时;
最后一行输出可以看出代理对象的类型,类名中包含了 $Proxy 的字样,所以以后注意,看到这种字样的,基本上都是通过jdk动态代理创建的代理对象。
Proxy使用注意
- jdk的Proxy代理只能为接口代理类,如果想为某个类创建代理类,需要使用cglib;
- Proxy类中提供了几个常用的静态方法用来代理接口,判断是否代理类的灯;
- 通过Proxy创建代理对象,当调用代理对象任意方法时,会被InvocationHandler接口中的invoke方法进行处理。
CGLIB代理
- cglib弥补了jdk动态代理的不足,jdk动态代理只能为接口创建代理,而cglib不管是接口还是类,都可以使用cglib创建代理;
- cglib是一个字节码生成里,用于拓展java类和实现接口;
- cglib创建代理的过程,相当于创建了一个新的类,可通过cglib来配置这个新的类需要实现的接口,以及需要继承的父类;
- cglib可以为类创建代理,但是这个类不能是final类型的,cglib为类创建代理的过程,使用Enchancer类通过继承来实现的,相当于给需要被代理的类创建了一个子类,然后会重写父类中的方法,来进行增强,由于final修饰的类是不能被继承的,final修饰的方法不能被重写,static修饰的方法也不能被重写,private修饰的方法也不能被子类重写,而其他类型的方法都可以被子类重写,被重写的这些方法可以通过cglib进行拦截增强。
CGLIB过程
- cglib根据父类,Callback,Filter及一些相关信息生成key;
- 根据key生成对应的子类的二进制表现形式;
- 使用ClassLoder装载对应的二进制,生成Class对象,并缓存;
- 最后实例化Class对象,并缓存。
案例cglib常用方法
1.拦截所有方法(MethodInterceptor)
@Test
public void demo14(){
//使用Enhancer给某个类创建代理类
Enhancer enhancer = new Enhancer();
//通过setSuperclass设置父类型,即需要创建代理的类
enhancer.setSuperclass(CgLibService3Impl.class);
//设置回调,需实现Callback接口,此处使用MethodInterceptor接口,它继承了Callback接口,
// 当调用代理对象的任何方法时,都会被MethodInterceptor接口的invoke方法处理
enhancer.setCallback(new MethodInterceptor() {
/**
* 代理对象方法了拦截
* @param o 代理对象
* @param method 被代理的类的方法
* @param objects 调用方法传递的参数
* @param methodProxy 方法代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用方法:"+method);
Object invokeSuper = methodProxy.invokeSuper(o, objects);
return invokeSuper;
}
});
//获取代理对象,调用Enhancer.creat方法获取代理对象,这个方法返回的是Object类型的
CgLibService3Impl libService3 = (CgLibService3Impl) enhancer.create();
libService3.cgm3();
}2.拦截所有方法并返回固定值(FixedValue)
当调用任意方法时都返回一个固定值,可以使用FixedValue接口:
public class CgLibService3Impl implements CgLibIService3 {
@Override
public void cgm3() {
System.out.println("我是cglib。m3方法!");
}
public String cgFixm1(){
return "李四";
}
}
@Test
public void demo15() {
Enhancer enhancer = new Enhancer();
//设置代理类的父类
enhancer.setSuperclass(CgLibService3Impl.class);
//通过callback来对被代理方法进行增强
enhancer.setCallback(new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "张三";
}
});
//创建代理对象
CgLibService3Impl proxy = (CgLibService3Impl)enhancer.create();
System.out.println(proxy.cgFixm1());
}
//输出
张三直接放行,不做任何操作(NoOp.INSTANCE)
@Test
public void demo16() {
Enhancer enhancer = new Enhancer();
//设置代理类的父类
enhancer.setSuperclass(CgLibService3Impl.class);
//代理方法直接放行不做处理
enhancer.setCallback(NoOp.INSTANCE);
//创建代理对象
CgLibService3Impl proxy = (CgLibService3Impl)enhancer.create();
System.out.println(proxy);
System.out.println(proxy.cgFixm1());
}不同的方法使用不同的拦截器(CallbackFilter)
@Test
public void demo17(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CgLibService3Impl.class);
Callback[] callbacks = {
new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用方法:"+method);
Object invokeSuper = methodProxy.invokeSuper(o, objects);
return invokeSuper;
}
},
new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "张三";
}
}
};
enhancer.setCallbacks(callbacks);
/**
* 设置callback过滤器
* callbackfilter用来判断调用方法的时候使用callbacks数组中哪个call
*/
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
return method.getName().startsWith("insert")?0:1;
}
});
CgLibService3Impl cgLibService3 = (CgLibService3Impl) enhancer.create();
cgLibService3.insert1();
cgLibService3.insert2();
System.out.println(cgLibService3.get1());
System.out.println(cgLibService3.get2());
}
//输出
调用方法:public void com.zd.aop.service.impl.CgLibService3Impl.insert1()
insert1
调用方法:public void com.zd.aop.service.impl.CgLibService3Impl.insert2()
insert2
张三
张三为多个接口创建代理
public interface CglibIService1 {
void cgm1();
}
public interface CglibIService2 {
void cgm2();
}
//测试
@Test
public void demo9(){
Enhancer enhancer = new Enhancer();
//设置代理对象需要实现的接口
enhancer.setInterfaces(new Class[]{CglibIService1.class, CglibIService2.class});
//通过callback来对被代理方法进行增强
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("方法:"+method.getName());
return null;
}
});
Object proxy = enhancer.create();
if (proxy instanceof CglibIService1){
((CglibIService1) proxy).cgm1();
}
if (proxy instanceof CglibIService2){
((CglibIService2) proxy).cgm2();
}
//看一下代理对象的类型
System.out.println(proxy.getClass());
//看一下代理类实现的接口
System.out.println("创建代理类实现的接口如下:");
for (Class<?> anInterface : proxy.getClass().getInterfaces()) {
System.out.println(anInterface);
}
}
//输出
方法:cgm1
方法:cgm2
class com.zd.aop.service.CglibIService1$$EnhancerByCGLIB$$c80461e
创建代理类实现的接口如下:
interface com.zd.aop.service.CglibIService1
interface com.zd.aop.service.CglibIService2
interface org.springframework.cglib.proxy.Factory为类和接口同时创建代理
public interface CglibIService1 {
void cgm1();
}
public interface CglibIService2 {
void cgm2();
}
@Test
public void demo10(){
Enhancer enhancer = new Enhancer();
//设置代理类的父类
enhancer.setSuperclass(CglibServiceImpl.class);
//设置代理对象需要实现的接口
enhancer.setInterfaces(new Class[]{CglibIService1.class,CglibIService2.class});
//通过callback来对被代理方法进行增强
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
long startime = System.nanoTime();
Object result = methodProxy.invokeSuper(o, objects);//调用父类中的方法
System.out.println(method+",耗时(纳秒):"+(System.nanoTime() - startime));
return null;
}
});
//创建代理对象
Object proxy = enhancer.create();
//判断代理对象是否是CglibServiceImpl类型的
System.out.println("proxy instanceof CglibServiceImpl"+(proxy instanceof CglibServiceImpl));
if (proxy instanceof CglibServiceImpl){
CglibServiceImpl cglibService = (CglibServiceImpl) proxy;
cglibService.cgm1();
cglibService.cgm2();
}
//看一下代理对象的类型
System.out.println(proxy.getClass());
//输出代理对象的父类
System.out.println("代理类的父类:"+proxy.getClass().getSuperclass());
//看一下代理类实现的接口
System.out.println("创建代理类实现的接口如下:");
for (Class<?> anInterface : proxy.getClass().getInterfaces()) {
System.out.println(anInterface);
}
}
//输出
proxy instanceof CglibServiceImpltrue
cgm1
public void com.zd.aop.service.impl.CglibServiceImpl.cgm1(),耗时(纳秒):13823300
cgm2
public void com.zd.aop.service.impl.CglibServiceImpl.cgm2(),耗时(纳秒):89600
class com.zd.aop.service.impl.CglibServiceImpl$$EnhancerByCGLIB$$5ae7a220
代理类的父类:class com.zd.aop.service.impl.CglibServiceImpl
创建代理类实现的接口如下:
interface com.zd.aop.service.CglibIService1
interface com.zd.aop.service.CglibIService2
interface org.springframework.cglib.proxy.Factory为类和接口同时创建代理
LazyLoader的使用
LazyLoader实现延迟加载
Dispatcher
通过Dispathcer对类扩展一些接口
cglib中的NamingPolicy接口
CGLIB和Java动态代理的区别
- java动态代理只能对接口进行代理,不能对类进行代理(因为所有生成的代理类父类为Proxy,java类继承不知处多重继承);CGLIB可以代理普通类;
- java动态代理使用java原生的反射api进行操作,在生成类上比较高效,CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中较为高效;
AOP理解
什么是AOP
在代码中在原有代码基础上添加公共的需求时,通过代理在执行原有逻辑前进行横向切面编程的叫做AOP。
在程序中具有公共特性的某些类/方法上进行拦截,在方法执行前/后/结果返回后增加执行的一些方法。
Spring中AOP的概念
目标对象(target)
目标对象指将要被增强的对对象,即包含住业务逻辑的类对象。
连接点(JoinPoint)
程序运行的某一个点,比如执行某个方法,在SpringAop中JoinPoint总是表示一个方法的执行
JoinPoint接口
package org.aopalliance.intercept;
public interface Joinpoint {
/**
* 转到拦截器链中的下一个拦截器
*/
Object proceed() throws Throwable;
/**
* 返回保存当前连接点静态部分【的对象】,这里一般指被代理的目标对象
*/
Object getThis();
/**
* 返回此静态连接点 一般就为当前的Method(至少目前的唯一实现是MethodInvocation,所以连
接点得静态部分肯定就是本方法)*/
AccessibleObject getStaticPart();
}Invocation接口
package org.aopalliance.intercept;
/**
* 此接口表示程序中的调用
* 调用是一个连接点,可以被拦截器拦截。
*/
public interface Invocation extends Joinpoint {
/**
* 将参数作为数组对象获取。可以更改此数组中的元素值以更改参数。
* 通常用来获取调用目标方法的参数
*/
Object[] getArguments();
}MethodInvocation接口
package org.aopalliance.intercept;
import java.lang.reflect.Method;
/**
* 方法调用的描述,在方法调用时提供给拦截器。
* 方法调用是一个连接点,可以被方法拦截器拦截。
*/
public interface MethodInvocation extends Invocation {
/**
* 返回正在被调用得方法~~~ 返回的是当前Method对象。
* 此时,效果同父类的AccessibleObject getStaticPart() 这个方法
*/
Method getMethod();
}代理对象(Proxy)
AOP中会通过代理的方式,对目标对象生成一个代理对象,代理对象中会加入需要增强功能,通过代理对象来间接的方式目标对象,起到增强目标对象的效果。
通过(Advice)
需要在目标对象中增强的功能,如上面说的:业务方法前验证用户的功能、方法执行之后打印方法的执行日志。
通知中有2个重要的信息:方法的什么地方,执行什么操作,这2个信息通过通知来指定。
方法的什么地方?之前、之后、包裹目标方法、方法抛出异常后等。
如:
在方法执行之前验证用户是否有效。
在方法执行之后,打印方法的执行耗时。
在方法抛出异常后,记录异常信息发送到mq。
Advice接口
package org.aopalliance.aop;
public interface Advice {
}MethodBeforeAdvice接口
方法执行前通知,需要在目标方法执行前执行一些逻辑的,可以通过这个实现。
通俗点说:需要在目标方法执行之前增强一些逻辑,可以通过这个接口来实现。before方法:在调用给定方法之前回调。
package org.springframework.aop;
public interface MethodBeforeAdvice extends BeforeAdvice {
/**
* 调用目标方法之前会先调用这个before方法
* method:需要执行的目标方法
* args:目标方法的参数
* target:目标对象
*/
void before(Method method, Object[] args, @Nullable Object target) throws
Throwable;
}
AfterReturningAdvice接口
方法执行后通知,需要在目标方法执行之后执行增强一些逻辑的,可以通过这个实现。
不过需要注意一点:目标方法正常执行后,才会回调这个接口,当目标方法有异常,那么这通知会被跳过。
package org.springframework.aop;
public interface AfterReturningAdvice extends AfterAdvice {
/**
* 目标方法执行之后会回调这个方法
* method:需要执行的目标方法
* args:目标方法的参数
* target:目标对象
*/
void afterReturning(@Nullable Object returnValue, Method method, Object[]
args, @Nullable Object target) throws Throwable;
}ThrowsAdvice接口
package org.springframework.aop;
public interface ThrowsAdvice extends AfterAdvice {
}MethodInterceptor接口
方法拦截器,这个接口最强大,可以实现上面3种类型的通知,上面3种通知最终都通过适配模式将其转换为MethodInterceptor方式去执行。
package org.aopalliance.intercept;
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
/**
* 拦截目标方法的执行,可以在这个方法内部实现需要增强的逻辑,以及主动调用目标方法
*/
Object invoke(MethodInvocation invocation) throws Throwable;
}拦截器链
一个目标方法中可以添加很多Advice,这些Advice最终都会被转换为 MethodInterceptor 类型的方法拦截器,最终会有多个 MethodInterceptor ,这些 MethodInterceptor 会组成一个方法调用链。
Aop内部会给目标对象创建一个代理,代理对象中会放入这些 MethodInterceptor 会组成一个方法调用链,当调用代理对象的方法的时候,会按顺序执行这些方法调用链,一个个执行,最后会通过反射再去调用目标方法,进而对目标方法进行增强。
切入点(Pointcut)
用来指定需要将通知使用到哪些地方,比如需要用在哪些类的哪些方法上,切入点就是做这个配置的。
PointCut接口
package org.springframework.aop;
public interface Pointcut {
/**
* 类过滤器, 可以知道哪些类需要拦截
*/
ClassFilter getClassFilter();
/**
* 方法匹配器, 可以知道哪些方法需要拦截
*/
MethodMatcher getMethodMatcher();
/**
* 匹配所有对象的 Pointcut,内部的2个过滤器默认都会返回true
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}ClassFilter接口
@FunctionalInterface
public interface ClassFilter {
/**
* 用来判断目标类型是否匹配
*/
boolean matches(Class<?> clazz);
}MethodMatcher接口
public interface MethodMatcher {
/**
* 执行静态检查给定方法是否匹配
* @param method 目标方法
* @param targetClass 目标对象类型
*/
boolean matches(Method method, Class<?> targetClass);
/**
* 是否是动态匹配,即是否每次执行目标方法的时候都去验证一下
*/
boolean isRuntime();
/**
* 动态匹配验证的方法,比第一个matches方法多了一个参数args,这个参数是调用目标方法传入的
参数
*/
boolean matches(Method method, Class<?> targetClass, Object... args);
/**
* 匹配所有方法,这个内部的2个matches方法任何时候都返回true
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}切面(Aspect)
通知(Advice)和切入点(Pointcut)的组合。切面来定义在哪些地方(Pointcut)执行什么操作(Advice)。
顾问(Advisor)
Advisor 其实它就是 Pointcut 与 Advice 的组合,Advice 是要增强的逻辑,而增强的逻辑要在什么地方执行是通过Pointcut来指定的,所以 Advice 必需与 Pointcut 组合在一起,这就诞生了 Advisor 这个类,spring Aop中提供了一个Advisor接口将Pointcut 与 Advice 的组合起来。
Advisor有好几个称呼:顾问、通知器。
Advisor接口
package org.springframework.aop;
import org.aopalliance.aop.Advice;
/**
* 包含AOP通知(在joinpoint处执行的操作)和确定通知适用性的过滤器(如切入点[PointCut])的基
本接口。
* 这个接口不是供Spring用户使用的,而是为了支持不同类型的建议的通用性。
*/
public interface Advisor {
/**
* 返回引用的通知
*/
Advice getAdvice();
}PointcutAdvisor接口
来获取 Pointcut ,AOP使用到的大部分Advisor都属于这种类型的。
package org.springframework.aop;
/**
* 切入点类型的Advisor
*/
public interface PointcutAdvisor extends Advisor {
/**
* 获取顾问中使用的切入点
*/
Pointcut getPointcut();
}IntroductionAdvisor接口
一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能。可以通过IntroductionAdvisor给目标类引入更多接口的功能。
AOP的使用
- 引入包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>- 导入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/context/spring-aop.xsd">
<!--配置aop:需要导入aop的约束-->
<aop:config>
<!--切入点:express表达式,execution(要执行的位置!****)-->
<aop:pointcut id="pointcut" expression="execution(com.zd.aop.service.impl.UserServiceImpl.*(..))"/>
</aop:config>
</beans>使用原生Spring Api接口
ProxyFactoryBean方式
实现FactoryBean接口实现方法给指定的bean创建一个代理对象:
1. 需要增强的功能,这个放在通知(Advice)中实现;
2. 目标对象(target)表示需要给哪个对象进行增强;
3. 代理对象(proxy)将增强的功能和目标对象组合在一起,然后行程一个代理对象,通过代理对象访问目标对象,起到增强目标对象的作用。
ProxyFactoryBean使用步骤:
- 创建ProxyFactoryBean对象;
- 通过ProxyFactoryBean.setTargetName设置目标对象的bean名称,目标对象是spring容器中的一个bean;
- 通过ProxyFactoryBean。setInterceptorNames添加需要增强的通知;
- 将ProxyFactoryBean注册到Spring容器,假设名称为proxyBean;
- 从Spring查找名称为proxyBean的bean,这个bean就是生成好的代理对象。
//接口
public interface UserService {
void add();
void upd();
void delete();
void select();
}
//实现
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("新增方法执行!");
}
@Override
public void upd() {
System.out.println("修改方法执行!");
}
@Override
public void delete() {
System.out.println("删除方法执行!");
}
@Override
public void select() {
System.out.println("查询方法执行!");
}
}
//后置日志类
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println(o1.getClass().getName()+"的"+method.getName()+"的返回值是"+o);
}
}
//前置日志类
public class BeforeLog implements MethodBeforeAdvice {
/**
*
* @param method 要执行的目标对象方法
* @param objects 参数
* @param o 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了!");
}
}
//测试类
@Test
public void demo4() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean(UserService.class);
userService.add();
}<!--注册bean-->
<bean id="userService" class="com.zd.aop.service.impl.UserServiceImpl"/>
<bean id="beforeLog" class="com.zd.aop.service.impl.BeforeLog"/>
<bean id="afterLog" class="com.zd.aop.service.impl.AfterLog"/>
<!--配置aop:需要导入aop的约束-->
<aop:config>
<!--切入点:express表达式,execution(要执行的位置!****)-->
<aop:pointcut id="pointcut" expression="execution(* com.zd.aop.service.impl.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>ProxyFactoryBean中的interceptorNames
interceptorNames用来指定拦截器的bena名称列表,可批量拦截可非批量拦截。
- 批量拦截
proxyFactoryBean.setInterceptorNames(“需要匹配的bean名称*”);
使用自定义类实现
//接口
public interface UserService {
void add();
void upd();
void delete();
void select();
}
//实现
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("新增方法执行!");
}
@Override
public void upd() {
System.out.println("修改方法执行!");
}
@Override
public void delete() {
System.out.println("删除方法执行!");
}
@Override
public void select() {
System.out.println("查询方法执行!");
}
}
//自定义日志类
public class DiyPointCut {
public void before(){
System.out.println("方法执行前====");
}
public void after(){
System.out.println("方法执行后====");
}
}
//测试类
@Test
public void demo4() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean(UserService.class);
userService.add();
}<bean id="diy" class="com.zd.aop.service.impl.DiyPointCut"/>
<aop:config>
<!--自定义切面引用自定义bean-->
<aop:aspect ref="diy">
<!--切入-->
<aop:pointcut id="point" expression="execution(* com.zd.aop.service.impl.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>使用注解实现
<!--开启aop注解支持-->
<aop:aspectj-autoproxy/>//注解实现定义切面及通知
@Aspect
public class DiyPointCut {
@Before("execution(* com.zd.aop.service.impl.UserServiceImpl.*(..))")
public void before() {
System.out.println("方法执行前====");
}
@After("execution(* com.zd.aop.service.impl.UserServiceImpl.*(..))")
public void after() {
System.out.println("方法执行后====");
}
@Around("execution(* com.zd.aop.service.impl.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint pj) throws Throwable {
System.out.println("环绕前");
pj.proceed();
System.out.println("环绕后");
Signature signature = pj.getSignature();
System.out.println(signature);
}
}
//测试
@Test
public void demo4() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean(UserService.class);
userService.add();
}
//输出
环绕前
方法执行前====
新增方法执行!
方法执行后====
环绕后
void com.zd.aop.service.UserService.add()整合Mybatis
步骤
- 导入jar包
- junit
- mybatis
- mysql数据库
- spring
- aop
- spring-jdbc
- mybatis-spring
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
</dependencies>- 编写配置文件
- 测试