- 异常收集
- IOC:
- 常用实现类:
- Spring对bean的管理细节:
- Spring依赖注入
- 基于注解的IOC配置
- AOP
- Maven依赖配置文件.xml
- Spring配置文件.xml
- AOP标签:(基于xml的AOP配置)
- 基于注释的AOP配置
- Spring中的JdbcTemplate
- Spring提供的事务控制
- 基于xml+注解的实现
- java动态代理知识
概念:Spring是分层的JavaSE\EE轻量级开发框架。以IOC(Inverse Of Control,控制反转)和AOP(Aspect Oriented Programming,面向切面编程)为内核。
名词说明:
- bean:可重用组件。通常指被依赖、被反复调用的方法或类。也是Spring实现IOC的理念:通过管理bean的自动创建而进行类(方法)间解耦。
耦合和解耦:
耦合:类和方法之间的依赖。
解耦:降低程序间的依赖关系。
实际开发中:为保证类的独立性,应做到编译期不依赖,运行时才依赖。大概思路如下:
-
使用反射而非new创建对象:
如在java中连接mysql,首步是注册驱动。
此时应该用
Class.forName("com.mysql.cj.jdbc.Driver")
(依赖于字符串)而非
DriverManager.regesiterDriver(new com.mysql.cj.jdbc.Driver())
(依赖于一个类); -
通过读取配置文件来获取类的全限定名:
上述方法中,使用了固定字符串,其结果是想要使用别的数据库时,需要修改而非拓展。其耦合性还是蛮强,如果使用配置文件获取全限定名的方式,即可更好地解决。
如果可以的话,使用单例模式。
核心容器内容(Core Container)
IOC:
作用:降低类之间的依赖关系、耦合强度。
通用定义:将创建对象的权利交给框架(自己也可写代码可以用工厂模式实现),降低本类或方法,对依赖类的耦合性。
Spring中的IOC:
用ApplicationContext接口的实现类来实现。其功能相当于我们自己编写了工厂类、读取配置文件加上控制反转。形成了解耦效果。不过其有更多的拓展功能,更灵活。
前言:对于抽象类、接口等,可以在eclipse中,看其实现类或父类。F4
常用实现类:
-
ClassPathXmlApplicationContext
:可以加载类路径下的xml配置文件 -
FileSystemXmlApplicationContext
:可以加载任意磁盘位置下的xml配置文件(有访问权限) -
AnnotationConfigApplicationContext
:
部分接口的运作区分:
加载类的时机:
-
ApplicationContext
:采用立即加载策略。当读到配置文件时,立刻加载配置文件中所有的类。(单例对象适用,但该接口很智能的,会根据类的属性是单例和多例进行加载策略切换,实际开发中多用该接口的实现类) -
BeanFactory
:采用延迟加载策略。只有到使用时,才加载类(初始化创建)。(多例对象适用)
Bean
在配置文件.xml中:
格式:
标签:
-
:主要的标签,其<>内有多个属性。 -
:位于
标签内部,用作构造函数中的参数注入。详细看下方依赖注入 -
:位于
标签内部,用作set方法的参数注入。-
标签有很多的子标签:(结构相同,标签可以互换) - 给List结构集合注入的标签:
- 、
、 - 通用子标签:
value
- 通用子标签:
- 给Map结构集合注入的标签:
-
子标签:
。
-
-
属性:
id
:每个bean以id来标识,其代表的是一个可重用的组件(类);默认情况下创建的类是单例的。class
:加载该类的路径。scope
:作用范围,一般用于切换单例或者多例模式。init-method
:用于指定该bean初始化的构造函数。destroy-method
:用于指定该bean的析构函数。factory-bean
:用于引用已在核心容器IOC(配置文件.xml)里面创建的bean。其值为id。factory-method
:与前者配合,用于返回一个方法的结果(类),其值为functionName。对于
、
标签的属性:name
:通过形参名字匹配形参。(最常用)index
:通过索引匹配形参。(仅constrctor-arg)type
:通过类型匹配形参。(仅constructor-arg)value
:给基本类型形参赋值,与上面三个相对应。ref
:给类型数据形参赋值,其通过引用已有的
,给形参传入非基础类型的类。
Spring对bean的管理细节:
-
创建bean(需要的类)的三种方式:
-
使用默认构造函数创建:
配置文件:
;spring只能通过默认构造函数来创建对象。如果没有默认构造函数会报错(当创建了带参数的构造函数时,且没有创造不带参的{默认}构造函数时)。
-
使用一个工厂类的方法返回值创建:(多用于jar包中.class文件,无法修改)
配置文件:
;spring会先创建工厂类,并将其作为可重用组件,然后调用其public方法,得到一个返回的建好的目标类。
注意:经实验工厂的方法可以是private,仍然可以正常得到一个返回值(类)。
-
使用一个工厂类的static方法返回值创建:(也是多用于无法修改的jar包)
配置文件:
;如上所述。
-
-
bean对象的作用范围:
说明:用
中的属性scope
来定义作用范围,其有以下取值:- singleton:单例模式(默认)
- prototype(原型):多例模式。其作用如:每次调用ClassPathXmlApplicationContext(“bean.xml”)创建一个新的对象。(此时跟BeanFactory一样,延迟加载)
- request:作用于web应用的请求范围。
- session:作用于web应用的会话范围。
- global-session:作用于集群环境的会话范围。当非集群时,其效果等同session。
-
bean对象的生命周期:
说明:属性scope决定了不同的生命周期。scope:
生命周期:出生、存活、死亡。
- singleton:生命周期与spring框架一样。
- prototype:出生:使用时创建。死亡:当离开其作用域时GC回收销毁。
Spring依赖注入
说明:IOC的目的是降低类间耦合,使用Spring框架来管理依赖,我们只需要在配置文件中说明依赖关系的维护,称为依赖注入。
可注入的类型:三类:
- 基本数据类型包括String
- bean:引用配置文件中已设置的
。
- 复杂类型\集合类型
注入的方式:三种:
-
使用构造函数提供:
如:
优点:如果类被创建,则参数一定以及被设置。不会忘掉提供参数注入,若没有则无法创建。
缺点:当我们在不需要这些使用这些参数时,仍然需要提供。
-
使用set方法提供(与构建函数优缺点相反)(更常用灵活)
先在类中对成员变量,提供相应的set方法。可以在eclipse或idea中用generate set生成。
如:
private String name;
private Set mySet;
private Map myMap;
public void setName(String name) {
this.name = name;
}
public void setMySet(Set mySet) {
this.mySet = mySet;
}
public void setMyMap(Map myMap) {
this.myMap = myMap;
}
然后设置配置文件.xml。
如:
- 优点:当不需要参数时,可以不提供,或者提供部分参数。
- 缺点:如果某成员必须有值,但获取对象时,set方法可能并没有执行。
set方法实现复杂\集合数据的注入:
说明:方法与上类似,就是在配置文件.xml中,在该
里面会存在子标签,分为两类:List标签,和Map标签。标签名字在前文有标注。
注意,下面代码中Map和Prop都是Map结构,其标签可以互换使用。List结构的也一样可以互换。
如:
"111"
"222"
"333"
this is prop1
this is prop2
详情可以看ubuntu eclipes SpringLearn项目。
使用注解提供:看后文。
基于注解的IOC配置
说明:注解所提供的功能效果与上述配置xml文件一样。只是实现方式的不同。
以下根据不同作用介绍不同格式:
创建对象:(放入组件)
注解:四个注解功能几乎完全一样,可以互换,最主要是为了三层对象更清晰。
-
@Component
:一般用于表现该类不属于三层体系。
-
@Controller
:一般用在表明该类是表现层UI
-
@Service
:一般表明该类是业务层Service
-
@Repository
:一般表明该类是数据层DAO
- 配置类的注释中请看下文:配置类
例子:
类中:在java类中,于类体上一行,加上注解@Component("")
,如:
@Component("NewService4")
public class NewService4 {
public void run() {
System.out.println("NewService is running...");
}
}
bean.xml中:
①先配置
主标签的属性如:可在官网文档core中查到;
②导包:导入NewService4所在的包,可以是祖先目录也可以是当前目录。也可以用下方的配置类来代替
标签格式:
...
配置configuration类(*)
①在java文件夹下,新建config包,并在其中新建一个或多个configuration类。
②@Configuration
:注解该类,作用:指明该类是一个配置类。
③@ComponentScan
:指明组件扫描路径。@ComponentScan("com.path")
④@Bean
:在类里使用,将方法的返回值(类),放到容器中,其在容器中的key默认为方法名,可以使用属性指定id名。@Bean(name="IdOfContainner")
当该类有参数时,Spring框架会去容器中查找是否有符合的Bean,方式与注释类型注入一样。
⑤容器类型:
AnnotationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
其他:
- 当要修改bean组件的Scope时,使用
@Scope(...)
- 当有多个配置类时,可以在一个配置类中导入其他配置类:
@Import(OtherConfig.class)
。当使用时,本类是主配置类,而@Import
中导入的类都是子配置类,可以导入多个类,因为@Import
方法中存放的是字节码数组。(父子关系的配置更合理和清晰)
- 使用properties配置如数据库连接之类的信息(properties文件主要用于将key映射为全限定名)
-
@PropertySource
:注释用于指定 .property文件的路径。其值为:@propertySource("classpath:...")
classpath是限定符。
具体看Ubuntu--Eclipse--XmlIOC项目
例子:
@Configuration
@ComponentScan("com.yinghuo.XmlIOC")
public class SpringConfiguration {
@Bean(name="runner")
public QueryRunner createQueryRunner(DataSource ds) {
return new QueryRunner(ds);
}
@Bean(name="DataSource")
@Scope(prototype)
public DataSource createDataSource() {
...
}
}
其他注解:
-
@Before
:可以相当于初始化init-method使用,用于注释对象初始化函数体。
-
@Test
:是junit对方法的注释,表明测试时会执行该方法。
对于纯xml和纯注解的选择(*)
说明:(建议)
- 纯xml:组件多时,其稍显复杂和庞大。
- 纯注解:有时候更加麻烦,比如为了不在配置类中写死数据库这些资料,需要设置properties属性来进行辅助
- 混合:自己写的类用注解更方便,对于jar包中的一些类,采用xml写更省事。两者混合比较好。当然在公司允许个人选择的情况下。
注入数据
注解:
bean类型注入
@Autowired
:自动按照类型注入,用于注入数据功能。
-
@Qualifier
:只能与@Autowired
注解搭配,不能单独使用。
用于直接指定注入的类型:value=beanId(key)
@Qulifies(value="Class的BeanId")
-
@Resource
:相当于前两个配合的功能;
用于类型注入,并直接指定注入的类型。@Resource(name="Id")
-
注意,如果@Resource注解无法识别,是因为jdk11之后完全移除了javax拓展,而不能使用。导入java依赖即可。
javax.annotation
javax.annotation-api
1.3.2
基本数据类型注入:
-
@Value
:用于指定数据的值。同时可以使用Spring的spEL表达式:${表达式}。
集合数据类型注入:只能通过xml配置实现。
注解位置:放在类中需要初始化的成员变量前面。
当配置文件.xml中,已经将该类所属包放入了组件扫描标签
内之后,就不需要多余配置了。
此时Spring框架会在Core Container核心容器中寻找配对,如:
@Component
public class ...
@Autowired
private AccountDAO dao;
核心容器中,存放着Map类型,当单独使用@Autowired
自动注解 注入时,查找顺序:
- ①在容器中寻找Object匹配的对象:如上述注解,如果找到一个
AccountDAO
及其父类或实现接口,且仅有唯一一个,则会将该对象注入数据,初始化dao。
- ②如果找不到匹配的Object对象:虽然不会报错,但如果该类被使用,并且要使用该成员变量时,会出现空指针异常。
- ③存在多个匹配的Object对象:此时会根据变量名和容器中组件的id进行匹配,如果相匹配,则返回该对象。否则报错,存在多个可匹配的对象。
改变作用范围
注解:
@Scope
:仅此一个注解,用于改变作用范围。其取值与之前描述的在bean标签内部的scope属性一样。写在类体前。
生命周期相关
注解:
-
@PostConstruct
:用于指定初始化方法,放在类里的初始化方法前。
-
@PreDestroy
:用于指定销毁方法,放在类里的销毁方法前。
Spring集成Junit
为什么要集成:
0.jpg
怎么集成:
-
@RunWith
:这是Junit提供的注释,使用其,属性中填写JUnit专门为Spring提供的Runner类,即可将main方法替换成Spring的。
-
@ContextConfiguration
:指定Spring的配置文件(填入:classpath = bean.xml或者classes = config.class)。此时Spring的核心容器已经被创建(应该是被RunWith创建),可以直接在Test类中对成员变量进行数据注入。
导入的依赖:spring-test
:其他与spring-context一样。
注意: SpringJUnit4ClassRunner requires JUnit 4.12 or higher.
1.jpg
AOP
说明:
Aspect Oriented Programming,面向切面编程。通过预编译和运行期动态代理实现程序功能统一维护的一种技术。利用AOP可以对业务逻辑各个部分进行隔离,从而使业务逻辑耦合度降低。提高程序的可重用性,和提高开发的效率。
如当某类中,方法之间具有重复的代码段,此时可将其进行分离,从而简化代码,并且增强业务逻辑性。如ubuntu--eclipse--XmlIOC项目的BeanFactory类,实现了事务处理从业务层分离。
java动态代理知识
作用:
在程序运行期间,不修改源码的方式对源码方法功能增强。(拓展开放修改关闭)
优势:
- 减少重复代码
- 提高开发效率
- 维护方便。
Spring中的AOP
利用配置的方法实现aop,精简代码。
相关术语:
Joinpoint(连接点):在spring中指被代理类的所有方法,每一个都是连接点。
Pointcut(切入点):指被代理类中,被过滤增强的方法(连接点)。有的方法只是连接点,但是没有被过滤增强,就不是切入点。
-
Advice(通知):指功能为拦截切入点的方法,如Invoke();及在该方法中,调用切入点前后的方法。如事务处理中的开启事务、提交事务等都称为通知。
- 环绕通知:指invoke这样的代理方法。
- 前置通知:指invoke里面,在调用切入点之前的方法。如开启事务。
- 后置通知:指invoke里面,在调用切入点之后的方法。如提交事务。
- 异常通知:指invoke里面,catch语句中的方法。如回滚事务。
- 最终通知:指invoke里面,finally语句中的方法。如断开数据库连接。
introduction(引介):引介是一种特殊的通知,在不修改源码的前提下,用于运行期增加一个方法或字段。
Target(目标对象):被代理对象
Weaving(织入):实现功能增强的过程,如spring动态运行时,在切入点前面织入前置通知。spring采用动态代理织入,而AspectJ采用编译期织入,和类加载期织入的方法。
Proxy(代理):一个被代理对象,经过织入增强后,返回的结果代理类。使用代理对象调用方法,可以实现增强后的功能。
-
Aspect(切面):切入点pointcut和通知、引介的结合称为切面。
在spring配置时,必须要说清楚切面中的执行步骤、执行条件等。
开发阶段(our jobs)
可以有如下步骤:
- 编写核心业务代码。
- 提取公共代码,制作成通知。
- 在spring中配置文件中,声明切入点与通知的关系(描述切面)。
运行阶段(Spring 框架完成)
- 监控切入点方法。
- 当切入点方法被调用时,动态创建目标对象的代理对象,并根据配置文件,织入通知、引介等。
- 调用代理对象方法。
Maven依赖配置文件.xml
...
org.springframework
spring-context
5.2.5.RELEASE
org.aspectj
aspectjweaver
1.9.5
...
Spring配置文件.xml
首先要导入xmlns:aop的命名空间:可以参考Springframework Core 里面的代码。
然后是将业务类和通知类(包含增强方法的类)当做组件放入核心容器中。
最后将业务类和通知类,进行切面配置。
如下:
AOP标签:(基于xml的AOP配置)
:aop配置主标签,里面包含多个切面(通知类)。
-
:配置切面标签。
属性:
-
id
:标识唯一切面。
-
ref
:索引通知所属类。
-
、
、
、
:用于aspect内部,标识通知方法相对切入点的位置。依次为,前置、后置、异常、最终。
属性:
-
method
:通知类里的方法。
-
pointcut
:其值为切入点表达式:"execution( 访问修饰符 返回值 包名.包名...类名.方法名(参数) )"
-
pointcut-ref
:其值为切入点id索引。
-
:用于aspect内部,用于标识该标签为环绕标签。其提供的功能与jdk动态代理Proxy中的调用处理对象的Invoke方法一样。
功能:提供手动代码的方式,配置前置、后置、异常、最终通知。与Invoke的功能一样。
属性与上述的通知标签一样。
-
方法中:
在其对应的通知方法中,应该要有切入点调用和完整的四种通知。Spring框架以该方法参数的方式提供了切入点方法的调用方法、参数列表等。
通知方法参数:proceedingJoinPoint pjp
通知方法内调用切入点方法:pjp.proceed(pjp.getArgs())
;
通知方法内获取切入点参数:pjp.getArgs()
;
-
:用于标识切入点 、切入点表达式。
属性:
-
id
:用于标识切入点。
-
expression
:用于输入切入点表达式。
说明:该标签可以用于
子标签或者
子标签,是谁的子标签,其可被引用范围即是哪个的范围。
切入点表达式全通配写法:execution(* *..*.*(..))
; (对所有类的所有方法都加入切面配置)
方法:
写法说明:
- ①省略了访问修饰符。(访问修饰符)
- ②任意返回值类型。( 方法返回值)
- ③任意包路径。(包名路径):用*代替包名,有几层包就写几个 *:如三层: *.*.*;或者用 ’*..Sevice‘ 表示:任意包下面的Service类
- ④任意类。(类名):在类名后加
.*(..)
可以代表该类的所有方法。也可以输入指定参数,只允许该参数方法被过滤。
总说:类名、包名、方法名都可以用*
代替;用..
代替所有种类参数(如果用*
代替则必须有且只有一个参数);
-
实际开发中切入点表达式通常写法:业务层实现类下的所有方法,如:
pointcut=execution(* com.yinghuo.springAOP.service.impl.*.*(..))
基于注释的AOP配置
xml配置+注释
-
xml中:
- ①context的命名空间配置好,然后加入
标签进行组件扫描。
- ②用标签声明开启spring注解aop的支持:
-
代码注释:
-
①将所有相关类---被代理类、通知所属类全部加入注释。下述是通知所属类的注释,别忘记@Aspect
@Component
@Aspect
public class Handler{
...
}
-
②在代理类中,创建一个切入点表达式函数。
//类中
@Pointcut("execution(* *..*.*(..))")
public void pct(){}
-
③注释所有通知方法
@Before("pct()") //其他通知类似
public void beforeService() {
...
}
@Around("pct()")
public Object proxyService(ProceedingJoinPoint pjp){
...
}
Spring中的JdbcTemplate
说明:对jdbc进行浅层封装。
功能:提供基础数据库交互。
步骤:
①生成DataSource
,可以用spring提供的DriverManagerDataSource
类。需要参数:驱动器、url、name、password。
②创建JdbcTemplate
类,将上面的DriverManagerDataSource
作为参数,而生成。
以上都可以使用springIOC的方式生成。
常用方法:
JdbcTemplate jdbcTemp = new JdbcTemplate(DataSource ds);
//无返回结果的执行语句
void jdbctemp.execute("mySql 语句");
//不设条件的查询
List JdbcTemp.query("mysql语句",new BeanPropertyRowMapper(被代理类.class));
//有条件查询
List JdbcTemp.query("mysql语句",new BeanPropertyRowMapper(被代理类.class),args...);
spring整合重复代码:
spring用了一个JdbcDaoSupport
抽象类整合了JdbcTemplate
和DriverManagerDataSource
类,我们可以通过继承它,而减少重复代码。但这种情况下,只能使用xml配置bean,而不能用注释了。
Spring提供的事务控制
说明:之前我们自己编写事务控制,并用Spring提供的AOP进行动态代理。但Spring自己也有事务控制,我们只需要配置即可使用。
xml实现
步骤:(可以在后面的bean.xml配置代码中找到)
- ①创建事务管理器
- ②创建事务通知
- ③创建切入点表达式
- ④建立切入点和通知的联系
- ⑤配置事务属性。
依赖:
org.springframework
spring-tx
5.2.5.RELEASE
org.springframework
spring-jdbc
5.2.5.RELEASE
org.aspectj
aspectjweaver
1.9.5
相关类介绍:
-
PlatFormTransactionManager
接口:
-
DataSourceTransactionManager
:常用的实现类。使用spring jdbc(JdbcTemplate)或iBatis实现。全限定名:org.springframework.djbc.datasource.DataSourceTransactionMAnager
-
HibernateTransactionManager
:使用Hibernate实现的事务管理。
xml文件配置:
想看unbuntu--eclipse--AnnotationAOP项目
基于xml+注解的实现
步骤:
- ①xml中,配置事务管理器
- ②xml中,开启注释事务:
- ③然后在被代理类前,注释:
@Transactional(propagation=Propagation.REQUIRED,readOnly=falase)
- ④上述设置是全局型的,对于需要其他属性的切入点方法,需在其方法前注释③中同样的代码,只是属性不同。
java动态代理知识
可以看Ubuntu--eclipse--dynamicProxyStudy项目
可以使用java提供的Proxy类,来对一个基于接口的动态代理。(若是普通类,则需要使用基于子类的动态代理)
final Producer producer = new Producer();
ProducerInterface proxyProducer = (ProducerInterface)Proxy.newProxyInstance
(producer.getClass().getClassLoader(),producer.getClass().getInterfaces(),
new InvocationHandler() {...}
)
如上,Producer
是我自定义的类,其实现了ProducerInterface接口;
而proxyProducer是一个实现了对producer对象动态代理的类。通过Proxy.newProxyInstance()
方法返回值(对象)得到。
以后可利用该proxyProducer
对象来替代produce
对象执行操作,并进行过滤处理。
newProxyInstance()
方法的参数:
- ①对象的类加载器。
- ②对象的接口。
- ③匿名内部类,来动态地生成一个处理类,其要求实现
Invoke(Object proxy, Method method, Object[] args)
方法:在里面进行过滤处理,并返回一个对象。
导入外部依赖包cglib,来实现基于子类的动态代理。
cglib
cglib
2.2.2
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(),
new MethodInterceptor() {
/**
*
* @param proxy
* @param method
* @param args
* 以上三个和基于接口的动态代理的invoke方法一样
* @param methodProxy:当前执行方法的代理对象。
* @return
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {
//对方法调用进行过滤
float remainder = (Float)args[0];
Object returnObj = null;
if(method.getName().equals("saleProduct")) {
returnObj = method.invoke(producer, remainder*0.8f);
}
return returnObj;
}
});
总说:像给对象增加事务支持等全方法步骤相同的功能时,可以使用动态代理类来处理,能够使服务类代码简洁,而且减少服务类编写代码时间。非常有用。
缺点:配置文件中需要配置的更复杂,如基于接口的代理类,和被代理类是同一个类型,类型注入时就不能使用@Autowired
而是要指定名字。使用AOP可以解决这种麻烦。
异常收集
org.springframework.beans.factory.BeanNotOfRequiredTypeException
错误原因:是因为spring默认使用jdk的基于接口的动态代理,所以类型必须为接口类,当然spring注入时,会注入其实现类,但是接收类必须是接口类。
说明:当在测试类里执行时ubuntu--eclipse--AnnotationAOP中的testFind时,出现了如下。
经测试,当在其他main类中,使用如下代码是可以成功运行并实现过滤增强方法:
ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
ServiceInterface s = ac.getBean("Service",ServiceInterface.class);
System.out.println(s.findAccountById(3));
而当将上述中的ServiceInterface改成实现类Service时,却出现同样的错误。
当我将在测试类中的接收类,private Service s
改成private ServiceInterface s
时,就成功了。?