SSM整合尚硅谷Spring
Spring(一)----IOC控制反转、DI依赖注入
Spring基础知识学习笔记(一),内容包括:
-
Spring入门案例
-
IOC控制反转理解
-
属性注入的不同方式
-
注入不同类型的属性值
-
自动装配与注解开发
参考视频:
B站 尚硅谷雷丰阳大神的Spring、Spring MVC、MyBatis课程
【狂神说Java】Spring5最新完整教程IDEA版通俗易懂
1. Spring概述
-
开源的免费框架,是一个容器,可以管理所有的组件(类);
-
轻量级的、非入侵的框架,不依赖于Spring的API
-
控制反转(IOC)和面向切面编程(AOP)
-
支持事务处理,支持对框架整合
-
组件化、一站式
文档:https://docs.spring.io/spring/docs/5.2.5.RELEASE/spring-framework-reference/core.html#spring-core
【总结】:Spring是一个轻量级的、控制反转和面向切面编程的框架
体系结构:
-
Test:Spring的单元测试模块
-
Core Container:核心容器(IOC),包括4部分:
-
spring-core:提供了框架的基本组成部分,包括 IoC 和依赖注入功能。
-
spring-beans:提供 BeanFactory,
-
spring-context:模块建立在由core和 beans 模块的基础上建立起来的,它以一种类似于JNDI注册的方式访问对象。Context模块继承自Bean模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过Servelet容器)等功能
-
spring-expression:提供了强大的表达式语言,用于在运行时查询和操作对象图。它是JSP2.1规范中定义的统一表达式语言的扩展,支持set和get属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等
-
-
AOP+Aspects:面向切面编程模块
-
Data Access:数据访问模块
-
Web:Spring开发Web引用模块
导入依赖:spring-webmvc 包含的最广泛
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.0.RELEASE</version> </dependency> 123456
2. HelloWorld案例
2.1 IOC和DI
Inversion of Control:控制反转。
主动式:自己需要什么自己创建
BookServlet{ BookService bs = new BookService(); AirPlane ap =new AirPlane(); }
被动式:
BookServlet{ BookService bs ; public void test(){ bs.checkout(); } }
控制,即资源的获取方式,包括:
-
主动式:要什么资源自己创建,对于复杂对象的创建时比较庞大的工程
-
被动式:资源的获取不是我们自己创建,而是交给容器创建。
所谓容器,是用来管理所有的组件的(即有功能的类);BookServlet,BookService都受容器管理,容器可以自动探查出哪些组件需要用到另一些组件;容器帮我们创建BookService 对象,并且把BookService 对象赋值过去;
容器:婚介所:
主动获取变为被动接受
程序员只需要告诉容器在什么时候创建什么对象
DI:Dependency Injection,依赖注入,是IOC的一种实现形式。容器能知道哪个组件(类 )运行时需要另外一个类,容器通过反射的形式,将容器中准备好的BookService对象注入(用反射)到BookServlet中
只要是容器管理的组件,都能使用容器提供的强大功能
反射:
Class stuClass1 = stu1.getClass(); Class stuClass2 = Student.class; Class stuClass3 = Class.forName("fanshe.Student");
2.2 入门案例
HelloWorld:所有的对象交给容器创建,给容器中注册组件
-
新建一个Person类,添加set方法
public class Person { private String lastName; private Integer age; private String gender; private String email; public Person() { } public Person(String lastName, Integer age, String gender, String email) { this.lastName = lastName; this.age = age; this.gender = gender; this.email = email; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "Person{" + "lastName='" + lastName + '\'' + ", age=" + age + ", gender='" + gender + '\'' + ", email='" + email + '\'' + '}'; } }
-
新建一个Spring配置文件ApplicationContext.xml,注册bean。
使用
bean
标签注册一个Person对象,Spring会自动创建这个Person对象-
class:写要注册的组件的全类名
-
id:这个对象的唯一标识
-
使用
property
标签为Person对象的属性值,name:指定属性名;value:指定属性值
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!--注册一个Person对象,Spring会自动创建这个Person对象 class:写要注册的组件的全类名,地址 id:这个对象的唯一标识 --> <bean id="person01" class="com.xiao.bean.Person"> <!--使用property标签为Person对象的属性赋值 name:指定属性名 value:指定属性值 --> <property name="lastName" value="zhangsan"/> <property name="age" value="20"/> <property name="email" value="[email protected]"/> <property name="gender" value="0"/> </bean> </beans>
-
测试:
public class IocTest { @Test public void test(){ //从容器中拿到这个组件 /* ApplicationContext:代表IOC容器 ClassPathXmlApplicationContext当前应用的xml配置文件在classpath下的ApplicationContext.xml 根据spring的配置文件得到ioc容器对象 对象创建是容器做的 */ ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext.xml"); Person bean = (Person) ioc.getBean("person01"); System.out.println(bean); } }
【几个细节】:
-
ApplicationContext:IOC容器的接口
-
ClassPathXmlApplicationContext("ioc.xml"):ioc容器的配置文件在类路径下,
FileSystemXmlApplicationContext("d://ioc.xml")ioc容器的配置文件在磁盘路径下
-
同一个组件在IOC容器中默认是单实例的
-
容器中的对象的创建在容器创建完成的时候就已经创建好了
-
容器中如果没有这个组件,获取组件时会报异常 NoSuchBeanDefinitionException
-
IOC容器用
property
标签创建这个组件对象的时候,会利用setter方法为其属性赋值,注意属性名是set方法后的那串的首字母小写
-
2.3 根据bean类型获取bean实例
ioc.getBean()方法中可以传入bean的id,也可以传入class对象,也可以同时传入。
如果一个类型指只注册了一个,则可以通过ioc.getBean(....class)
获得该对象
Person bean1 = ioc.getBean(Person.class); //找到Person类型 sout(bean);
但是如果IOC容器中这个类型的bean有多个,则会报异常 NoUniqueBeanDefinitionException
也可以同时传入bean的id和class对象:
Person bean1 = ioc.getBean("person02",Person.class);
3. 属性的注入方式
-
依赖:bean对象的创建依赖于容器
-
注入:bean对象中所有的属性由容器来注入
3.1 setter注入
需要借助set方法,使用propetry
标签
<property name="lastName" value="zhangsan"/> 1
3.2 通过构造器注入
使用constructor-arg
构造器参数标签,则调用构造器进行属性注入,需要借助有参构造
-
通过构造函数中的参数名称注入
<bean id="person" class="com.xiao.bean.Person"> <constructor-arg name="lastName" value="wangwu"/> <constructor-arg name="age" value="30"/> </bean>
-
只写value属性,会默认按顺序寻找构造方法进行匹配
<bean id="person" class="com.xiao.bean.Person"> <constructor-arg value="wangwu"/> <constructor-arg value="30"/> </bean> 1234
-
通过构造函数参数类型,默认按照顺序
<bean id="person" class="com.xiao.bean.Person"> <constructor-arg type="java.lang.String" value="wangwu"/> <constructor-arg type="java.lang.Integer" value="30"/> </bean> 1234
-
通过构造函数参数索引,如果有多个重载的构造函数时也可以配合type一起使用
<bean id="person" class="com.xiao.bean.Person"> <constructor-arg index="0" value="wangwu"/> <constructor-arg index="1" value="30"/> </bean> 1234
3.3 p名称空间注入
使用p:propertyName直接注入属性的值。本质上还是调用的set方法
导入头文件约束:
增加 xmlns:p="http://www.springframework.org/schema/p" <?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" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="person01" class="com.xiao.bean.Person"> <property name="lastName" value="zhangsan"/> <property name="age" value="20"/> <property name="email" value="[email protected]"/> <property name="gender" value="0"/> </bean> <bean id="person04" class="com.xiao.bean.Person" p:lastName="zhangsan" p:age="30" p:email="[email protected]" p:gender="1"> </bean> </beans>
3.4 c命名空间注入
c(构造: Constructor)命名空间注入,使用c:propertyName注入属性值,本质上使用的是构造器注入
导入头文件约束:
xmlns:c="http://www.springframework.org/schema/c" 1 <bean id="person05" class="com.xiao.bean.Person" c:lastName="zhangsan" c:age="30" c:email="[email protected]" c:gender="1"> </bean> 123
4. 注入不同类型的属性值
新建了一个Student类和一个Address类,来测试不同类型的属性值注入:
Student类:
public class Student { private String name; private Address address; private String[] books; private List<String> hobbys; private Map card; private Set<String> games; private String wife; private Properties info; //get/set方法 //... } 123456789101112
Address类:
public class Address { private String name; private Integer num; //get/set方法 //... } 123456
4.1 注入基本类型值
之前的例子都是注入基本类型的属性。如果不赋值的话,会使用属性的默认值
4.2 注入null
如果有属性给了初始值,想注入为null,则在property内部需要使用null
标签:
<bean id="student01" class="com.xiao.bean.Student"> <property name="name"> <null/> </property> </bean>
注意,使用value="null"是不对的:
<bean id="student01" class="com.xiao.bean.Student"> <property name="name" value="null"/> </bean>
上面的用法虽然对象的name属性打印出来是null,但是bean.getName()==null是false:
Student bean = ioc.getBean("student01", Student.class); System.out.println(bean); System.out.println(bean.getName()==null); //false 123
结果:
Student{name='null', address=null, books=null, hobbys=null, card=null, games=null, false 1
4.3 注入bean
可以使用ref
引用外部的值:
<!--先注册一个Address对象--> <bean id="address01" class="com.xiao.bean.Address"> <property name="name" value="beijing"/> <property name="num" value="001"/> </bean> <bean id="student02" class="com.xiao.bean.Student"> <!--通过id值引用--> <property name="address" ref="address01"/> </bean>
要注意,ref是严格的引用,通过容器拿到的Address实例就是Student实例中的Address属性
Address address01 = ioc.getBean("address01", Address.class); Student student02 = ioc.getBean("student02", Student.class); System.out.println(student02); System.out.println(student02.getAddress() == address01); //true
也可以引用内部bean,在property
标签体中再定义bean,这个Address和外面的没有关系,只能内部使用,外面获取不到:
<bean id="student03" class="com.xiao.bean.Student"> <property name="address"> <bean class="com.xiao.bean.Address"> <property name="name" value="tianijng"/> <property name="num" value="002"/> </bean> </property> </bean>
4.3 集合类型赋值
01 数组
array
标签+value
标签:
<property name="books"> <array> <value>西游记</value> <value>红楼梦</value> <value>水浒传</value> </array> </property>
02 List
list
标签+value
标签:
<property name="hobbys"> <list> <value>玩游戏</value> <value>看电影</value> </list> </property> 123456
03 Map
map
标签+entry
标签,entry
也可以使用ref引用:
<property name="card"> <map> <entry key="中行" value="001"/> <entry key="邮政" value="002"/> <entry key-ref="..." value-ref="...."/> </map> </property>
04 Properties
props
标签:
<property name="info"> <props> <prop key="学号">20190604</prop> <prop key="性别">男</prop> <prop key="姓名">小明</prop> </props> </property>
05 util名称空间
util名称空间可以创建集合类型的bean,以便别的地方引用。
头文件约束:
xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation= "http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd" <!--util名称空间 提取出通用的集合--> <util:list id="myList"> <value>玩游戏</value> <value>看电影</value> </util:list> <!--使用ref直接引用util提取出来的集合id即可--> <bean id="student05" class="com.xiao.bean.Student"> <property name="hobbys" ref="myList"/> </bean> 123456789
4.4 级联属性赋值
propetry
标签中的name标签,可以使用级联属性,修改属性的属性,但是原来属性的值会被修改。
<bean id="address01" class="com.xiao.bean.Address"> <property name="name" value="beijing"/> <property name="num" value="001"/> </bean> <bean id="student02" class="com.xiao.bean.Student"> <property name="address" ref="address01"/> <!--将address01中的num属性进行了修改--> <property name="address.num" value="00005"/> </bean>
4.5 继承实现配置信息重用
指定parent属性为要重用的bean的id值,不写的属性就沿用,也可以重写定义属性
<bean id="person01" class="com.xiao.bean.Person"> <property name="lastName" value="zhangsan"/> <property name="age" value="20"/> <property name="email" value="[email protected]"/> <property name="gender" value="0"/> <property name="flag" value="true"/> </bean> <!--parent:要重用的配置信息 --> <bean id="person001" class="com.xiao.bean.Person" parent="person01"> <!--单独修改name属性的值 --> <property name="lastName" value="zhang"/> </bean>
还可以指定属性abstract=“true”,这样的bean只能被用来继承信息,不能获取实例,专门用于被继承。否则会报异常 BeanIsAbstractException
<bean id="person01" class="com.xiao.bean.Person" abstract="true"> <!--使用property标签为Person对象的属性赋值 name:指定属性名 value:指定属性值 --> <property name="lastName" value="zhangsan"/> <property name="age" value="20"/> <property name="email" value="[email protected]"/> <property name="gender" value="0"/> <property name="flag" value="true"/> </bean>
5 bean的一些性质
5.1 bean之间依赖
多个bean的默认创建顺序,是按照配置顺序创建的。
<bean id="student" class="com.xiao.bean.Student"></bean> <bean id="address" class="com.xiao.bean.Address"></bean> <bean id="person" class="com.xiao.bean.Person"></bean> 123 Student创建了 Address创建了 Person创建了 123
可以用depends-on属性进行设置:
<bean id="student" class="com.xiao.bean.Student" depends-on="person,address"></bean> <bean id="address" class="com.xiao.bean.Address"></bean> <bean id="person" class="com.xiao.bean.Person"></bean> 123 Person创建了 Address创建了 Student创建了 123
5.2 bean的作用域scope
在bean配置中可以设置作用域属性scope:
-
singleton: 单例模式,是默认模式。在容器启动完成之前就已经创建好对象保存在容器中了。 懒汉单例任何时候获取都是之前创建好的
-
prototype :多实例,原型模式,容器启动会不去创建,每次从容器中getBean获取的时候才会产生一个新对象,饿汉多实例,每次获取都会创建新对象
-
request:在web环境下,同一次请求创建一个bean实例(没用)
-
session:在web环境下,同一次会话创建一个bean实例(没用)
5.3 静态工厂与实例工厂
工厂模式:工厂帮我们创建对象;有一个专门帮我们创建对象的类,这个类就是工厂
静态工厂:工厂本身不用创建对象,通过静态方法调用,对象 = 工厂类.工厂方法名( )
实例工厂:工厂本身需要创建对象,先创建工厂对象,再通过工厂对象创建所需对象
工厂类 工厂对象 = new 工厂类();
工厂对象.getAirplane("plane01")
新建三个类Air、AirStaticFactory和AirInstanceFactory:
public class Air { private String name; private Double weight; private Double length; private Integer PersonNum; //get/set/toString... } public class AirStaticFactory { //提供一个static静态方法获取Air对象 //AirStaticFactory.getAirPlane(); public static Air getAir(String name){ System.out.println("AirStaticFactory正在造飞机!"); Air air = new Air(); air.setName(name); air.setLength(100.0); air.setWeight(100.0); air.setPersonNum(200); return air; } public class AirInstanceFactory { //提供一个方法获取Air对象 //new AirStaticFactory().getAirPlane(); public Air getAir(String name){ System.out.println("AirInstanceFactory正在造飞机!"); Air air = new Air(); air.setName(name); air.setLength(100.0); air.setWeight(100.0); air.setPersonNum(200); return air; } }
静态工厂:不需要创建工厂本身,class指定静态工厂的全类名,factory-method指定工厂方法
<!--静态工厂,不需要创建工厂本身,class指定静态工厂的全类名--> <!--直接创建会创建AirStaticFactory,我们要告诉bean创建getAir对象 ,factory-method,用constructor-arg有参构造器传参--> <bean id="air01" class="com.xiao.AirStaticFactory" factory-method="getAir"> <constructor-arg name="name" value="林青霞"/> </bean> 1234 //获取到的就是Air的实例 Air air01 = ioc.getBean("air01",Air.class);
实例工厂:先创建示例工厂本身,再创建对象,指定当前对象的创建需要哪个工厂factory-bean和哪个方法factory-method
<!--实例工厂,需要先创建示例工厂本身--> <bean id="airInstanceFactory" class="com.xiao.AirInstanceFactory"> </bean> <!--指定当前对象的创建需要哪个工厂和哪个方法,不需要指定class了,factory-bean当前兑现刚创建那个工厂实例 factory-method指定使用哪个工厂方法--> <bean id="air02" factory-bean="airInstanceFactory" factory-method="getAir"> <constructor-arg name="name" value="张学友"/> </bean> Air air02 = ioc.getBean("air02",Air.class);
5.4 自定义工厂
实现了FactoryBean接口的类,是Spring可以认识的工厂类,Spring都认为是工厂,Spring会自动调用工厂方法创建对象。
public class MyFactoryBeanImpl implements FactoryBean<Air> { //传入泛型为要建造的对象Air //工厂方法,Spring会自动调用这个方法来创建对象并返回 //返回创建的对象Air air @Override public Air getObject() throws Exception { sout("myfactorybean帮你创建对象..") Air air = new Air(); air.setName("zhangsan"); return air; } //返回对象的类型,Spring会自动调用这个方法来确认创建的对象是什么类型 @Override public Class<?> getObjectType() { return Air.class; } //是单例模式吗? @Override public boolean isSingleton() { return false; } }
注册工厂对象,会自动调用工厂方法返回对象:
<!--注册工厂对象,会自动调用工厂方法返回对象--> //MyFactoryBeanImpl会自动调用FactoryBean内getObject()方法返回创建Air对象 <bean id="air03" class="com.xiao.MyFactoryBeanImpl"> </bean> Object air03 = ioc.getBean("air03"); //air03是个Air类
FactoryBean这种类型,无论单/多实例,IOC容器启动时不会创建实例,使用getBean时才会创建
5.5 bean的生命周期方法
可以为bean自定义一些生命周期方法,Spring在创建或销毁bean时调用。init-method
,destroy-method
,不能有参数。
IOC容器中注册的bean:
-
单实例bean:容器启动的时候就会创建好,容器关闭也会销毁创建的bean
(容器启动)构造器 —> 初始化方法 —> (容器关闭)销毁方法
-
多实例bean:获取的时候才去创建
(容器启动)构造器 —> 初始化方法 ,容器关闭不会调用bean的销毁方法
在Air类中新增两个方法:
public class Air { private String name; private Double weight; private Double length; private Integer PersonNum; public void destroy(){ System.out.println("销毁方法被调用了!"); } public void init(){ System.out.println("初始方法被调用了"); } } <bean id="air04" class="com.xiao.Air" init-method="init" destroy-method="destroy"> </bean>
5.6 bean的后置处理器
定义一个类实现BeanPostProcessor接口,其中两个方法postProcessBeforeInitialization
和postProcessAfterInitialization
会在调用初始化方法前后调用。需要注册这个实现类
即使没有定义初始化方法,这两个方法也会被调用。
public class MyBeanPostProcessor implements BeanPostProcessor { /** * 前置处理器,在初始化方法之前调用 * @param bean 传递过来的,将要初始化的bean * @param beanName * @return 经该方法处理之后可以返回一个新的bean * @throws BeansException */ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("【"+beanName+"】将要调用初始化方法了..BeforeInitialization..这个bean是这样的:+【"+bean+"】"); return bean; } /** * 后置处理器,在初始化方法之后调用 * @param bean * @param beanName * @return 经该方法处理后返回给IOC容器保存的bean * @throws BeansException */ @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("【"+beanName+"】初始化方法调用完了..AfterInitialization..这个bean是这样的:+【"+bean+"】"); return bean; } } <bean id="air04" class="com.xiao.Air" init-method="init" destroy-method="destroy"> </bean> <bean id="myBeanPostProcessor" class="com.xiao.MyBeanPostProcessor"/> 123
结果:
【air04】将要调用初始化方法了..BeforeInitialization..这个bean是这样的:+【Air{name='null', weight=null, length=null, PersonNum=null}】 初始方法被调用了 【air04】初始化方法调用完了..AfterInitialization..这个bean是这样的:+【Air{name='null', weight=null, length=null, PersonNum=null}】
6. bean的装配
6.1 Spring管理连接池
配置C3P0的数据库连接池,注册一个ComboPooledDataSource对象即可
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="root"/> <property name="password" value="root"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis"/> <property name="driverClass" value="com.mysql.jdbc.Driver"/> </bean>
6.2 引入外部配置文件 *
单实例:数据库连接池,一个项目一个池
首先新建一个数据库连接池的配置文件db.properties:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=root
需要用到context命名空间:
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"
使用context:property-placeholder location=" ... "
标签导入数据库配置文件db.properties,就可以用$取出对应的属性了:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <context:property-placeholder location="classpath:db.properties"/>
【一个小坑】:Spring内部已经定义过一个username了,${username}就是系统的用户名,所以这里定义的是jdbc.username
6.3 基于XML的自动装配
自动装配是Spring满足bean依赖的一种方式。Spring会在上下文中自动寻找,并给bean自动装配属性。
Spring中的三种装配方式:
-
在xml显示配置
-
在Java中显示配置
-
隐式的自动装配bean
在bean
标签中设置autowire
属性:
<bean id="air05" class="com.xiao.Air" autowire="byName" ></bean> 1
-
autpwire="default/no"
:不自动装配 -
autpwire="byName"
:按照名字,以属性名作为id去容器中找到这个组件,为其赋值;如果找不到就装配null -
autpwire="byType"
:按照类型,以属性的类型作为查找依据去容器中找到这个组件,为其赋值,该类型必须只有一个,否则会报异常NoUniqueBeanDifinetionException;如果找不到就装配null -
autpwire="construction"
:按照构造器进行赋值:先按照有参构造器的参数类型进行装,如果没有就直接为组件装配null即可;如果按照类型有多个,就会把参数名作为id继续匹配,匹配到就自动装配,匹配不到就装配null。不会报错
7. 注解开发 *
7.1 不同层组件
-
通过给bean上添加注解,可以快速的将bean加入到IOC容器中。创建Dao、Service、Controller层所需要用到的注解:
-
某个类添加上任何一个注解都能快速的将这个组件加入到ioc容器管理中
-
@Component
:组件,放在类上,将某个类注册到Spring中,id是类名首字母小写。相当于:<bean id=".." class="..">
-
@Repository
:Dao持久化层 -
@Service
:Service业务逻辑层 BookService -
@Controller
:Controller控制器层。控制器:控制网站跳转逻辑Servlet
-
-
还需要告诉Spring,自动扫描加了注解的组件:添加context名称空间,
<context:component-scan base-package="com.xiao"/>
。还需要有AOP包的依赖。idea不用配置? -
组件的id默认是类名首字母小写,作用于默认是单例,可以修改。
ioc.getBean("id"); id=bookDao
-
@Repository("bookdaohaha") id改写成bookdaohaha
默认是类名首字母小写,但可以改写
@Repository(value = "book") @Scope(value = "prototype") public class BookDao { }
-
@Value
:注入值,注入基本数据类型和String类型数据 -
@Scope
:标注作用域。singleton, prototype…细节:如果注解中有且只有一个属性要赋值时,且名称是value,value在赋值是可以不写。
7.2 context扫描包的配置
指定要扫描的包:
<context:component-scan base-package="com.xiao"/>
指定扫描包时指定排除一些不要的组件:
<context:component-scan base-package="com.xiao"> <!--指定排除不要的组件--> <context:exclude-filter type="..." expression="..."/> </context:component-scan>
-
type="annotation"
:按照注解进行排除,expression
属性中指定要排除的注解的全类名 -
type="assignable"
:按照类名进行排除,expression
属性中指定要排除的类的全类名
只扫描进入指定的组件,默认都是全部扫描进来,use-default-filters
需要设置为false:
<context:component-scan base-package="com.xiao" use-default-filters="false"> <context:include-filter type="..." expression="..."/> </context:component-scan> 123
7.3 Autowired自动装配 *Autowired
1. 基本使用
直接在成员上添加@Autowired
完成自动装配。
Dao层:
@Repository public class BookDao { //声明一个方法 public void savaBook() { System.out.println("保存了一本书!"); } }
Service层,使用注解@Autowired
完成成员BookDao的自动装配,调用dao层的方法:
@Service public class BookService { //使用@Autowired完成成员BookDao的自动装配 @Autowired private BookDao bookDao; public void sava() { sout("bookservice正在调用bookdao保存图书"); bookDao.savaBook(); } }
Controller层,使用注解@Autowired
完成成员BookService的自动装配,调用service层的方法:
https://blog.csdn.net/qq_40126686/article/details/109728660 servlet
通俗来讲servlet专门用来接收客户端的请求,专门接收客户端的请求数据,然后调用底层service处理数据并生成结果 浏览器http请求------》tomcat服务器-------》到达servlet-----》执行doget,dopost方法----》返回数据
@Controller public class BookServlet { @Autowired private BookService bookService; public void doGet() { sout("bookservlet正在调用bookservice保存图书"); bookService.sava(); } }
public class IOCTest{ ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); @Test public void test02(){ ioc.getBean(BookServlet = ioc.getBean(BookServlet.class)); bookServlet.doGet(); } } bookservlet正在调用bookservice保存图书 bookservice正在调用bookdao保存图书 保存了一本书
02 Autowired的执 行流程
@Autowired:Spring会自动的为这个属性赋值,一定是去容器找这个属性对应的组件
@Autowired
可以直接用在属性上,执行流程:
-
首先按照类型去容器中找对应的组件,如果找到一个就赋值,找不到就抛异常;
@Autowired private BookService bookService 变量名:bookService
-
如果有多个类型(bookService,bookServiceExt)匹配时,会使用要注入的对象变量名称作为bean的id,在spring容器查找,找到了也可以注入成功,找不到就报错。
-
结合注解
@Qualifer
,指定一个id,让Spring别使用变量名为id:在自动按照类型注入的基础之上,再按照指定的bean的id去查找。它在给字段注入时不能独立使用,必须和@Autowired
一起使用;但是给方法参数注入时,可以独立使用。
@Autowired
标注的属性如果找不到就会报错,可以指定required属性,找不到就自动装配null
@Autowired(required = false)
03 注解加在方法上
@Autowired
:也可以使用在set方法上,执行流程跟上面一样;
@Qualifer
:还可以用在方法的参数,指定按照哪个id去装配。指定一个名为id,让spring别使用变量名为id
@Nullable
:标记的属性可以null
@Service public class BookService { private BookDao bookDao; @Autowired private void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } public void read() { this.bookDao.readBook(); } }
04 @Resource
@Resource:
直接按照Bean的id注入,是Java自带的注解。执行流程:
j2ee:java的标准
Autowired:是spring的标准
Resouce扩展性更强,因为是java的标准
-
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
-
如果指定了name,则从上下文中查找id匹配的bean进行装配,找不到则抛出异常
-
如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
-
如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
7.4 Spring的单元测试
使用Spring的单元测试,不需要用ioc.getBean()来获取组件了,直接Autowired组件,Spring自动装配
导入依赖:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.0.RELEASE</version> <scope>test</scope> </dependency> <!--Junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
添加注解:
-
@ContextConfiguration:指定Spring配置文件的位置
-
@RunWith:指定用哪种驱动进行单元测试,默认是junit,这里指定用Spring的单元测试模块来执行标了@Test注解的测试方法
/* *@ContextConfiguration:指定Spring配置文件的位置 *@RunWith:指定用哪种驱动进行单元测试,默认是junit,这里指定用Spring的单元测试模块来执行标了@Test注解的测试方法 * */ @ContextConfiguration(locations = "classpath:ApplicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) public class Test02 { @Autowired private BookController bookController; @Test public void test01() { this.bookController.one(); } }
@Autowired好处:不用ioc.getBean();获取组件了,直接Autowired组件为我们自动装配
Spring(二)----动态代理、AOP
Spring基础知识学习笔记(二),内容包括:
-
代理模式:静态代理和动态代理
-
AOP实现:注解实现+配置文件实现
-
切面、通知、切入点、切入点表达式
-
环绕通知
OOP:(Object Oriented Programming) 面向对象编程。
AOP:(Aspect Oriented Programming) 面向切面编程,基于OOP基础之上的编程思想,
在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运算。
应用场景:计算器运行计算方法的时候进行日志记录,不推荐直接在方法内部,修改维护麻烦。
日志记录:系统的辅助功能;
业务逻辑:核心功能; 二者耦合了
希望:在核心功能运行期间,系统的辅助功能自己动态的加上。
参考视频:
B站 尚硅谷雷丰阳大神的Spring、Spring MVC、MyBatis课程
1. 代理模式
1.1 静态代理
静态代理角色分析:
-
抽象角色 : 一般使用接口或者抽象类来实现
-
真实角色 : 被代理的角色
-
代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
-
客户 : 使用代理角色来进行一些操作
案例:房东有房子,交给中介代理,客户直接找中介,中介在租房前后带客户看房子和收中介费。
租房接口Renet:
//抽象角色:租房接口 public interface Rent { public void rent(); }
真实角色房东:Host,实现了Rent接口,可以出租房子
//真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } }
代理角色Proxy:
public class Proxy implements Rent { private Host host; //无参 public Proxy() { } //有参(参数host定义) public Proxy(Host host) { this.host = host; } @Override public void rent() { //中介在出租房屋前带客户看房子 seeHouse(); this.host.rent(); //中介在出租房屋后收中介费 fare(); } public void seeHouse() { System.out.println("带客户看房子"); } public void fare() { System.out.println("收中介费"); } }
客户Client,找中介租房:
public class Client { public static void main(String[] args) { //房东 Host host = new Host(); //中介来代理房东 Proxy proxy = new Proxy(host); //客户找中介,中介出租房屋 proxy.rent(); } }
结果:
带客户看房子 房屋出租! 收中介费
静态代理的好处:
-
使得真实角色更加纯粹,不再去关注一些公共的事情
-
公共的业务由代理来完成,实现了业务的分工
-
公共业务发生扩展时变得更加集中和方便
缺点 :
-
类多了 , 多了代理类 , 工作量变大了 ,开发效率降低
1.2 动态代理
作用:日志记录,解耦
动态代理的角色和静态代理的一样 ,区别是动态代理的代理类是动态生成的 ,静态代理的代理类是提前写好的。
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
-
基于接口的动态代理----JDK动态代理,代理对象和被代理对象唯一能产生的关联就是实现了同一个接口。如果目标对象没有实现任何接口,是无法为其创建代理对象的。
-
基于类的动态代理–cglib
-
java字节码 javassist
JDK动态代理需要两个核心类:Proxy代理和InvocationHandler调用处理程序。
Proxy:
Proxy.newProxyInstance()
方法为目标对象创建代理对象,返回代理对象。三个参数:
-
ClassLoader loader:和被代理对象使用相同的类加载器。
-
Class<?>[] interfaces:和被代理对象具有相同的行为。实现相同的接口。
-
InvocationHandler:如何代理,方法执行器。
InvocationHandler:
调用其invoke()
方法,执行被代理对象的任何方法,都会经过该方法,三个参数:被代理对象、方法、参数
-
Object proxy:被代理的对象,给jdk使用,任何时候都不用动
-
Method method:当前将要执行的目标对象方法
-
Object[] args:执行方法的参数
calculator.getClass().getClassLoader(); Class<?>[] interfaces = calculator.getClass().getInterfaces(); InvocationHandler h = new InvocationHandler(){ //匿名实现 @Override public Object invoke(Object proxy,Method method ,Object[]args ) throws Throwable{ //反射执行目标方法 calculator被代理人 Object result = method.invoke(calculator,args); return result; } }
代码实现:
-
定义一个出租房子的接口Rent
-
房东类实现Rent,具有出租房子的功能
-
定义一个类实现InvocationHandler接口,来创建动态代理对象,增强功能
-
动态代理对象调用方法
/** @Description: 定义一个类实现InvocationHandler接口,来创建动态代理对象 */ public class ProxyInvocationHandler implements InvocationHandler { private Rent rent; //设置要代理的接口 public void setRent(Rent rent) { this.rent = rent; } //声明一个生成代理类的方法 public Object getProxy() { //Proxy.newProxyInstance()传入三个参数:类加载器,类实现的接口,InvocationHandler对象 return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this); } //处理实例,并返回结果 @Override public Object invoke //先看房 seeHouse(); //使用反射机制invoke方法,传入被代理的接口和参数。使用真实对象的方法 Object result = method.invoke(rent, args); fare(); return result; } public void seeHouse() { System.out.println("中介带看房子"); } public void fare(){ System.out.println("中介收费"); } } /** * @Description:测试类 */ public class ProxyTest { public static void main(String[] args) { //真实角色 Host host = new Host(); ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(); //传入要代理的接口 proxyInvocationHandler.setRent(host); //获得代理对象 Rent proxy = (Rent) proxyInvocationHandler.getProxy(); //代理对象使用真实对象的方法,方法被增强了 proxy.rentHouse(); } }
1.3 动态代理实现日志功能
-
定义一个Calculator接口,声明加减乘除方法
-
定义一个MyCalculator类实现Calculator接口,完成方法体
-
定义一个生成代理对象的类CalculatorProxy,获取代理对象
-
重写InvocationHandler的invoke方法,在执行目标方法前后,添加相应的日志输出,也可以处理异常信息
Calculator接口:
public interface Calculator { //加减乘除方法 public int add(int i, int j); public int subtract(int i, int j); public int multiply(int i, int j); public int divide(int i, int j); } 123456789
MyCalculator类:
public class MyCalculator implements Calculator { @Override public int add(int i, int j) { return i + j; } @Override public int subtract(int i, int j) { return i - j; } @Override public int multiply(int i, int j) { return i * j; } @Override public int divide(int i, int j) { return i / j; } }
日志工具类LogUtils:
public class LogUtils { //执行前 public static void before(Method method,Object... args) { System.out.println("【"+method.getName()+"】方法开始执行了,用的参数列表是【"+ Arrays.asList(args)+"】"); } //执行后 public static void after(Method method,Object result) { System.out.println("【"+method.getName()+"】方法执行完成了,计算结果是【"+ result+"】"); } //出现异常 public static void exception(Method method,Exception e) { System.out.println("【"+method.getName()+"】方法出现异常了,异常信息是:"+e.getCause()); } //方法结束 public static void end(Method method) { System.out.println("【"+method.getName()+"】方法最终结束了"); } }
生成代理对象的类CalculatorProxy:
public class CalculatorProxy { /** * Proxy.newProxyInstance() * 为传入的参数对象创建一个动态代理对象 * @param calculator 被代理的对象 * @return */ public static Calculator getProxy(Calculator calculator) { Object proxy = Proxy.newProxyInstance(calculator.getClass().getClassLoader(), calculator.getClass().getInterfaces(), new InvocationHandler() { /** * @param proxy 代理对象,给JDK使用的 * @param method 当前将要执行的目标对象的方法 * @param args 参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { //目标方法执行前 LogUtils.before(method,args); System.out.println("动态代理要帮你执行方法!"); //利用反射执行目标方法 result = method.invoke(calculator, args); //目标方法执行后 LogUtils.after(method,result); } catch (Exception e) { //目标方法出现异常 LogUtils.exception(method,e); } finally { //目标方法结束后 LogUtils.end(method); } //返回值必须返回出去,外界才能拿到真正执行后的返回值 return result; } }); //返回代理对象 return (Calculator) proxy; } }
测试:
public class CalculatorTest { @Test public void test(){ Calculator calculator = new MyCalculator(); Calculator proxy = CalculatorProxy.getProxy(calculator); proxy.add(1,2); proxy.divide(2,0); } } 12345678910
结果:
【add】方法开始执行了,用的参数列表是【[1, 2]】 动态代理要帮你执行方法! 【add】方法执行完成了,计算结果是【3】 【add】方法最终结束了 【divide】方法开始执行了,用的参数列表是【[2, 0]】 动态代理要帮你执行方法! 【divide】方法出现异常了,异常信息是:java.lang.ArithmeticException: / by zero 【divide】方法最终结束了
动态切入:将某段代码(日志)动态(不写死在业务逻辑中)的切入到指定方法(加减乘除)的指定位置(方法的开始、结束)进行运行
代理对象和被代理对象唯一能产生的关联就是实现同一接口,如果没实现任何接口,是无法为他创建代理对象;
Spring实现AOP功能,底层就是动态代理
2. AOP
AOP:(Aspect Oriented Programming) 面向切面编程,将某段代码动态的切入到指定方法的指定位置(方法的开始、结束、异常…)。
使用场景:
-
加日志保存到数据库
-
做权限验证
-
做安全检查
-
做事务控制
2.1 几个专业术语
-
横切关注点:与业务逻辑无关的,但是需要关注的部分,就是横切关注点,方法的开始、返回、异常、结束等。
-
切面(ASPECT)类:在上面例子中相当于自己定义的一个日志工具类。
-
通知(Advice):切面必须要完成的工作,是类中的一个方法。
-
目标(Target):被通知对象。
-
代理(Proxy):向目标对象应用通知之后创建的对象。
-
连接点(JointPoint):每一个方法的每一个位置都是一个连接点
-
切入点(PointCut):切面通知执行的 “地点”,即真正需要执行日志记录的地方
-
切入点表达式:在众多连接点中选出我们感兴趣的地方
2.2 注解实现步骤
需要AOP织入,要导入依赖:
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
try{@Before method.invoke(obj,args); @AfterReturning }catch(e){ @AfterThrowing }finally{ @After }
步骤:
-
将目标类和切面类(封装了通知方法的类)加入到IOC容器中,注解
@Component
,配置文件开启context:component-scan包扫描 -
告诉Spring到底哪个是切面类,在类上注解
@Aspect
-
告诉Spring切面中的方都是何时何地运行,方法上注解
通知注解
-
@Before:在目标方法之前运行;前置通知
-
@After:在目标方法之后运行;后置通知
-
@AfterReturning:在目标方法正常返回之后;返回通知
-
@AfterThrowing:在目标方法抛出异常之后;异常通知
-
@Around:环绕通知
-
-
在注解中写切入点表达式:execution(访问权限符 返回值类型 方法全类名(参数表))
-
配置文件中开启基于注解的AOP功能
AOP名称空间头文件
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd" 123
<!--开启注解支持--> <aop:aspectj-autoproxy/> 12
代码实现:
目标类:
@Component public class MyCalculator implements Calculator { ... } 1234
切面类:
@Aspect @Component public class LogUtils { //执行前 @Before("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))") public static void before() { System.out.println("方法开始执行了"); } //执行后 @AfterReturning("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))") public static void after() { System.out.println("方法执行完成了"); } //出现异常 @AfterThrowing("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))") public static void exception() { System.out.println("方法出现异常了"); } //方法结束 @After("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))") public static void end() { System.out.println("方法最终结束了"); } }
测试,获取到目标对象的bean,执行方法
public class AopTest { ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext.xml"); @Test public void test() { //注意这里是根据接口类型获取的 Calculator bean = ioc.getBean(Calculator.class); System.out.println(bean);//[email protected] System.out.println(bean.getClass());//class com.sun.proxy.$Proxy22 bean.add(1,2); } } 1234567891011121314
配置文件:
<?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" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--包扫描--> <context:component-scan base-package="com.xiao.MyProxy02"/> <!--开启注解支持--> <aop:aspectj-autoproxy/> </beans>
2.3 注解实现的几个细节
01 获取组件
IOC容器中保存的是组件的代理对象。ioc.getBean()中使用的接口类型,也可以用id名
Calculator bean = ioc.getBean(Calculator.class); System.out.println(bean);//[email protected] System.out.println(bean.getClass());//class com.sun.proxy.$Proxy22 123
02 cglib
<aop:aspectj-autoproxy />
有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>
时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
cglib可以为没有实现接口的组件创建代理对象,通过本类类型或者id名获取到:
class com.xiao.MyProxy02.MyCalculator$$EnhancerBySpringCGLIB$$5ef61d8e 1
03 切入点表达式的写法
固定格式:execution(访问权限符 返回值类型 方法全类名(参数表)),表达式中支持 && 、||、 !
"execution(* *.*(..))"
:表示任意返回值类型,任意包下的任意类的任意方法,任意参个数
通配符:
-
*
可以匹配一个或多个字符;匹配一个参数;匹配一层路径;权限位置不写就行 -
..
匹配任意多个参数,任意类型参数,任意多层路径
04 通知方法的执行顺序
正常执行:Before →方法执行 →After → AfterReturning(正常返回)
出现异常:Before →方法执行 →After → AfterThrowing
05 拿到目标方法的详细信息
从JoinPoint对象中可以拿到方法的详细信息,joinPoint.getArgs(),joinPoint.getSignature()
也可以接收异常和返回值,需要自己传入对应的参数Object result、Exception exception,并且要告诉Spring指定返回值returning ,指定异常throwing
//执行前 @Before("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))") public static void before(JoinPoint joinPoint) { System.out.println("【"+ joinPoint.getSignature().getName()+"】方法开始执行了,用的参数列表是【"+ Arrays.asList(joinPoint.getArgs())+"】"); } //执行后 @AfterReturning(value = "execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))",returning = "result") public static void after(JoinPoint joinPoint,Object result) { System.out.println("【"+ joinPoint.getSignature().getName()+"】方法执行完成了,执行结果是【"+ result +"】"); } //出现异常 @AfterThrowing(value = "execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))",throwing = "exception") public static void exception(JoinPoint joinPoint,Exception exception) { System.out.println("【"+joinPoint.getSignature().getName()+"】方法出现异常了,异常信息是:"+exception); } //方法结束 @After("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))") public static void end(JoinPoint joinPoint) { System.out.println("【"+joinPoint.getSignature().getName()+"】方法最终结束了"); } 123456789101112131415161718192021
06 抽取可重用的切入点表达式
自定义一个没有返回值和参数的方法,加上@Pointcut
注解,声明切入点表达式,别的地方可以直接使用其方法名进行引用
@Pointcut("execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))") public static void myPoint(){ } //执行前 @Before("myPoint()") public static void before(JoinPoint joinPoint) { ... } 12345678910
07 环绕通知
@Around
:就是利用反射调用目标方法,可以在其中定义环绕前置、环绕返回、环绕异常和环绕后置通知。环绕通知是优先于普通通知执行的。
环绕通知只作用在自己的切面内。
@Around("myPoint()") public Object myAround(ProceedingJoinPoint point) throws Throwable { //获取参数 Object[] args = point.getArgs(); //获取方法名 String name = point.getSignature().getName(); Object proceed = null; try { // @Before System.out.println("【环绕前置通知】..【" + name + "】方法开始,用的参数列表是" + Arrays.asList(args)); //就是利用反射调用目标方法,类似于method.invoke(obj,args) proceed = point.proceed(args); // @AfterReturning System.out.println("【环绕返回通知】..【" + name + "】方法返回,返回值是" + proceed); } catch (Exception e) { // @AfterThrowing System.out.println("【环绕异常通知】..【" + name + "】方法出现异常,异常信息是" + e); //为了让外界知道这个异常,将其抛出 throw new RuntimeException(e); } finally { // @After System.out.println("【环绕后置通知】..【" + name + "】方法结束"); } //反射调用后的返回值也一定返回出去 return proceed; } 123456789101112131415161718192021222324252627
结果:
【环绕前置通知】..【add】方法开始,用的参数列表是[1, 2] 【add】方法开始执行了,用的参数列表是【[1, 2]】 【环绕返回通知】..【add】方法返回,返回值是3 【环绕后置通知】..【add】方法结束 【add】方法最终结束了 【add】方法执行完成了,执行结果是【3】 123456
执行顺序:
(环绕前置 —> 普通前置) —> 目标方法执行 —> 环绕正常返回/出现异常 —> 环绕后置 —> 普通后置 —> 普通返回或者异常
08 多切面情况
执行顺序按照类名顺序,前置1–>前置2–>目标方法 -->后置2–>后置1
在切面上使用@Order
注解,给一个int值,值越小,优先级越高
2.4 配置文件实现
在容器中注册bean,相当于@component
<aop:config>
:进行配置。
<aop:aspect ref="...">:`指定谁是切面类,相当于`@Aspet
<aop:pointcutid="..." expression="..."
:指定切入点和切入表达式
<aop:before method="..." pointcut-ref="..." >
:指定怎么切入,切在哪里,相当于@Before
等,该标签中也可以指定返回值、异常等信息。
<!--注册bean--> <bean id="logUtils" class="com.xiao.MyProxy02.LogUtils"/> <bean id="myCalculator" class="com.xiao.MyProxy02.MyCalculator"/> <aop:config> <!--自定义切面aspect,ref:要引用的类--> <aop:aspect ref="logUtils"> <!--切入点--> <aop:pointcut id="point" expression="execution(public int com.xiao.MyProxy02.MyCalculator.*(int,int))"/> <!--前置--> <aop:before method="before" pointcut-ref="point"/> <!--返回--> <aop:after-returning method="after" pointcut-ref="point" returning="result" /> <!--异常--> <aop:after-throwing method="exception" pointcut-ref="point" throwing="exception"/> <!--后置--> <aop:after method="end" pointcut-ref="point"/> </aop:aspect> </aop:config>