一、bean的几种创建方式(IOC)
1. 使用构造函数创建bean
1.1 使用默认构造函数创建bean
即<bean id=.. class=.. />
, 则默认调用无参构造函数来创建bean,并装入容器中. 此时如果类中没有默认构造函数,那么对象将无法被创建
1.2 使用有参构造函数创建bean
1 | <bean id=".." class=".."> |
2. 使用工厂方法创建bean
2.1 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
例如: 我想通过类GameFactory
中的getGame()
方法来创建一个Game对象
那么在配置文件中:
1 | <!--先配置GameFactory类的信息--> |
然后就可以通过”MyGame”这个关键字来获取Game的对象了。
2.2 使用静态工厂方法里的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
依然用StaticGameFactory
的例子来说明,只不过getGame()
方法变为静态方法,那么就应该像如下这样弄.
1 | <bean id="MyGame" factory-bean="cn.xjtu.czf.factory.StaticGameFactory" factory-method="getGame"></bean> |
3. 使用setter方法来创建bean
4. 使用注解来创建bean
1. 标记了@Controller
, @Service
, @Repository
和@Component
的类, 用于创建bean
@Controller
: 表现层@Service
: 业务层@Repository
:持久层1
2
3
4
5/* 等价于 <bean id="aaa" class="包.AAA" /> */
"aaa") (
public class AAA{
...
}
2. 使用配置类@Configuration+@Bean来创建
1 |
|
@Configuration
- 作用:指定当前类是一个配置类
- 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。例如
AnnotationConfigApplicationContext(配置类名.class)
@Bean
- 作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
- 属性:
- name:用于指定bean的id。当不写时,默认值是当前方法的名称
- 细节:
- 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象(bean的id就是形参的名字)。查找的方式和Autowired注解的作用是一样的
- 其中的
initMethod
和destroyMethod
可以分别指派初始化和销毁方法.(但在多实例的时候,销毁方法不由spring管理,由GC管理..)
配置类的导入:
- 平级结构:
使用AnnotationConfigApplicationContext(配置类1.class,配置类2.class,配置类3.class,...)
- 树型结构:
使用AnnotationConfigApplicationContext(根配置类.class)
,然后对于根配置类 使用:@ContextConfiguration(classes = {配置子类.class})
或者@ContextConfiguration(locations = {"classpath:包名/aaa.xml", "classpath*:bbb.xml"})
@Import({配置子类1.class, 配置子类2.class,...})
- 平级结构:
在配置类中使用
properties文件
来初始化配置类中的成员变量(成员变量可以作为初始化bean的变量,从而降耦)@PropertySource
:其中,1
2
3
4
5
6
7
8
9
10
11
12
13
14
"Config.properties") (value =
public class AccountConfig {
"${age}") (
private Integer age = null;
"${money}") (
private Float money = null;
"${name}") (
private String name = null;
"account") (
public Account getAccount(){
return new Account(name, age, money);
}
}Config.properties
的内容为:1
2
3name=czf
age=22
money=1
注入数据的注解
@Autowired
: spring会自动到容器中去寻找当前@Autowired
标注的类型,根据类型来初始化。 但是要注意的是,这里多态也会起作用,如果有多个类实现了同一个接口,而当前变量或对象类型正好是接口类型,那么就会找到多个匹配成功的对象; 然后就会根据变量名去匹配,如果依然匹配不到的话,就会报错; 但我们一般不会把变量名写的和类名一样,因此解决办法就是使用@Qualifier
@Qualifier
在给类的成员变量注入的时候,是不可以单独使用的,必须和@Autowired
在一起使用; 但是在给方法参数注入的时候,是可以单独使用的; 使用方法就是:@Qualifier(value="具体某个类的别名")
, 用输入的类名来填充当前需要填充的变量;@Resource
相当于上面两个的合体, 可以直接单独使用,直接通过 类的别名 来确定给当前变量填充的类型,但要注意,这里接收类的别名的参数是name
不再是value
;@Resource(name="某个类的别名")
- 基本类型和String类型的注入(
@Value(value="数据的值")
)- 可以使用Spring中的el表达式;
- SlEL的写法:${表达式} (配合
@PropertySource
使用)
- 集合类型的注入,只能通过xml来做。
Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解]
- @Resource:
- 可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的;没有能支持@Primary功能没有支持@Autowired(reqiured=false);
- @Inject:
- 需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能;
- @Autowired:Spring定义的; @Resource、@Inject都是java规范
- @Resource:
5. 使用Conditional
注解
Conditional
:按照一定的条件进行判断,满足条件则给容器注册bean
1 |
|
6. 通过BeanFactory来注册Bean
- 使用Spring提供的 FactoryBean(工厂Bean);
- 默认获取到的是工厂bean调用getObject创建的对象
- 要获取工厂Bean本身,我们需要给id前面加一个&,&colorFactoryBean
下面看个示例, 使用实现了FactoryBean接口的ColorFactoryBean类来注册Bean:
1 | //创建一个Spring定义的FactoryBean |
在配置类中:
1 | @... |
7. 想在自定义Bean中获取Spring框架底层的Bean(例如 ApplicationContext)
- 自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx);
- 自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware;
- 把Spring底层一些组件注入到自定义的Bean中;
- xxxAware:功能使用xxxProcessor(继承了);
- ApplicationContextAware==》ApplicationContextAwareProcessor;
三、Bean的初始化和销毁方法
1. 使用Bean(initMethod=”..”, destoryMethod=”..”)
2. 让Bean实现InitializingBean接口和DisposableBean接口
1 |
|
3. 使用JSR250
@PostConstruct
,@PreDestory
@PostConstruct
在bean创建完成并且属性赋值完成;来执行初始化方法@PreDestory
在容器销毁bean之前通知我们进行清理工作1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class Dog{
...
// 对象创建并赋值之后调用
public void init(){
...
}
...
// 容器移除对象之前执行这个方法
public void destory(){
...
}
...
}
4. 在Bean的初始化方法调用前/后的预置方法
BeanPostProcessor接口有2个方法需要去实现:
postProcessBeforeInitialization
:在bean初始化之前执行postProcessAfterInitialization
:在初始化之后执行
对于Bean而言,实现了上面这两个接口,就可以在Bean初始化之前和之后做一些事情.
二、扫描包
扫描包内注解.
1. xml方式扫描
1 | <context:component-scan base-package="包"/> |
2. 注解方式扫描
使用注解类
@ComponentScan
用于通过注解指定spring在创建容器时要扫描的包.我们使用此注解就等同于在xml中配置了:1
<context:component-scan base-package="czf"></context:component-scan>
@Filter
通过excludeFilter
和includeFilter
可以实现在扫描的时候扫描部分或排除部分类.
三、面向切面编程(AOP)
0. 注意!
使用AOP时,一定要导入aspectj!!!解析切入点表达式需要AspectJ Weaver
, 使用环绕通知的ProceedingJoinPoint对象需要AspectJ Runtime
!! 不要忘记了!!
1. spring中基于XML的AOP配置步骤
把通知Bean也交给spring来管理
使用aop:config标签表明开始AOP的配置
使用aop:aspect标签表明配置切面
- id属性:是给切面提供一个唯一标识
- ref属性:是指定通知类bean的Id。
在aop:aspect标签的内部使用对应标签来配置通知的类型
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount()返回值可以使用通配符,表示任意返回值
- com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个......AccountServiceImpl.saveAccount())
包名可以使用..表示当前包及其子包* ..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配: * *...*()参数列表:
- 可以直接写数据类型:
- 基本类型直接写名称
int
- 引用类型写包名.类名的方式
java.lang.String
- 基本类型直接写名称
- 可以使用通配符表示任意类型,但是必须有参数:
- 可以使用..表示有无参数均可,有参数可以是任意类型
- 可以直接写数据类型:
全通配写法:
- * ...*(..)
实际开发中切入点表达式的通常写法:
- 切到业务层实现类下的所有方法
- * com.itheima.service.impl..(..)
1
2
3
4
5
6
7
8
9
10
11<!-- 步骤1 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--步骤2: 配置AOP-->
<aop:config>
<!--步骤3: 配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 步骤4: 配置通知的类型,并且建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
- * com.itheima.service.impl..(..)
- 切到业务层实现类下的所有方法
重用切入点表达式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<!--配置AOP-->
<aop:config>
<!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>环绕切入
1
2
3
4
5
6
7
8
9
10
11
12<aop:config>
<!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置环绕通知 详细的注释请看下面的Logger类中-->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
1 | package com.itheima.utils; |
2. spring中基于注解的AOP配置
扫描注解:
- 在xml中添加
<context:component-scan base-package="com.itheima"></context:component-scan>
来扫描包,或者使用配置类来扫描..
- 在xml中添加
开启spring注解的AOP支持。
- 在xml中添加
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
或者在配置类上加@EnableAspectJAutoProxy
开启spring注解AOP的支持
- 在xml中添加
配置切面
- 在增强类上(如上面的Log类),加
@Aspect
注解..表明该类作为切面,用于增强”切入点”
- 在增强类上(如上面的Log类),加
配置切入点
- 切入点表达式的写法:
1
2
3
4// 下面,pt1()就是切入点表达式"execution(* com.itheima.service.impl.*.*(..))"的id,
// pt1(),函数名加括号,就是id!! 一个字母都不能少!!
"execution(* com.itheima.service.impl.*.*(..))") (
private void pt1(){}
- 切入点表达式的写法:
配置通知:前置通知,后置通知,异常通知,最终通知
- 在增强方法上,加上注解
@Before("切入点表达式id"), @After(..), @AfterReturning(..), @AfterThrowing(..)
这里需要注意的是, 尽量不要使用上面这几个注解来做,因为它们的调用顺序可能会出问题.. 所以尽量还是用xml的方法来做..如果非要使用注解的话,那就用环绕注解来做.
- 在增强方法上,加上注解
配置环绕通知:
- 在增强类中的方法加上
@Around("切入点表达式id")
注解,就说明当前方法是增强方法.
- 在增强类中的方法加上
先来看一个例子:
1. Calculator.java
1 | package czf.bean; |
2. LogAspect.java
1 | package czf.aspect; |
最后再总结一下AOP的使用:
1、导入aop模块;Spring AOP:(spring-aspects)
2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)
3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行;
- 通知方法:
- 前置通知(@Before):logStart:在目标方法(div)运行之前运行
- 后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
- 返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
- 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
- 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
- 通知方法:
4、给切面类的目标方法标注何时何地运行(通知注解);
5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)
[7]、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】
- 在Spring中很多的 @EnableXXX;
AOP三步:
a、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
b、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
c、开启基于注解的aop模式;@EnableAspectJAutoProxy
四、记录一些点
1. 单元测试中使用spring
@ContextConfiguration
这个注解通常与@RunWith(SpringJUnit4ClassRunner.class
)`联合使用用来测试,
@RunWith(SpringJUnit4ClassRunner.class)
用于替换单元测试类的启动器(本身的启动器无法使用spring的IOC)。
@ContextConfiguration(locations={"aaa.xml"})
或 @ContextConfiguration(classes={"配置类.class"})
进行扫描操作,用于获取装有Bean的容器.
下面看个例子:
1 | .class) (SpringJUnit4ClassRunner |
2. 在使用AOP的时候,需要额外依赖aspectjweaver!!!! 别忘记了!!!
3. 如果配置了AOP,那么只有是从容器里拿到的Bean才是被AOP增强过的,AutoWired等其他途径初始化得到的都是原始的Bean!
4. 从容器中使用getBean
获取到的对象, 如果是实现了某个接口的类的话,一定要强制转成其接口类
因为spring用的是代理类,而不是实际的子类
关于代理,百度一下spring 代理,就理解其机制了
就是说,虽然你知道你拿到的是UserServiceImpl类型的对象,但实际上,spring存的是一个Proxy对象,该Proxy指向了一个UserServiceImpl对象,你直接对Proxy进行强制转换自然是失败的。
最后附上一个Spring文档: