-
Spring的表达式语言spEL
- Spring表达式语言(SpEL):是一个支持运行时查询和操作对象图的强大表示是语言,是一种可以与一个基于spring的应用程序中的运行时对象交互的东西。总得来说SpEL表达式是一种简化开发的表达式,通过使用表达式来简化开发,减少一些逻辑、配置的编写。
- 语法类似于 EL:SpEL 使用 #{...} 作为定界符 , 所有在大括号中的字符都将被认为是 SpEL , SpEL 为 bean 的属性进行动态赋值提供了便利。
-
通过 SpEL 可以实现:
- 通过 bean 的 id 对 bean 进行引用,用了SpEL在bean标签中可以用value代替ref。
- 可以像EL一样用点运算符调用方法以及对象中的属性。
- 计算表达式的值
- 正则表达式的匹配。
-
SpEL 字面量,意义不大,spring内部本身有数据类型的自动转换机制,直接写值就好了,不必用SqEL,了解:
- 整数:#{8}
- 小数:#{8.8}
- 科学计数法:#{1e4}
- String:可以使用单引号或者双引号作为字符串的定界符号。
- Boolean:#{true}
-
SpEL引用bean , 调用它属性和方法:
- 引用其他对象:#{car}
- 引用其他对象的属性:#{car.price}
- 调用其它方法 , 还可以链式操作:#{person.pet.toString()}
- 调用静态方法静态属性:#{T(java.lang.Math).PI}
- Spring EL 操作List、Map集合取值
-
SpEL支持的运算符号:
- 算术运算符:+,-,*,/,%,^(加号还可以用作字符串连接)
- 比较运算符:< , > , == , >= , <= , lt , gt , eg , le , ge
- 逻辑运算符:and , or , not , |
- if-else 运算符(类似三目运算符):?:(temary), ?:(Elvis)
- 正则表达式:#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}'}
-
Spring中的bean的生命周期
- 生命周期图解:
- spring生命周期:
1、实例化一个Bean--也就是我们常说的new;
2、按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;
3、如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值
4、如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以);
5、如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法);
6、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;
7、如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。
8、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法、;
注:以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton,这里我们不做赘述。
9、当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destroy()方法;
10、最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
- BeanPostProcessor接口演示:对BeanPostProcessor接口,做一个实现类,演示一下这些接口如果要在项目中自定义的话应该怎么用,生命周期的前后置处理方法的执行情况
-
Spring通过工厂方法进行配置
-
在Spring的世界中, 我们通常会利用 xml配置文件 或者 annotation注解方式来配置bean实例!在第一种利用 xml配置文件 方式中, 还包括如下三小类
- 反射模式(我们前面的所有配置都是这种模式)
- 工厂方法模式
- Factory Bean模式
其中反射模式最常见, 我们需要在bean 配置中配置我们需要的bean object的全类名。
上面bean 里面的class属性就是全类名, Spring利用java反射机制创建这个bean object。 -
工厂方法模式在工厂方法模式中, Spring不会直接利用反射机制创建bean对象, 而是会利用反射机制先找到Factory类,然后利用Factory再去生成bean对象。而Factory Mothod的具体使用方式也分两种, 分别是静态工厂方法 和 实例工厂方法。
-
静态工厂方法方式所谓静态工厂方式就是指Factory类不本身不需要实例化, 这个Factory类中提供了1个静态方法来生成bean对象
里面定义了1个静态的bean 容器map. 然后提供1个静态方法根据Car 的id 来获取容器里的car对象。
xml配置:
小结由上面的例子, 静态工厂方法方式是非常适用于作为1个bean容器, 只不过bean集合定义在工厂类里面而不是项目xml配置文件里面。缺点也比较明显, 把数据写在class里面而不是配置文件中违反了我们程序猿的常识和spring的初衷。当然优点就是令人恶心的xml配置文件更加简洁。所以,工厂方法的配置,了解一下就行了,个人建议不要在项目中使用。 -
实例工厂方法方式所谓实例工厂方式也很容易看懂, 就是工厂类里面的getBean 方法不是静态的, 也就是说要先实例1个工厂的对象, 才能依靠这个工厂对象去调用getBean 方法获得bean 对象。
小结:显然,实例化工厂方法比静态工厂方法,要灵活一些,没把数据写死在工厂类里,但是实际开发中,用的最多的还是反射模式!
-
用注解的方式来配置Bean
- 在实际项目开发中,可能使用注解配置Bean,使用的还要广泛一些,因为更方便简洁!
- 什么是注解:
传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事务,这么做有两个缺点:1、如果所有的内容都配置在.xml文件中,那么.xml文件将会十分庞大;如果按需求分开.xml文件,那么.xml文件又会非常多。总之这将导致配置文件的可读性与可维护性变得很低2、在开发中在.java文件和.xml文件之间不断切换,是一件麻烦的事,同时这种思维上的不连贯也会降低开发的效率为了解决这两个问题,Spring引入了注解,通过"@XXX"的方式,让注解与Java Bean紧密结合,既大大减少了配置文件的体积,又增加了Java Bean的可读性与内聚性。 - 注解方式配置:
Spring注解配置初始化对象(<bean>):spring中使用注解配置对象前,要在配置文件中配置context:component-scan 标签告诉spring框架,配置了注解的类的位置配置文件applicationContext.xml:<context:component-scan base-package="cn.example.bean"></context:component-scan>注解说明:Component最初spring框架设计的,后来为了标识不同代码层,衍生出Controller,Service,Repository三个注解 作用相当于配置文件的bean标签,被注解的类,spring始化时,就会创建该对象@Component("user") 给类注解@Service("user") // service层@Controller("user") // web业务层@Repository("user")//dao层@Scope(scopeName="singleton") 等同于配置文件的scope属性@Value(value="188") //给值属性赋值,可以用在方法上或者属性上@Resource(name="car") //给对象赋值,该值car必须要已经声明(在配置文件中已经配置,或者在类对应中已经注解)@PostConstruct //指定该方法在对象被创建后马上调用 相当于配置文件中的init-method属性@PreDestroy //指定该方法在对象销毁之前调用 相当于配置文件中的destory-method属性@Autowired //自动装配对象赋值@Qualifier("car2") 一起使用 告诉spring容器自动装配哪个对象 - 示例:不使用注解
- 使用注解:
也可以:
@Resource的装配顺序:1、@Resource后面没有任何内容,默认通过name属性去匹配bean,找不到再按type去匹配2、指定了name或者type则根据指定的类型去匹配bean3、指定了name和type则根据指定的name和type去匹配bean,任何一个不匹配都将报错
-
@Autowired
-
@Autowired顾名思义,就是自动装配,其作用是为了消除代码Java代码里面的getter/setter与bean属性中的property。当然,getter看个人需求,如果私有属性需要对外提供的话,应当予以保留。因此,引入@Autowired注解,不要忘记配置文件要写
然后才是在JavaBean的属性上加注解:
这里@Autowired注解的意思就是,当Spring发现@Autowired注解时,将自动在代码上下文中找到和其匹配(默认是类型匹配)的Bean,并自动注入到相应的地方去。有一个细节性的问题是,假设此时我把.xml文件的<bean id="monkey" class="cn.ybzy.springtest.Monkey" p:monkeyName="mm"></bean><bean id="tiger" class="cn.ybzy.springtest.Tiger" p:tigerName="tt"></bean>行两行给去掉,再运行,会抛出异常,因为,@Autowired注解要去寻找的是一个Bean,Tiger和 Monkey的Bean定义都给去掉了,Spring容器找不到了自然抛出异常。那么,如果属性找不到对应的对象我不想让Spring容器抛 出异常,而就是显示null,可以吗?可以的,就是将@Autowired注解的required属性设置为false 即可:
- @Autowired接口注入
上面的比较简单,我们只是简单注入一个Java类,那么如果有一个接口,有多个实现,Bean里引用的是接口名,又该怎么做呢?比如有一个Car接口:
这样做的话就会报错,Car接口有两个实现类,Spring并不知道应当引用哪个实现类。这种情况通常有两个解决办法:1、删除其中一个实现类,Spring会自动去base-package下寻找Car接口的实现类,发现Car接口只有一个实现类,便会直接引用这个实现类2、实现类就是有多个该怎么办?此时可以使用@Qualifier注解,指明你要spring装载那个对象:
- @inject:
功能和@Autowired差不多的一个注解@inject,它是jsr330规范的注解,用它的话要导入相应的jar包,推荐使用@Autowired<dependency><groupId>javax.inject</groupId><artifactId>javax.inject</artifactId><version>1</version></dependency>
-
component-scan标签详解
-
base-package: 指定spring扫描注解的类所在的包。当需要扫描多个包的时候,可以使用逗号分隔。如果只希望扫描特定的类,不是扫描包里的所有类的时候,可以使用resource-pattern属性来指定只扫描的包。这标签是需要context的命名空间的。
只是这样配置,上面test可以访问到所有的有注解的对象!加上resource-pattern来指定只扫描的包:
这样配置,除了User的对象,其他都找不到了!
-
子标签<context:exclude-filter type="annotation" expression=""/>配置在不扫描的类,可以有很多个这样的子标签。
这样配置,@controller注解的类的对象就找不到了!
-
子标签<context:include-filter type="annotation" expression=""/>配置要扫描的类,也可以有多个。
除了包含的注解以外的注解的类的对象都找不到了! - 上面都是用的type=annotation,下面再看一下assignable
排除UserDao这个接口以及这个接口的实现类!
-
泛型的依赖注入
- 泛型依赖注入就是允许我们在使用spring进行依赖注入的同时,利用泛型的优点对代码进行精简,将可重复使用的代码全部放到一个类之中,方便以后的维护和修改。同时在不增加代码的情况下增加代码的复用性。
-
Spring的切面编程概述
- AOP:AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,这种散布在各处的与具体业务无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
- 用log4j这个日志框架来看一看这种“大量代码的重复”的情况:
1、log4j日志配置文件log4j.properties:
### set log levels ###log4j.rootLogger = debug , stdout , D### 输出到控制台 ###log4j.appender.stdout = org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.Target = System.outlog4j.appender.stdout.layout = org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern = %n %d %p [%l] %m %n### 输出到日志文件 ###log4j.appender.D = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.D.File = ./log.loglog4j.appender.D.Append = true## 只输出DEBUG级别以上的日志!!!log4j.appender.D.Threshold = DEBUGlog4j.appender.D.layout = org.apache.log4j.PatternLayoutlog4j.appender.D.layout.ConversionPattern = %n %d %p [%l] %m %
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。Spring的AOP就可以实现核心关注点和横切关注点的分离Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB(CGLIB是一个强大的、高性能的代码生成库)AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:1、定义普通业务组件2、定义切入点,一个切入点可能横切多个业务组件3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。
-
基于注解的Spring的AOP代码实现试验
- 基于AspectJ框架来实现Spring的AOP的:
步骤1: 添加jar包<dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>步骤2:写Spring的配置文件,把注解扫面打开,写接口和实现类,有main方法是测试类
步骤3:定义切面类,LoggerAspect
步骤4:spring的配置文件中,让@Before注解起作用
可以看到在指定的切入点,切入了切面类的方法,将分离的日志代码和业务代码在运行时合起来了!
步骤5:继续完善切面类
步骤6:一个方法实现了,扩展到多个方法,用*代替add方法名,表示任意方法:
最后,对@Before注解在解释一下:@Before和它注解的这个方法在AOP里叫:通知(advice),这个我们除了@Before这个叫前置通知(在切入目标方法执行之前执行)外,还有:@After后置通知(在目标方法执行之后执行)@AfterReturning返回通知(在目标方法返回结果之后执行)@AfterThrowing异常通知(在目标方法跑出异常之后执行)@Around环绕通知(围绕着方法执行)@Before注解后的括号的内容叫AspectJ表达式,这里还可进一步使用通配符*
public int 换成 * : 任何修饰符和返回值类型
AopTestImpl换成*,表示cn.ybzy.springdemo包里的所有类
还可用两个点儿表示任意参数
很多个方法都是相同的切入点表达式,可以像提公因式样的提出来:
-
AOP实现后置、返回、异常和环绕通知
- 后置通知: 在切入点的目标方法执行后(无论有异常抛出没的),都会执行这个通知方法!
如果想要在通知方法里访问到目标方法返回的结果,可以用返回通知, - 返回通知:是在目标方法执行之后没有异常,并且返回结果后才执行通知方法:
- 异常通知:当目标对象抛出异常的时候执行通知方法,添加一个div除法方法
- 环绕通知:就是把前面4中通知全给整合在一起,环绕目标方法的所有通知的意味。
使用它必须要求:1、必须要带参数ProceedingJoinPoint类型的参数,这个参数可以直接调用原来的目标方法。2、环绕通知方法必须有返回值,这个反正值就是目标方法的返回值。
当同一个目标方法有多个切面的时候,哪个切面先执行,取决于在切面类上的注解@order(值小的先执行)
-
基于xml配置文件的AOP使用
- 前面的例子中,配置切面类,AopTest类还是用注解,我们主要看有切面的通知方法在xml里该怎么配,方法上的注解删除干净。