spring入门——慕课网
2017-02-06 09:21
141 查看
慕课网,spring入门篇
IOC:控制反转(Inversion of Control)
DI:依赖注入(Dependency Injection)
边看边编写的代码:
http://download.csdn.net/detail/fulq1234/9754355
**2-1:IOC及Bean容器**
1.IOC是接口编程
2.接口:OneInterface
实现类:OneInterfaceImpl
spring-ioc.xml:
测试类:
3.Bean容器初始化
文件
FileSystemXmlApplicationContext context=new FileSystemXmlApplicationContext("F:/workspace/appcontext.xml");
//classpath
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("classpath:spring-context.xml");
Web应用
**2-2:Spring注入方式**
1.设值注入:自动调用类的set方法
构造注入:必须有个构造器,构造器的参数是InjectionDAO
**3-1:Spring Bean装配之Bean的配置项及作用域**
1.Bean配置项
Id
Class
Scope
Constructor arguments
Properties
Autowiring mode
lazy-initialization mode
Initialization/destruction method
Bean的作用域(scope)
singleton:单例,指一个Bean容器中只存在一份,默认使用
prototype:每次请求(每次使用)创建新的,destroy方式不生效
request:每次http请求创建一个实例且仅在当前request内有效
session:同上,每次http请求创建,当前session内有效
global session:基于portlet的web中有效(portlet定义了global session),如果是在web中,同session
**3-2:spring Bean装配之Bean的生命周期**
1.生命周期
定义
初始化
使用
销毁
在网上一个比较好的图片,引用一下
2.初始化
实现org.springframework.beans.factory.InitializingBean接口,覆盖afterPropertiesSet方法
配置init-method
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class Examplebean{
public void init(){}
}
3.销毁
实现org.springframework.beans.factory.DisposableBean接口,覆盖destroy方法
配置destroy-method
4.配置全局默认初始化.销毁方法
**3-3.Spring Bean装配之Aware接口**
1.ApplicationContextAware
BeanNameAware
Java文件
spring-aware.xml
<bean id="moocbeanName" class="com.imooc.aware.MoocbeanName"/>
测试
打印:
MoocbeanName:moocbeanName
MoocbeanName:1607305514
testBeanname:1607305514
**3-4.Spring Bean装配之自动装配(Autowiring)**
No:不做任何操作
byname:根据属性名自动装配
byType:类型,如果没有找到相匹配的bean,则什么事都不发生
Constructor:与byType方式类似,不同之处在于它应用与构造器参数。如果没有找到,那么就抛出异常
正常的是以“设值注入”和“构造注入”,现在不用写这个,用自动装配
<beans ..... default-autowire="byName">
</beans>
**3-5.Spring Bean装配之Resource**
1.针对于资源文件的统一接口
- UrlResource : URL对应的资源,根据一个URL地址即可构建
- ClassPathResource : 获取类路径下的资源文件
- FileSystemResource : 获取文件系统里面的资源
- ServletContextResource : ServletContext封装的资源,用于访问ServletContext环境下的资源
- InputStreamResource : 针对于输入流封装的资源
- ByteArrayResource : 针对于字节数组封装的资源
2.ResourceLoader
所有Application contexts都包含接口ResourceLoader
**4-1.Spring Bean装配之Bean的定义及作用域的注解实现**
1.<context:annotation-config/>:仅会查找在同一个AppliationContext中的Bean注解
@Component,@Repository,@Service,@Controller
@Required
@Autowired
@Qualifier
@Resource
<context:component-scan>包含<context:annotation-config>,通常在使用前者后,不再使用后者
2.默认情况下,类被自动发现并注册bean的条件是:使用@Component,@Repository,@Service,@Controller注解或者使用@Component的自定义注解
如:下面例子的XML配置忽略所有的@Repository注解并用“Stub”代替
type包含:annotation,assignable,aspectj,regex,custom
3.还可以使用use-default-filters="false"禁用自动发现与注册
4,定义Bean
. @Service("myMovieListener")
. 可以自定义bean的命令策略
5,作用域(Scope)
@Scope("prototype")
也可以自定义scope策略,实现ScopeMetadataResolver接口并提供一个无参构造器
6,代理方式
可以使用scoped-proxy属性指定代理,有三个值可选:no,interfaces,targetClass
**4-2.Spring Bean装配之Autowired注解说明-1**
1.@Required注解试用于bean属性的setter方法。
受影响的bean(MovieFinder)属性必须在配置时被填充,
2.@Autowired
可以setter方法
可用于构造器或者成员变量
@Autowired(required=false)
每个类只能有一个构造器被标记为required=true
**4-3.Spring Bean装配之Autowired注解说明-2**
1.可以使用@Autowired注解那些众所周知的解析依赖性接口,比如:BeanFactory,ApplicationContext context,Enviroment,ResourceLoader,ApplicationEventPublisher,
@Autowired是由Spring BeanPostProcessor处理的,所以不能在自己的BeanPostProcessor或BeanFacotryPostProcessor类型应用这些注解,这些类型必须通过xml或者spring的@Bean注解
.可以通过添加注解给需要该类型的数组的字段或方法,以提供ApplicationContext中的所有特定类型的bean
.可以用于装配key为String的Map
.如果希望数组有序,可以让bean实现org.springframework.core.Ordered接口或使用的@Order注解
打印结果:
list...
com.imooc.beanannotation.multibean.BeanImplTwo
com.imooc.beanannotation.multibean.BeanImplOne
map....
beanImplOne com.imooc.beanannotation.multibean.BeanImplOne
beanImplTwo com.imooc.beanannotation.multibean.BeanImplTwo
**4-4.Spring Bean装配之Autowired注解说明-3**
@Qualifier
按类型自动装配可能多个bean实例的情况,可以使用Spring的@Qualifier注解缩小范围(或指定唯一),也可以用于指定单独的构造器参数或方法参数
1.
2.
如果通过名字进行注解注入,主要使用的不是@Autowired(即使在技术上能够通过@Qualifier指定bean的名字)@Resource
3.@Autowired适用于fields,constructors,multi-argument methods这些允许在参数级别使用@Qualifier注解缩小范围的情况
@Resource适用于成员变量,只有一个参数的setter方法,所以在目标是构造器或一个多参数方法时,最好的方式是使用qualifiers
**4-5 spring Bean装配之基于Java的容器注解说明——@Bean**
.@Bean标识一个用于配置和初始化一个由SpringIoC容器管理的新对象的方法,类似于XML配置文件的<bean/>
.可以在Spring的@Component注解的类中使用@Bean注解(仅仅是可以)。
.上一点中,通常使用的是@Configuration
等价于
**4-6 spring Bean装配之基于Java的容器注解说明——@ImportResource和@Value**
等价于
**4-7 spring Bean装配之基于Java的容器注解说明——@Bean和@Scope**
默认@Bean是单例的
@Bean
@Scope(value="session",proxyMode=ScopedProxyMode.TARGET_class)
**4-8 spring Bean装配之基于Java的容器注解说明——基于泛型的自动装配**
使用1
使用2
**4-9. Spring Bean装配之Spring对JSR支持的说明**
@Resource
@Named 使用特定名称进行依赖注入,于@Component是等效的
@Injected
**5-1. Spring AOP基本概念及特点**
AOP:Aspect Oriented Programming的缩写
通过预编译和运行期动态代理实现程序功能的统一维护的一种技术。
预编译
AspectJ
运行期动态代理(JDK动态代理,CGLib动态代理)
SpringAOP JbossAOP
AOP的几个相关概念
切面(Aspect):
连接点(Joinpoint)
通知(Advice):在特定连接点执行的动作
切入点(Pointcut)
引入(Introduction)
目标对象(Target Object)
AOP代理(AOP Proxy)
织入(Weaving)
Advice的类型
前置通知(Before advice)
返回后通知(After returning advice)
抛出异常后通知(After throwing advice)
后通知(After(finally) advice)
环绕通知(Around Advice)
**5-2. 配置切面aspect**
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop>
</aop:config>
**5-3. 配置切入点Pointcut**
pointcut
. execution(public **(..)) 切入点为执行所有public方法时
. execution(* set*(..)) 切入点为执行所有set开始的方法时
. execution(* com.xyz.service.AccountService.*(..) 切入点为执行AccountService类中的所有方法时
. execution(* com.xyz.service..(..)) 切入点为执行com.xyz.service包下的所有方法时
. execution(* com.xyz.service...(..)) 切入点为执行com.xyz.service包及其子包下的所有方法时
. within(com.xyz.service.*)(only in Spring AOP)
. within(com.xyz.service..*(only in Spring AOP) within 用于匹配指定类型内的方法执行
. this(com.xyz.service.AccountService)(only in Spring AOP) this 用于匹配当前AOP代理对象类型的执行方法。
. target(com.xyz.service.AccountService)(only in Spring AOP) target用于匹配当前目标对象类型的执行方法
. args(java.io.Serializable)(only in Spring AOP)
. @Target(org.springframework.tansaction.annotation.Transactional)(only in Spring AOP)
. @within(org.springframework.tansaction.annotation.Transactional)(only in Spring AOP)
. @annotation(org.springframework.tansaction.annotation.Transactional)(only in Spring AOP)
args用于匹配当前执行的方法传入的参数为指定类型的执行方法。
pointcut
. @args(com.xyz.security.Classified)(only in Spring AOP)
. bean(tradeService)(only in Spring AOP)
. bean(*Service)(only in Spring AOP)
**5-4. Advice应用(上)**
public Object around(ProceedingJoinPoint pjp){
//start stopwatch
Object retVal = pjp.proceed();
//stop stopwatch
return retVal;
}
public Object aroundInit(ProceedingJoinPoint pjj,String bizName,int times) throws Throwable{
System.out.println("MoocAspect aroundInit bizName:"+bizName+",times:"+times);
//Start
Object obj= pjj.proceed();
//Stop
return obj;
}
应用举例,记录日志。当执行action类的时候,会记录日志
package com.company.business.admin.core;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.company.business.admin.core.service.IAdminLogManager;
import com.company.common.bean.core.AdminUser;
import com.company.core.util.DateUtil;
/**
* 此切面主要用于切所有action类的所有方法<br/>
* 只要action执行任何方法就会触发该切面
* @since 2016-12-6 16:50
*/
@Component
@Aspect
public class AdminAspect {
@Autowired
private IAdminLogManager adminLogManager;
private Log log = LogFactory.getLog(AdminAspect.class);
@Pointcut("execution (public * com.company.business.admin.dev.*.action.*Controller.*(..))")
public void serviceExecution(){}
@Before("serviceExecution()")
public void startSomething(JoinPoint jp) {
}
@After("serviceExecution()")
public void endSomething(JoinPoint jp) {
AdminUser currUser = UserContext.getCurrentAdminUser();
String username = "未知";
String realname = "未知";
if (currUser != null) {
username = currUser.getUsername();
realname = currUser.getRealname();
}
try {
String methodname = jp.getSignature().getName();
if (!methodname.toLowerCase().contains("list") && !methodname.toLowerCase().contains("show")) {
adminLogManager.add(new AdminLog(username,
realname, //真实姓名
jp.getTarget().getClass().getName(), //操作人操作的controller类
jp.getSignature().getName(), //操作人操作的controller类的方法名
DateUtil.getDateline()//操作时间
));
}
} catch (Exception e) {
log.error("切面插日志异常:" + e.getMessage() ,e);
}
}
}
**5-6. Introductions应用**
. schema-defined aspects只支持singleton model
Fit.java
FitImpl.java
MoocAspect.java
AspectBiz.java
test
输出结果
**5-7. Advisors**
**6-1. Spring AOP API的Pointcut,advice概念及应用**
. NameMatchMethodPointcut根据方法名字进行匹配
. 成员变量:mappedNames,匹配的方法名集合
BeforeAdvice
ThrowsAdvice
AfterReturningAdvice
Interceptor
MethodInterceptor implements Interceptor
引入通知(Introduction advice)作为一个特殊的拦截通知,需要IntroductionAdvisor和IntroductionInterceptore仅适用于类,不能和任何切入点一起使用。
**6-2. ProxyFactoryBean及相关内容(上)**
测试类
运行结果
**6-3. ProxyFactoryBean及相关内容(下)**
Proxying classes
. 前面的例子,如果没有Person接口,这种情况下Spring会使用CGLIB代理,而不是JDK动态代理。
. CGLIB(Code Generation Library)代理的工作原理是在运行时生产目标类的子类,Spring配置这个生成的子类委托方法调用到原来的目标。
. 用*做通配,匹配所有拦截器(Interceptor而不是Advice)加入通知链
简化的proxy定义
抽象属性标记父bean定义为抽象的这样它不能被实例化
**7-1. AspectJ介绍及Ponitcut注解应用**
Spring中配置@AspectJ
确保AspectJ的aspectjweaver.jar库包含在应用程序(版本1.6.8或更高版本)的classpath中
或者是
aspect
@AspectJ切面使用@Aspect注解配置,拥有@Aspect的任何bean将被Spring自动识别并应用
@Aspect注解是不能够通过类路径自动检测发现的,所有需要配合使用@Component注释或者在xml配置bean
pointcut
一个切入点通过一个普通的方法定义来提供,并且用@Pointcut注释,方法返回类型必须是void
execution:匹配方法执行的连接点
within:限制匹配特定类型的连接点
this
target
args
@target
@args
@withiin
@annotation
组合pointcut
切入点表达式可以通过&&,||和!进行组合,也可以通过名字引用切入点表达式。
如:@Pointcut("anyPublicOperation() && inTrading()")
**7-2. Advice定义及实例**
@Component
@Aspect
public class MoocAspect{
@Before("execution(* com.imooc.aop.aspectj.biz.*Biz.*(..))")
public void before(){}
}
//After returning advice
@Aspect
public class AfterReturningExample{
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck(){}
}
或者
@Aspect
public class AfterReturningExample{
@AfterReturning(pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",returning="retVal")
public void doaccessCheck(Object retVal){}
}
After throwing advice
@AfterThrowing
After(finally)advice
@After
Around advice
@Around,通知方法的第一个参数必须是ProceedingJoinPoint类型
@Aspect
public class AroundExample{
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable{
//start stopwatch
Object retVal = pjp.proceed();
//stop stopwatch
return retVal;
}
}
**7-3. Advice扩展**
代码1.
代码2.
代码3.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.NETHOD)
public @interface Auditable{
AuditCode value();
}
代码4.
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable){
AuditCode code = auditable.value();
//...
}
IOC:控制反转(Inversion of Control)
DI:依赖注入(Dependency Injection)
边看边编写的代码:
http://download.csdn.net/detail/fulq1234/9754355
**2-1:IOC及Bean容器**
1.IOC是接口编程
2.接口:OneInterface
实现类:OneInterfaceImpl
spring-ioc.xml:
<bean id="oneInterface" class="com.imooc.ioc.interfaces.OneInterfaceImpl"></bean>
测试类:
OneInterface temp=(OneInterface)ac.getBean("oneInterface");
3.Bean容器初始化
文件
FileSystemXmlApplicationContext context=new FileSystemXmlApplicationContext("F:/workspace/appcontext.xml");
//classpath
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("classpath:spring-context.xml");
Web应用
<web-app> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>context</servlet-name> <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class> <load-on-start>1</load-on-start> </servlet> </web-app>
**2-2:Spring注入方式**
1.设值注入:自动调用类的set方法
<beans> <bean id="injectionService" class="com.imooc.ioc.injection.service.InjectionServiceImpl"> <property name="injectionDAO" ref="injectionDAO"/> </bean> <bean id="injectionDAO" class="com.imooc.ioc.injection.dao.InjectionDAOImpl"/> </beans>
构造注入:必须有个构造器,构造器的参数是InjectionDAO
<bean id="injectionService" class="com.imooc.ioc.injection.service.InjectionServiceImpl"> <constructor-arg name="injectionDAO" ref="injectionDAO"/> </bean>
**3-1:Spring Bean装配之Bean的配置项及作用域**
1.Bean配置项
Id
Class
Scope
Constructor arguments
Properties
Autowiring mode
lazy-initialization mode
Initialization/destruction method
Bean的作用域(scope)
singleton:单例,指一个Bean容器中只存在一份,默认使用
prototype:每次请求(每次使用)创建新的,destroy方式不生效
request:每次http请求创建一个实例且仅在当前request内有效
session:同上,每次http请求创建,当前session内有效
global session:基于portlet的web中有效(portlet定义了global session),如果是在web中,同session
**3-2:spring Bean装配之Bean的生命周期**
1.生命周期
定义
初始化
使用
销毁
在网上一个比较好的图片,引用一下
2.初始化
实现org.springframework.beans.factory.InitializingBean接口,覆盖afterPropertiesSet方法
配置init-method
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class Examplebean{
public void init(){}
}
3.销毁
实现org.springframework.beans.factory.DisposableBean接口,覆盖destroy方法
配置destroy-method
4.配置全局默认初始化.销毁方法
<?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" default-init-method="init" default-destory-method="destroy"> </beans>
**3-3.Spring Bean装配之Aware接口**
1.ApplicationContextAware
BeanNameAware
Java文件
import org.springframework.beans.factory.BeanNameAware; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class MoocbeanName implements BeanNameAware,ApplicationContextAware{ private String name; public void setBeanName(String arg0) { name=arg0; System.out.println("MoocbeanName:"+arg0); } public void setApplicationContext(ApplicationContext arg0) throws BeansException { System.out.println("MoocbeanName:"+arg0.getBean(name).hashCode()); } }
spring-aware.xml
<bean id="moocbeanName" class="com.imooc.aware.MoocbeanName"/>
测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:spring-aware.xml"}) public class MoocApplicationContextTest { @Autowired ApplicationContext ac; @Test public void testBeanname(){ System.out.println("testBeanname:"+ac.getBean("moocbeanName").hashCode()); } }
打印:
MoocbeanName:moocbeanName
MoocbeanName:1607305514
testBeanname:1607305514
**3-4.Spring Bean装配之自动装配(Autowiring)**
No:不做任何操作
byname:根据属性名自动装配
byType:类型,如果没有找到相匹配的bean,则什么事都不发生
Constructor:与byType方式类似,不同之处在于它应用与构造器参数。如果没有找到,那么就抛出异常
正常的是以“设值注入”和“构造注入”,现在不用写这个,用自动装配
<beans ..... default-autowire="byName">
</beans>
**3-5.Spring Bean装配之Resource**
1.针对于资源文件的统一接口
- UrlResource : URL对应的资源,根据一个URL地址即可构建
- ClassPathResource : 获取类路径下的资源文件
- FileSystemResource : 获取文件系统里面的资源
- ServletContextResource : ServletContext封装的资源,用于访问ServletContext环境下的资源
- InputStreamResource : 针对于输入流封装的资源
- ByteArrayResource : 针对于字节数组封装的资源
2.ResourceLoader
所有Application contexts都包含接口ResourceLoader
public interface ResourceLoader{ Resource getResource(String location); } Resource template = ctx.getResource("some/resource/path/myTemplate.txt");//依赖于Application context Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt"); Resource template = ctx.getResource("file:/some/resource/path/myTemplate.txt"); public class MoocResource implements ApplicationContextAware{ private ApplicationContext applicationContext; public void setApplicationContext(ApplicationContext arg0) throws BeansException { this.applicationContext=arg0; } public void resource() throws IOException{ //Resource resource = applicationContext.getResource("classpath:config.txt"); //Resource resource = applicationContext.getResource("file:F:\\eclipse-jee-mars-2-win32-x86_64\\eclipse\\workspace\\Spring\\src\\main\\resources\\config.txt"); //Resource resource = applicationContext.getResource("url:http://www.w3school.com.cn/xml/xml_syntax.asp"); Resource resource = applicationContext.getResource("config.txt");//依赖于Application context,也就是classpath System.out.println("file:"+resource.getFilename()); System.out.println("length:"+resource.contentLength()); } }
**4-1.Spring Bean装配之Bean的定义及作用域的注解实现**
1.<context:annotation-config/>:仅会查找在同一个AppliationContext中的Bean注解
@Component,@Repository,@Service,@Controller
@Required
@Autowired
@Qualifier
@Resource
<context:component-scan>包含<context:annotation-config>,通常在使用前者后,不再使用后者
2.默认情况下,类被自动发现并注册bean的条件是:使用@Component,@Repository,@Service,@Controller注解或者使用@Component的自定义注解
如:下面例子的XML配置忽略所有的@Repository注解并用“Stub”代替
<beans> <context:component-scan base-package="org.example"> <context:include-filter type="regex" expression=".*Stub.*Repository"/> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan> </beans>
type包含:annotation,assignable,aspectj,regex,custom
3.还可以使用use-default-filters="false"禁用自动发现与注册
4,定义Bean
. @Service("myMovieListener")
. 可以自定义bean的命令策略
<beans> <context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator"/> </beans>
5,作用域(Scope)
@Scope("prototype")
也可以自定义scope策略,实现ScopeMetadataResolver接口并提供一个无参构造器
<beans> <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/> </beans>
6,代理方式
可以使用scoped-proxy属性指定代理,有三个值可选:no,interfaces,targetClass
<beans> <context:component-scan base-package="org.example" scoped-proxy="interfaces"/> </beans>
**4-2.Spring Bean装配之Autowired注解说明-1**
1.@Required注解试用于bean属性的setter方法。
受影响的bean(MovieFinder)属性必须在配置时被填充,
public class SimpleListerner{ private MovieFinder m; @Required public void setMovieFinder(MovieFinder m){ thism=m; } }
2.@Autowired
可以setter方法
private MovieFinder m; @Autowired public void setMovieFinder(MovieFinder m){ this.m=m; }
可用于构造器或者成员变量
@Autowired private MovieCatelog movieCatelog; private CustomerPDao cDao; @Autowired public MovieRecommender(CustomerPDao cDao){ this.cDao=cDao; }
@Autowired(required=false)
每个类只能有一个构造器被标记为required=true
**4-3.Spring Bean装配之Autowired注解说明-2**
1.可以使用@Autowired注解那些众所周知的解析依赖性接口,比如:BeanFactory,ApplicationContext context,Enviroment,ResourceLoader,ApplicationEventPublisher,
@Autowired是由Spring BeanPostProcessor处理的,所以不能在自己的BeanPostProcessor或BeanFacotryPostProcessor类型应用这些注解,这些类型必须通过xml或者spring的@Bean注解
.可以通过添加注解给需要该类型的数组的字段或方法,以提供ApplicationContext中的所有特定类型的bean
private Set<MovieCatelog> m; @Autowired public void setMovieCatelogs(Set<MovieCatelog> m){ this.m=m; }
.可以用于装配key为String的Map
.如果希望数组有序,可以让bean实现org.springframework.core.Ordered接口或使用的@Order注解
@Order(2) @Component public class BeanImplOne implements BeanInterface { } @Order(1) @Component public class BeanImplTwo implements BeanInterface { } @Component public class BeanInvoker { @Autowired private List<BeanInterface> list; @Autowired private Map<String,BeanInterface> map; public void say(){ if(null != list){ System.out.println("list..."); for(BeanInterface bean : list){ System.out.println(bean.getClass().getName()); } }else{ System.out.println("List is null!!!"); } if(null!=map && 0!=map.size()){ System.out.println("map...."); for(Map.Entry<String, BeanInterface> entry:map.entrySet()){ System.out.println(entry.getKey()+" "+entry.getValue().getClass().getName()); } }else{ System.out.println("map is null!!!!"); } } } @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:spring-beanannotation.xml"}) public class BeanInvokerTest { @Autowired ApplicationContext ac; @Test public void testMultiBean() { BeanInvoker invoker=(BeanInvoker) ac.getBean("beanInvoker"); invoker.say(); } }
打印结果:
list...
com.imooc.beanannotation.multibean.BeanImplTwo
com.imooc.beanannotation.multibean.BeanImplOne
map....
beanImplOne com.imooc.beanannotation.multibean.BeanImplOne
beanImplTwo com.imooc.beanannotation.multibean.BeanImplTwo
**4-4.Spring Bean装配之Autowired注解说明-3**
@Qualifier
按类型自动装配可能多个bean实例的情况,可以使用Spring的@Qualifier注解缩小范围(或指定唯一),也可以用于指定单独的构造器参数或方法参数
1.
public class MovieRe{ @Autowired @Qualifier("main") private MovieCatalog movieCatelog; //... } public class MovieRe{ private MovieCatalog movicCatelog; @Autowired public void prepare(@Qualifier("main") MovieCatelog movieCatelog){ this.movieCatelog=movieCatelog; } //... }
2.
<beans> <bean class="example.SimpleMovieCatelog"> <qualifier value="main"/> </bean> <bean class="example.SimpleMovieCatelog"> <qualifier name="action"/> </bean> </beans>
如果通过名字进行注解注入,主要使用的不是@Autowired(即使在技术上能够通过@Qualifier指定bean的名字)@Resource
3.@Autowired适用于fields,constructors,multi-argument methods这些允许在参数级别使用@Qualifier注解缩小范围的情况
@Resource适用于成员变量,只有一个参数的setter方法,所以在目标是构造器或一个多参数方法时,最好的方式是使用qualifiers
**4-5 spring Bean装配之基于Java的容器注解说明——@Bean**
.@Bean标识一个用于配置和初始化一个由SpringIoC容器管理的新对象的方法,类似于XML配置文件的<bean/>
.可以在Spring的@Component注解的类中使用@Bean注解(仅仅是可以)。
.上一点中,通常使用的是@Configuration
@Configuration public class AppConfig{ @Bean public MyService myService(){ return new MyServiceImpl(); } }
等价于
<beans> <bean id="myS" class="com.acme.services.MyServiceImpl"/> </beans>
**4-6 spring Bean装配之基于Java的容器注解说明——@ImportResource和@Value**
@Configuration @ImportResource("classpath:/com/acme/jdbc.properties"); public class AppConfig{ @Value("${jdbc.url}" private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource(){ return new DriverManagerDataSource(url,username,password); } }
等价于
<beans> <context:annotation-config/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <!--其作用是加载资源文件--> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
**4-7 spring Bean装配之基于Java的容器注解说明——@Bean和@Scope**
默认@Bean是单例的
@Bean
@Scope(value="session",proxyMode=ScopedProxyMode.TARGET_class)
**4-8 spring Bean装配之基于Java的容器注解说明——基于泛型的自动装配**
@Configuration public class MyConfiguration{ @Bean public StringStore stringStore(){ return new StringStore(); } @Bean public IntegerStore integerStore(){ return new IntegerStore(); } }
使用1
@Autowired private Store<String> s1; @Autowired private Store<Integer> s2;
使用2
@Autowired private List<Store<Integer>> s;
**4-9. Spring Bean装配之Spring对JSR支持的说明**
@Resource
@Named 使用特定名称进行依赖注入,于@Component是等效的
@Injected
**5-1. Spring AOP基本概念及特点**
AOP:Aspect Oriented Programming的缩写
通过预编译和运行期动态代理实现程序功能的统一维护的一种技术。
预编译
AspectJ
运行期动态代理(JDK动态代理,CGLib动态代理)
SpringAOP JbossAOP
AOP的几个相关概念
切面(Aspect):
连接点(Joinpoint)
通知(Advice):在特定连接点执行的动作
切入点(Pointcut)
引入(Introduction)
目标对象(Target Object)
AOP代理(AOP Proxy)
织入(Weaving)
Advice的类型
前置通知(Before advice)
返回后通知(After returning advice)
抛出异常后通知(After throwing advice)
后通知(After(finally) advice)
环绕通知(Around Advice)
**5-2. 配置切面aspect**
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop>
</aop:config>
**5-3. 配置切入点Pointcut**
pointcut
. execution(public **(..)) 切入点为执行所有public方法时
. execution(* set*(..)) 切入点为执行所有set开始的方法时
. execution(* com.xyz.service.AccountService.*(..) 切入点为执行AccountService类中的所有方法时
. execution(* com.xyz.service..(..)) 切入点为执行com.xyz.service包下的所有方法时
. execution(* com.xyz.service...(..)) 切入点为执行com.xyz.service包及其子包下的所有方法时
. within(com.xyz.service.*)(only in Spring AOP)
. within(com.xyz.service..*(only in Spring AOP) within 用于匹配指定类型内的方法执行
. this(com.xyz.service.AccountService)(only in Spring AOP) this 用于匹配当前AOP代理对象类型的执行方法。
. target(com.xyz.service.AccountService)(only in Spring AOP) target用于匹配当前目标对象类型的执行方法
. args(java.io.Serializable)(only in Spring AOP)
. @Target(org.springframework.tansaction.annotation.Transactional)(only in Spring AOP)
. @within(org.springframework.tansaction.annotation.Transactional)(only in Spring AOP)
. @annotation(org.springframework.tansaction.annotation.Transactional)(only in Spring AOP)
args用于匹配当前执行的方法传入的参数为指定类型的执行方法。
pointcut
. @args(com.xyz.security.Classified)(only in Spring AOP)
. bean(tradeService)(only in Spring AOP)
. bean(*Service)(only in Spring AOP)
**5-4. Advice应用(上)**
<beans> <bean id="moocAspect" class="com.imooc.aop.schema.advice.MoocAspect"></bean> <bean id="aspectBiz" class="com.imooc.aop.schema.advice.biz.AspectBiz"></bean> <aop:config> <aop:aspect id="moocAspectAOP" ref="moocAspect"> <aop:pointcut expression="execution(* com.imooc.aop.schema.advice.biz.AspectBiz.*(..))" id="moocPointcut"/> <aop:before method = "before" pointcut-ref = "moocPointcut"/> <aop:after-returning method="afterReturning" pointcut-ref = "moocPointcut"/> <aop:after-throwing method="afterThrowing" pointcut-ref="moocPointcut"/> <aop:after method="after" pointcut-ref="moocPointcut"/> <aop:around method="around" pointcut-ref="moocPointcut"/> <aop:around method="aroundInit" pointcut="execution(* com.imooc.aop.schema.advice.biz.AspectBiz.init(String,int)) and args(bizName,times)"/> </aop:aspect> </aop:config> </beans>
public Object around(ProceedingJoinPoint pjp){
//start stopwatch
Object retVal = pjp.proceed();
//stop stopwatch
return retVal;
}
public Object aroundInit(ProceedingJoinPoint pjj,String bizName,int times) throws Throwable{
System.out.println("MoocAspect aroundInit bizName:"+bizName+",times:"+times);
//Start
Object obj= pjj.proceed();
//Stop
return obj;
}
应用举例,记录日志。当执行action类的时候,会记录日志
package com.company.business.admin.core;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.company.business.admin.core.service.IAdminLogManager;
import com.company.common.bean.core.AdminUser;
import com.company.core.util.DateUtil;
/**
* 此切面主要用于切所有action类的所有方法<br/>
* 只要action执行任何方法就会触发该切面
* @since 2016-12-6 16:50
*/
@Component
@Aspect
public class AdminAspect {
@Autowired
private IAdminLogManager adminLogManager;
private Log log = LogFactory.getLog(AdminAspect.class);
@Pointcut("execution (public * com.company.business.admin.dev.*.action.*Controller.*(..))")
public void serviceExecution(){}
@Before("serviceExecution()")
public void startSomething(JoinPoint jp) {
}
@After("serviceExecution()")
public void endSomething(JoinPoint jp) {
AdminUser currUser = UserContext.getCurrentAdminUser();
String username = "未知";
String realname = "未知";
if (currUser != null) {
username = currUser.getUsername();
realname = currUser.getRealname();
}
try {
String methodname = jp.getSignature().getName();
if (!methodname.toLowerCase().contains("list") && !methodname.toLowerCase().contains("show")) {
adminLogManager.add(new AdminLog(username,
realname, //真实姓名
jp.getTarget().getClass().getName(), //操作人操作的controller类
jp.getSignature().getName(), //操作人操作的controller类的方法名
DateUtil.getDateline()//操作时间
));
}
} catch (Exception e) {
log.error("切面插日志异常:" + e.getMessage() ,e);
}
}
}
**5-6. Introductions应用**
. schema-defined aspects只支持singleton model
<beans> <bean id="moocAspect" class="com.imooc.aop.schema.advice.MoocAspect"></bean> <bean id="aspectBiz" class="com.imooc.aop.schema.advice.biz.AspectBiz"></bean> <aop:config> <aop:aspect id="moocAspectAOP" ref="moocAspect"> <aop:declare-parents types-matching="com.imooc.aop.schema.advice.biz.*(+)" implement-interface="com.imooc.aop.schema.advice.Fit" default-impl="com.imooc.aop.schema.advice.FitImpl"/> </aop:aspect> </aop:config> </beans>
Fit.java
package com.imooc.aop.schema.advice; public interface Fit { void filter(); }
FitImpl.java
package com.imooc.aop.schema.advice; public class FitImpl implements Fit { public void filter() { System.out.println("FitImpl filter."); } }
MoocAspect.java
package com.imooc.aop.schema.advice; import org.aspectj.lang.ProceedingJoinPoint; public class MoocAspect { public void before() { System.out.println("MoocAspect before."); } public void afterReturning() { System.out.println("MoocAspect afterReturning."); } public void afterThrowing() { System.out.println("MoocAspect afterThrowing."); } public void after() { System.out.println("MoocAspect after."); } public Object around(ProceedingJoinPoint pjp) { Object obj = null; try { System.out.println("MoocAspect around 1."); obj = pjp.proceed(); System.out.println("MoocAspect around 2."); } catch (Throwable e) { e.printStackTrace(); } return obj; } public Object aroundInit(ProceedingJoinPoint pjp, String bizName, int times) { System.out.println(bizName + " " + times); Object obj = null; try { System.out.println("MoocAspect aroundInit 1."); obj = pjp.proceed(); System.out.println("MoocAspect aroundInit 2."); } catch (Throwable e) { e.printStackTrace(); } return obj; } }
AspectBiz.java
package com.imooc.aop.schema.advice.biz; public class AspectBiz { public void biz() { System.out.println("AspectBiz biz."); // throw new RuntimeException(); } public void init(String bizName, int times) { System.out.println("AspectBiz init : " + bizName + " " + times); } }
test
package com.imooc.aop.schema.advice.biz; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.imooc.aop.schema.advice.Fit; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:spring-aop-schema-advice2.xml"}) public class AspectBizTest2 { @Autowired ApplicationContext ac; @Test public void testFit() { Fit biz=(Fit)ac.getBean("aspectBiz"); biz.filter(); } }
输出结果
FitImpl filter.
**5-7. Advisors**
<beans> <aop:config> <aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service..(..))"/> <aop:advisor pointcut-ref="businessService" advice-ref="tx-advice"/> </aop:config> <tx:advice id="tx-advice"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> </beans>
**6-1. Spring AOP API的Pointcut,advice概念及应用**
. NameMatchMethodPointcut根据方法名字进行匹配
. 成员变量:mappedNames,匹配的方法名集合
BeforeAdvice
ThrowsAdvice
AfterReturningAdvice
Interceptor
MethodInterceptor implements Interceptor
引入通知(Introduction advice)作为一个特殊的拦截通知,需要IntroductionAdvisor和IntroductionInterceptore仅适用于类,不能和任何切入点一起使用。
**6-2. ProxyFactoryBean及相关内容(上)**
<?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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="bizLogicImplTarget" class="com.imooc.aop.api.BizLogicImpl"/> <bean id="moocBeforeAdvice" class="com.imooc.aop.api.MoocBeforeAdvice"/> <bean id="moocAfterReturningAdvice" class="com.imooc.aop.api.MoocAfterReturningAdvice"/> <bean id="moocThrowsAdvice" class="com.imooc.aop.api.MoocThrowsAdvice"/> <bean id="moocMethodInterceptor" class="com.imooc.aop.api.MoocMethodInterceptor"/> <bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut"> <property name="mappedNames"> <list> <value>sa*</value> </list> </property> </bean> <bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="moocBeforeAdvice"/> <property name="pointcut" ref="pointcutBean"/> </bean> <!-- <bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref bean="bizLogicImplTarget"/> </property> <property name="interceptorNames"> <list> <value>defaultAdvisor</value> <value>moocBeforeAdvice</value> <value>moocAfterReturningAdvice</value> <value>moocThrowsAdvice</value> <value>moocMethodInterceptor</value> </list> </property> </bean> --> <bean id="bizLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value>com.imooc.aop.api.BizLogic</value> </property> <property name="target"> <!-- <ref bean="bizLogicImplTarget"/> --> <bean class="com.imooc.aop.api.BizLogicImpl"/> </property> <property name="interceptorNames"> <list> <value>moocBeforeAdvice</value> <value>moocAfterReturningAdvice</value> <value>moocThrowsAdvice</value> <value>moocMethodInterceptor</value> </list> </property> </bean> </beans>
package com.imooc.aop.api; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class MoocBeforeAdvice implements MethodBeforeAdvice { public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println("MoocBeforeAdvice:"+arg0.getName()+" "+arg1.getClass().getName()); } }
package com.imooc.aop.api; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class MoocAfterReturningAdvice implements AfterReturningAdvice { public void afterReturning(Object returnVal, Method method, Object[] arg2, Object target) throws Throwable { System.out.println("MoocAfterReturningAdvice:"+method.getName()+" "+target.getClass().getName()+" "+returnVal); } }
package com.imooc.aop.api; import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; public class MoocThrowsAdvice implements ThrowsAdvice{ public void afterThrowing(Exception ex) throws Throwable{ System.out.println("afterThrowingAdvice afterThrowing 1"); } public void afterThrowing(Method method,Object[] args,Object target,Exception ex) throws Throwable{ System.out.println("afterThrowingAdvice afterThrowing 2:"+method.getName()+""+target.getClass().getName()); } }
package com.imooc.aop.api; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class MoocMethodInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("MoocMethodInterceptor 1:"+invocation.getMethod().getName()+" "+invocation.getStaticPart().getClass().getName()); Object obj= invocation.proceed(); System.out.println("MoocMethodInterceptor 2:"+invocation.getMethod().getName()+" "+invocation.getStaticPart().getClass().getName()); return obj; } }
package com.imooc.aop.api; public interface BizLogic { String save(); }
package com.imooc.aop.api; public class BizLogicImpl implements BizLogic{ public String save() { System.out.println("BizLogicImpl:BizLogicImpl save."); return "Logic save."; } }
测试类
package com.imooc.aop.api; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:spring-aop-api.xml"}) public class BizLogicImplTest { @Autowired ApplicationContext ac; @Test public void testAA() { BizLogic logic=(BizLogic) ac.getBean("bizLogicImpl"); logic.save(); } }
运行结果
MoocBeforeAdvice:save [Ljava.lang.Object; MoocMethodInterceptor 1:save java.lang.reflect.Method BizLogicImpl:BizLogicImpl save. MoocMethodInterceptor 2:save java.lang.reflect.Method MoocAfterReturningAdvice:save com.imooc.aop.api.BizLogicImpl Logic save.
**6-3. ProxyFactoryBean及相关内容(下)**
Proxying classes
. 前面的例子,如果没有Person接口,这种情况下Spring会使用CGLIB代理,而不是JDK动态代理。
. CGLIB(Code Generation Library)代理的工作原理是在运行时生产目标类的子类,Spring配置这个生成的子类委托方法调用到原来的目标。
. 用*做通配,匹配所有拦截器(Interceptor而不是Advice)加入通知链
<property name="interceptorNames"> <list> <value>global*</value> </list> </property>
简化的proxy定义
抽象属性标记父bean定义为抽象的这样它不能被实例化
<beans> <bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean id="myService" parent="txProxyTemplate"> <property name="target"> <bean class="org.springframework.samples.MySpecialServiceImpl"/> </property> <property name="transactionAttributes"><!--此处可以覆盖txProxyTemplate--> <props> <prop key="get*">PROPAGATTION_REQUIRED,readOnly</prop> <prop key="find*">PROPAGATTION_REQUIRED,readOnly</prop> </props> </property> </bean> </beans>
**7-1. AspectJ介绍及Ponitcut注解应用**
Spring中配置@AspectJ
确保AspectJ的aspectjweaver.jar库包含在应用程序(版本1.6.8或更高版本)的classpath中
@Configuration @EnableAspectJAutoProxy public class AppConfig{}
或者是
<aop:aspectj-autoproxy/>
aspect
@AspectJ切面使用@Aspect注解配置,拥有@Aspect的任何bean将被Spring自动识别并应用
@Aspect注解是不能够通过类路径自动检测发现的,所有需要配合使用@Component注释或者在xml配置bean
pointcut
一个切入点通过一个普通的方法定义来提供,并且用@Pointcut注释,方法返回类型必须是void
execution:匹配方法执行的连接点
within:限制匹配特定类型的连接点
this
target
args
@target
@args
@withiin
@annotation
组合pointcut
切入点表达式可以通过&&,||和!进行组合,也可以通过名字引用切入点表达式。
如:@Pointcut("anyPublicOperation() && inTrading()")
**7-2. Advice定义及实例**
@Component
@Aspect
public class MoocAspect{
@Before("execution(* com.imooc.aop.aspectj.biz.*Biz.*(..))")
public void before(){}
}
//After returning advice
@Aspect
public class AfterReturningExample{
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck(){}
}
或者
@Aspect
public class AfterReturningExample{
@AfterReturning(pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",returning="retVal")
public void doaccessCheck(Object retVal){}
}
After throwing advice
@AfterThrowing
After(finally)advice
@After
Around advice
@Around,通知方法的第一个参数必须是ProceedingJoinPoint类型
@Aspect
public class AroundExample{
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable{
//start stopwatch
Object retVal = pjp.proceed();
//stop stopwatch
return retVal;
}
}
**7-3. Advice扩展**
代码1.
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") public void validateAccount(Account account){}
代码2.
@Pointcut("com.xyz.myapp.SystemArchitecture.dataaccessOperation() && args(acount,..)") private void accountDataAccessOperation(Account account){} @Before("accountDataAccessOperation(account)") public void validateAccount(Account account){}
代码3.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.NETHOD)
public @interface Auditable{
AuditCode value();
}
代码4.
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable){
AuditCode code = auditable.value();
//...
}
相关文章推荐
- 慕课网学习spring入门篇-Spring Bean装配(下)
- Spring入门篇--慕课网笔记
- 慕课网学习spring入门篇-专题一 IOC
- 慕课网学习spring入门篇-AOP基本概念
- 慕课网学习spring入门篇-Spring Bean装配(上)
- Spring入门09 - 属性参考与自动绑定
- Spring入门13 - MessageResource接口
- Spring入门10 - 集合对象注入
- Spring入门18 - DispatcherServlet定义档
- Spring 编程入门十大问题解答
- Spring入门04 - 第一个Spring程序
- Spring入门19 - ModelAndView类别
- Spring入门11 - DataSource注入
- Spring入门15 - Aware相关接口
- Spring入门17 - 第一个Spring MVC Web程序
- Spring入门02 - 控制反转IoC
- Spring入门06 - Bean定义档进阶读取
- Spring入门16 - BeanFactoryPostProcessor接口
- Spring入门03 - 依赖注入DI
- Spring 编程入门十大问题解答(转)