spring的一些笔记
核心容器的两个接口引发的问题
-
ApplicationContext
单例对象适用
它在构建核心容器时,创建对象是使用的是立即加载的方式,只要一读取完配置文件就立即创建对象
-
BeanFactory
多例对象适用
它在构建核心容器时,创建对象是使用的是延时加载的方式,什么时候要使用了,才真正获取对象
spring创建bean的三种方式
-
使用默认构造方法创建
在spring的配置文件中使用bean标签,配以id与class属性,且没有其它属性和标签时,
采用的就是默认构造函数创建bean对象,此时如果类中没有构造函数,则无法创建
<bean id="userdaoService" class="com.Cracker.service.Impl.UserdaoService"></bean>
-
第二种:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<bean id="instanceFactory" class="com.Cracker.factory.InstanceFactory"></bean> <bean id="userdaoService" factory-bean="instanceFactory" factory-method="getUserdaoService"></bean>
-
第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
<bean id="userdaoService" class="com.Cracker.factory.StaticFactory" factory-method="getUserdaoService"></bean>
-
bean的作用范围:
-
bean标签的scope属性:
-
作用:用于指定bean的作用范围
-
取值:常用的就是单例的和多例的
singleton:单例的(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
-
-
-
bean对象的生命周期
-
单例对象
- 出生:当容器创建时对象出生
- 活着:只要容器还在,对象一直活着
- 死亡:容器销毁,对象消亡
- 总结:单例对象的生命周期和容器相同
-
多例对象
- 出生:当我们使用对象时spring框架为我们创建
- 活着:对象只要是在使用过程中就一直活着。
- 死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
-
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype" init-method="init" destroy-method="destroy"></bean>
-
spring依赖注入
-
依赖注入(Dependency Injection):依赖关系的维护
-
IOC的作用:降低程序间的耦合(依赖关系)
-
依赖关系的管理:
以后都交给spring来维护,在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
-
依赖注入能注入的数据:
- 基本类型和String
- 其他bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型
-
注入的方式:
- 使用构造函数提供
- 使用set方法提供
- 使用注解提供
构造函数注入:
-
使用的标签:constructor-arg
-
标签出现的位置:bean标签的内部
-
标签中的属性:
- type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
- index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
- name:用于指定给构造函数中指定名称的参数赋值
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
-
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
-
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
-
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="泰斯特"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="birthday" ref="now"></constructor-arg> </bean> <!-- 配置一个日期对象 --> <bean id="now" class="java.util.Date"></bean>
set方法注入
-
涉及的标签:property
-
出现的位置:bean标签的内部
-
标签的属性
- name:用于指定注入时所调用的set方法名称(不是属性名)
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
-
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
-
弊端:
如果有某个成员必须有值,则获取对象是有可能set方法没有执行。
-
<bean id="userdaoService1" class="com.Cracker.service.Impl.UserdaoService"> <property name="name" value="李四"></property> <property name="age" value="20"></property> <property name="date" ref="date"></property> </bean>
-
使用的是默认构造器,没有,无法生成对象
复杂类型的注入/集合类型的注入
-
用于给List结构集合注入的标签:list,array,set(当然也有单独的标签)
-
用于个Map结构集合注入的标签: map ,props(当然也有单独的标签)
-
结构相同,标签可以互换
<bean id="userdaoServiceOne" class="com.Cracker.service.Impl.UserdaoServiceOne"> <property name="myStrs" > <list> <value> myStrs </value> </list> </property> <property name="myList"> <list> <value> myList </value> </list> </property> <property name="myMap"> <map> <entry key="a"> <value> AAA </value> </entry> </map> </property> <property name="properties"> <props> <prop key="b"> BBB </prop> </props> </property> </bean>
使用注解
-
用于创建对象的: 他们的作用就和在XML配置文件中编写一个
<bean>
标签实现的功能是一样的- Component:
- 作用:用于把当前类对象存入spring容器中
- 属性:value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
- Controller:一般用在表现层
- Service:一般用在业务层
- Repository:一般用在持久层
- 以上三个注解他们的作用和属性与Component是一模一样。他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
- Component:
-
用于注入数据的:他们的作用就和在xml配置文件中的bean标签中写一个
<property>
标签的作用是一样的-
Autowired:
-
作用:
自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
如果Ioc容器中有多个类型匹配时:先按照接口(IOC容器中的value的接口类型)匹配,如果有相同的,再按照id(IOC容器中的key)匹配,没有则报错
-
出现位置:可以是变量上,也可以是方法上
-
细节:在使用注解注入时,set方法就不是必须的了。
-
-
Qualifier:
- 作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以。
- 属性:value:用于指定注入bean的id。
-
Resource:
- 作用:直接按照bean的id注入。它可以独立使用
- 属性:name:用于指定bean的id。
-
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过XML来实现。
-
Value:
-
作用:用于注入基本类型和String类型的数据
-
属性:
value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
SpEL的写法:${表达式}
-
-
-
用于改变作用范围的: 他们的作用就和在bean标签中使用scope属性实现的功能是一样的
- Scope
- 作用:用于指定bean的作用范围
- 属性:value:指定范围的取值。常用取值:singleton,prototype
- Scope
-
和生命周期相关了解:他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的
- PreDestroy:用于指定销毁方法
- PostConstruct:用于指定初始化方法
基于xml的ioc配置扫描注解
-
告知spring要扫描注解的包
<context:component-scan base-package="com"></context:component-scan>
基于注解的ioc配置
-
Configuration注解:
- 作用:指定当前类作为配置类
- 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
-
ComponentScan注解:
-
作用:用于通过注解指定spring在创建容器时要扫描的包
-
属性:
-
value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
我们使用此注解就等同于在xml中配置了:
<context:component-scan base-package="com"></context:component-scan> >
-
-
-
Bean注解:
- 作用:用于把当前的返回值作为bean对象当如spring的ioc容器中
- 属性:
- name:指定bean对象id,不写,默认为方法名
- 细节:当我们使用注解的方法有参数时,spring会去容器中查找是否可用的bean对象,方法与Autowired注解是一样的
-
Import注解:
- 作用:用于导入其它配置类
- 属性:
- value:用于指定其它配置类的字节码。当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
-
PropertySource注解:
- 作用:用于指定properties文件的位置
- 属性:
- value:指定文件的名称和路径。关键字:classpath,表示类路径下
Spring整合junit的配置:
-
导入spring整合junit的jar包:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency>
-
使用junit提供的注解
@RunWith
把junit原有的main方法,替换成spring提供的main方法:@RunWith(SpringJUnit4ClassRunner.class)
-
告知spring的运行器,spring和ioc创建是基于xml还是注解,并说明位置:
@ContextConfiguration:
- locations:指定xml文件的位置,加上classpath关键字,表示在类路径下(不要在文件名前加上空格,或许会报错)
- classes:指定注解类所在地位置
动态代理
-
特点:字节码随用随创建,随用随加载
-
作用:不修改源码的基础上对方法增强
-
分类:
-
基于接口的动态代理(涉及的类:Proxy;提供者:JDK官方)
-
如何创建代理对象:使用Proxy类的newProxyInstance方法
-
创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用
-
newProxyInstance方法的参数
-
ClassLoader:类加载器
用来加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法
-
Class[]:字节码数组
用来让代理对象与被代理对象有相同的方法。固定写法。
-
InvocationHandler:用于提供增强的代码
它是让我们如何写代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写
-
实例:
package proxy; /** * 对厂家要求的接口 */ public interface IProducer { /** * 销售 * @param money */ public void saleProduct(float money); /** * 售后 * @param money */ public void afterService(float money); } package proxy.Impl; import proxy.IProducer; /** * 一个生产者 */ public class Producer implements IProducer { /** * 销售 * @param money */ public void saleProduct(float money) { System.out.println("销售产品,并拿到钱:"+money); } /** * 售后 * @param money */ public void afterService(float money) { System.out.println("提供售后服务,并拿到钱:"+money); } } package proxy; import proxy.Impl.Producer; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); IProducer proxyProducer = (IProducer)Proxy.newProxyInstance( producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 执行被代理对象的任何接口方法都会经过该方法 * @param proxy 代理对象的引用 * @param method 当前执行的方法 * @param args 当前执行的方法所需参数 * @return 和被代理对象方法具有相同的返回值 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提供增强的代码 Object returnValue = null; //获取方法执行的参数 Float money = (Float) args[0]; //判断当前方法是不是销售 if ("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer,money*0.8f); } return returnValue; } }); proxyProducer.saleProduct(10000f); } }
-
-
-
基于子类的动态代理(涉及的类:Enhancer;提供者:第三方cglib库)
-
如何创建代理对象:使用Enhancer类中的create方法
-
创建代理对象的要求:被代理类不能是最终类
-
create方法的参数
-
Class:字节码
它是用于指定被代理对象的字节码。
-
Callback:用于提供增强的代码
它是让我们如何写代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。我们一般写的都是该接口的子接口实现类:MethodInterceptor
-
实例:
package cglib; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import proxy.Impl.Producer; import java.lang.reflect.Method; /** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); Producer cglibProducer = (Producer)Enhancer.create( producer.getClass(), new MethodInterceptor() { /** * 执行被代理对象的任何接口方法都会经过该方法 * @param o 代理对象的引用 * @param method 当前执行的方法 * @param objects 当前执行的方法所需参数 * @param methodProxy 当前执行方法的代理对象 * @return 和被代理对象方法具有相同的返回值 * @throws Throwable */ public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //提供增强的代码 Object returnValue = null; //获取方法执行的参数 Float money = (Float) objects[0]; //判断当前方法是不是销售 if ("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer,money*0.8f); } return returnValue; } }); cglibProducer.saleProduct(20000f); } }
-
-
-
spring中基于xml的aop配置
-
把通知bean也交给spring来管理
-
使用aop:config标签来表明开始aop配置
-
使用aop:aspect标签来表明配置切面
- id属性:唯一标识一个切面
- ref属性:指定通知类的id
-
在aop:aspect标签内使用对应标签配置通知的类型
-
aop:before:表示配置前置通知
-
method属性:用于指定通知类中哪个方法作为前置通知
-
pointcut属性:用于指定切入点表达式,该表达式的含义是指对业务层的哪些方法增强
-
切入表达式的写法:
- 关键字:execution(表达式)
-
-
表达式:访问修饰符 返回类型 包名.包名…类名.方法名(参数列表)
-
标准的表达式写法:
public void com.Cracker.service.impl.UserService.saveUser()
- 访问修饰符可以省略:
void com.Cracker.service.impl.UserService.saveUser()
- 返回值可以使用通配符,表示任意返回值
* com.Cracker.service.impl.UserService.saveUser()
- 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*
* *.*.*.*.UserService.saveUser()
- 包名可以使用…表示当前包及其子包
* *…UserService.saveUser()
- 类名和方法名都可以使用*来实现通配
* *…*.*()
- 参数列表:
1.可以直接写数据类型:
基本类型直接写名称:int
引用类型直接写包名.类名的方式:java.lang.String
2.可以使用通配符表示任意类型,但是必须要有参数
3.可以使用…表示有无参数均可,有参数可以说任意类型
4.全通配写法:
* *…*.*(…)
5.实际开发中切入点表达式的通常写法:
切入到业务层实现类下的所有方法:
* com.Cracker.service.impl..(…)
-
aop:after-running:后置通知
-
aop:after-throwing:异常通知
-
aop:after:最终通知
-
aop:aroud:环绕通知
-
配置切入点表达式
id属性用于指定表达式唯一标识;expression属性用于指定表达式内容;
写在aspect里面只能在aspect里生效,写在外面,全局生效;
如果报错,看约束,是否要求写在aspect标签之前。
-
-
环绕通知:
-
问题:
当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
-
分析:
通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
-
解决:
Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),
此方法就相当于明确调用切入点方法。
该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 -
spring中的环绕通知:
它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
-
spring中基于注解的aop配置
-
配置spring开启注解aop的支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
@Aspect:表明这是一个通知类
-
@Pointcut:配置切入表达式:
@Pointcut("execution(* com.Cracker.service.impl.*.*(..))") private void ptOne(){}
-
@Before:配置前置通知
@Before(value = "ptOne()") public void beforePrintLog(){ System.out.println("Logger类中的beforePrintLog方法开始记录日志了。。。"); }
-
@AfterReturning:后置通知
-
@AfterThrowing:异常通知
-
@After:最终通知
-
@Around:环绕通知
-
注意:
spring的aop注解执行顺序有点问题,先执行Before,再执行After,然后再执行AfterReturning和AfterThrowing,所以想要用spring的aop的注解方式,建议用环绕通知