IOC
(一)IOC(控制反转)
1.耦合概念
耦合:
指的就是程序中的依赖关系.包括控制关系,调用关系,数据传递关系,模块儿间联系越多,其耦合性越强,耦合高的时候,独立性就会很差,可重用性就不好。我们在实际开发中应该避免这种耦合。
耦合分类:
1.内容耦合:当一个模块儿直接修改或操作另一个模块的数据时,或者一个模块不通过正常入口而转入另一个模块儿时,这样的耦合被称为内容耦合,内容耦合是最高程度的耦合,应该避免使用之.
2.公共耦合:两个或者两个以上的模块儿共同引用一个全局数据项,这种耦合被称为公共耦合,在具有大量公共耦合的结构中,确定究竟是哪个模块儿给全局变量赋了一个特定的值是十分困难的,
3.外部耦合: 一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表达传递该全局变量的信息,称之为外部耦合.
4.控制耦合:一个模块儿通过接口向另一个模块儿传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合
5.标记耦合: 若一个模块A通过接口向两个模块B和C传递一个公共参数,那么称模块B和C之间存在一个标记耦合.
6.数据耦合:模块之间通过参数来传递数据,那么被称为数据耦合,数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据
7.非直接耦合:两个模块之间没有直接关系,他们之间的联系完全是通过主模块的控制和调用来实现的,
总结
耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应该采用以下原则:
如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合.
在实际开发中我们应该做到:
编译期不依赖, 运行时才依赖
解决思路:
创建对象采用反射的方式来创建对象,而避免使用new关键字,
当我们反射创建对象时,又产生了新的问题:就是反射的全限定类名在类中写死了。
解决写死的问题:通过配置文件来配置(通过读取配置文件来获取要创建的对象的权限定类名)
2.单例多例
多例对象:
每次使用都会创建一个新的,从而就会重新初始化类成员,也就不会有线程安全问题
但是在同一个线程上如果有多个对象操作数据库时,事务问题除外。(遇到此种情况,需要做线程绑定,让每个线程只有一个对象)
单例对象:
在程序中永远只有一个对象。它有线程安全问题,因为多线程访问时,第一个线程改后,会影响到后面访问的线程
例如:ServletContext Servlet Filter Listener
3.IOC(控制反转)的基本概念
在每个框架的中都有一个容器的概念,所谓的容器就是将常用的服务,封装起来,然后,用户只需要遵循一定的规则,就可以达到统一、灵活、安全、方便、快速的目的
具有依赖注入功能的容器,负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖
IOC负责管理你的任意对象,并结合你对对象的描述进行初始化和加强.
比如,对于一个用注解@Controller声明的对象,Spring会认为这个对象是Web Controller ,如果这个对象里面有方法有@RequestMapping注解,则会将客户端发起的HTTP请求转化成java方法调用.
需要注意的是,必须先加载bean.xml才能使用在配置文件的配置,否则无法使用
4.IOC原理
解析xml配置,获取bean class地址,使用Java的反射机制,进行实例化对象,然后把对象返回.
5.将bean加入容器用注解和xml的
别人写好的类我们加入容器就用xml,因为无法在源码加注解.
我们自己写的类,用注解方便.
工作中是结合使用的
6.什么是bean对象
javabean指的是java语言编写的可重用组件
实体类仅仅是javabean之一
dao,service,实体类,控制器都可以说是可重用组件
Spring中的bean
由IoC容器管理的那些组成你应用程序的对象我们就叫它Bean
Bean就是由Spring容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了
7.bean对象(创建和取出)加载Spring上下文
bean默认是单例的 ,可以配置成多例,单例bean声明成员变量是线程不安全的,
单例bean线程不安全分析(有成员变量情况)
1:写线程同步代码 ,缺点可能会导致性能耗损
2:如果你是springbean,可以scope=“prototype” 每次请求多是新对象,就不存在线程安全 缺点可能会导致性能耗损
3:把共享的资源放到方法里,每次方法调用都是新资源(是局部变量嘛) 因此也没线程安全问题了,缺点可能会导致代码复杂度上升
在xml里面创建bean对象
bean:创建对象,存入spring容器
id:bean对象的唯一标识(全局唯一)
class:实现类的全限定类名
scope : 配置spring创建对象的作用域(生命周期)
singleton:单例,容器创建的时候创建对象,容器关闭的时候销毁对象(默认值)
prototype:(多例),当调用方法的时候创建对象,调用结束,垃圾回收
request:创建一个对象,存入request域中
session:创建一个对象,存入session域中
<!-- init-method : 初始化方法
在创建对象之后。立即执行的对象中的方法
* Thread thread = new Thread();
* thread.start();
destroy-method :销毁方法
在spring容器销毁的时候,对象销毁,立即执行的对象中的方法
-->
<bean id=“customerService” scope=“singleton” init-method="init"destroy-method=“destory” class=“cn.service.impl.CustomerServiceImpl”>
从容器里面取出bean对象(取出的是xml的) 下面有注解的
//从容器中获取对象
public static void main(String[] args) {
//1.获取spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext(“bean.xml”);
//2.从容器中获取对象
/**
* getBean:从容器中获取对象
* 根据spring容器中的唯一标识(customerService是唯一标识)
*/
CustomerService customerService = (CustomerService) ac.getBean(“customerService”);
//执行取出来的bean里面的方法
customerService.addCustomer();
}
注解中加载Spring的上下文,
AnnotationConfigApplicationContext
8.Bean的生命周期
singleton 作用域
单例,在加载容器的时候就直接初始化.
当一个bean的 作用域设置为singleton, 那么Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。换言之,当把 一个bean定义设置为singleton作用域时,Spring IOC容器只会创建该bean定义的唯一实例。这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都 将返回被缓存的对象实例,这里要注意的是singleton作用域和GOF设计模式中的单例是完全不同的,单例设计模式表示一个ClassLoader中 只有一个class存在,而这里的singleton则表示一个容器对应一个bean,也就是说当一个bean被标识为singleton时 候,spring的IOC容器中只会存在一个该bean。
2singleton Prototype
多例 每次调用getBean方法的时候都会创建一次,才会初始化.
prototype作用域部署的bean,每一次请求(将其注入到另一个bean中,或者以程序的方式调用容器的 getBean()方法)都会产生一个新的bean实例,相当与一个new的操作,对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责,容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法,而对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。 清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。(让Spring容器释放被singleton作用域bean占用资源的一种可行方式是,通过使用 bean的后置处理器,该处理器持有要被清除的bean的引用。)
singleton request
request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效,配置实例:
request、session、global session使用的时候首先要在初始化web的web.xml中做如下配置:
该生命周期的对象和request生命周期保持一致的
singleton session
session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。
,同Http请求相同,每一次session请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的session请求内有效,请求结束,则实例将被销毁。
9.注解的方式配置IOC
使用前准备
要想使用spring基于注解的配置,此时需要做两件事
第一:在导入约束时,要多导入一个名称空间和它的约束 名称空间是:context
第二:告知spring在创建容器时要扫描的包 context:component-scan
<context:component-scan base-package=“com.lmxi”></context:component-scan>
注入
/**
- Spring基于注解的配置,实现的功能就是下面的xml部分
- 用于创建对象的(把对象放入Spring容器的)
- 它就相当于在xml中写了一个bean标签。
- @Component
-
作用:把当前类创建一个对象,并且存入Spring的IOC容器 相当于配置文件中的<bean id="" class=""/>
-
属性:
-
也就是用getBean()取值的时候需要用到被注解修饰的类名的首字母小写value:用于指定bean的id。当不指定该属性时,默认值是当前类的名称(不是全限定类名),并且需要把首字母改小写。
列如:
@Service
public class AccountServiceImpl implements IAccountService {
-
取值是:
-
app.getBean("accountServiceImpl"); //注意默认取值是被@Service修饰
-
类名的小写
-
由此注解衍生的三个注解
-
@Controller 一般用于表现层对象(Controller层)
-
@Service 一般用于业务层对象(Service层)
-
@Repository 一般用于持久层对象(Mapper层)
-
这3个注解的作用和属性和@Component是一模一样的。他们为我们的开发提供了分层思想中更加精确的语义化支持。
//******************************** -
用于注入数据的(就是从Spring容器里面获取bean注入到修饰的Field上)
-
@Autowired(用来装配Bean) 如果看不懂最底下有图解
-
作用:自动按照类型注入,(是从Spring容器里面取出的.)
当只有唯一的一个数据类型匹配时,直接注入成功.
当一个类型都没匹配上时,则看required属性的取值,为true时报错。
如果超过一个类型匹配了,则会使用变量名称,作为bean的id再次查找,如果能找到,依然可以注入成功。 -
出现的位置很多:但是我们只关注Field和Method上。
-
属性:
-
required:指的是 是否必须注入成功。默认值是true 必须。
-
当使用注解注入时,set方法就不是必须的了,可以删掉。
-
需要注意的是:注入的时候如果注入到类,默认取值就是类的名字首字母小写
-
比如:
-
@Service public class AccountServiceImpl implements IAccountService {
-
取值是:
-
app.getBean("accountServiceImpl"); //注意默认取值是被@Service修饰
-
类名的小写
- @Qualifier(使用前提必须有@Autowired,如果没有就报错)
https://blog.youkuaiyun.com/qq_41489540/article/details/81056918详细介绍 -
作用:在自动按照类型注入的基础之上,再按照bean的id注入。
-
属性:
-
value:用于指定bean的id。
-
注意:
-
在给类成员注入时,它不能独立使用。必须配合@Autowired
-
但是给方法参数注入时,它可以独立使用(待会再讲)
- @Resource
-
作用:直接按照bean的id注入。
-
属性:
-
name:用于指定bean的id。
- 以上三个注解,都是用于注入其他bean类型数据的。不能注入基本类型和String,也不能注入集合类型。
- 集合类型的注入,必须是XML下才可以。在注解中我们每次只能注入一个对象(一个数据)
- @Value
-
作用:用于注入基本类型和String类型数据的
-
属性:
-
value:指定数据的内容。它可以使用SpEL。
-
SpEL: Spring Expression Language spring的el表达式
-
写法:${写表达式} 是从properties文件中获取
-
JSP: el表达式
-
写法:${} 是从四大域获取
-
mybatis: mybatis的el
-
写法:${} 是从properties文件中获取
- 用于改变作用范围的
- @Scope
-
作用:用于指定bean的作用范围
-
属性:
-
value:指定范围的取值。取值是固定的,和xml配置的取值是一样的(singleton,IAccountService as = ac.getBean("accountService",IAccountService.class);prototype,request,session,global-session)
- 和生命周期相关的
- @PostConstruct
-
作用:用于指定初始化方法
- @PreDestroy
-
作用:用于指定销毁方法
- 这两个注解了解即可
*/
@Autowired图解
先走红色的路线,如果数据类型一致,就找到,如果有两个数据类型一样的,就安装变量名称去匹配key值
key是变量名 下面value就是类型
10.读取Properties文件
properties配置文件:
nginx配置的图片服务器的端口号
nginx.pictureServer.port=8080
nginx配置的图片服务器的端口号
nginx.pictureServer.path=D:\Download
需要在SpringMVC 那里配置文件添加注解,如果在别的配置文件添加的话需要注意让它优先启动.(DispatcherServlet那里配置优先级)
java代码
@Value("
n
g
i
n
x
.
p
i
c
t
u
r
e
S
e
r
v
e
r
.
p
o
r
t
"
)
p
r
i
v
a
t
e
S
t
r
i
n
g
n
g
i
n
x
P
i
c
t
u
r
e
S
e
r
v
e
r
P
o
r
t
;
@
V
a
l
u
e
(
"
{nginx.pictureServer.port}") private String nginxPictureServerPort; @Value("
nginx.pictureServer.port")privateStringnginxPictureServerPort;@Value("{nginx.pictureServer.path}")
private String nginxPictureServerPath;
11.IOC的优缺点
优点
实现组件之间的解耦,提高程序的灵活性和可维护性
缺点
对象 生成因为是使用反射编程,在效率上有些损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的,除非某对象的生成对效率要求特别高
(二)(DI)依赖注入
1.加载Spring容器
- ClassPathXmlApplicationContext (通过xml配置Spring时候使用)
-
它是用于读取xml配置文件,创建IOC核心容器的。但是它只能读取类路径下的配置文件
-
AnnotationConfigApplicationContext(注解配置Spring时候使用)
-
它是用于读取注解创建IOC核心容器的,它是spring的第二天内容
-
FileSystemXmlApplicationContext(基本不用)
-
它是用于读取xml配置文件,创建IOC核心容器,它读取文件的位置可以是磁盘任意位置,只要有访问权限即可(如果没有访问权限,计算机使用者都无法使用,更别说程序了.)
-
BeanFactory和ApplicationContext的区别
-
BeanFactory:
-
它是SpringIOC容器的顶层接口。ApplicationContext是它的子接口。
-
它在创建bean对象的时候,采用的是延迟加载思想创建的。什么时候用到对象,什么时候创建。
-
ApplicationContext:
-
它是我们实际开发中用的最多的容器接口对象。它在创建Bean对象的时候,采用的是立即加载思想。也就是说只要一解析配置文件就马上创建对象并存入容器。
-
由于它是子接口,必然具备父接口的功能。也就是说它也支持延迟加载思想,但是需要根据具体情况具体选择。只不过选择的事是spring来做的。
-
2.ioc和di区别是什么
ioc负责创建对象,di依赖注入.
3.set方式注入(常用,比构造方法用的多)
a)基本注入
需要注意的是,必须先加载bean.xml才能使用在配置文件的配置,否则无法使用
<bean id="IUserService" class="com.lmxi.service.UserServiceImpl">
<!--也可以用set方式注入dao层-->
<!--然后就是开始注入 customerDaoImpl 这个和上面的标识一样-->
<bean id="customerDaoImpl" class="com.lmxi.dao.UserDaoImpl"/>
<bean id="time" class="java.util.Date"/>
b)注入集合map数组之类
aaa bbb ccc aaa bbb ccc aaa bbb ccc aaa bbb ccc4.构造方式注入(了解)
需要注意的是,必须先加载bean.xml才能使用在配置文件的配置,否则无法使用
注意需要有构造方法, 被注入的属性在UserServiceImpl里面就需要在那里有构造方法,
5.注解注入
a)基本
需要注意一下,普通方法(字段)进入容器里面(交给Spring容器管理,注册成为bean,就是存入Spring容器里面)才能被Spring注入(取出来),不进入就不能被Spring容器注入
4.注意:如果读取不到类就反射不到方法,如果Spring没有拿到类,就无法拿到类中的方法(不能把方法交给Spring容器管理)
要想用到方法.就得先让类被Spring容器管理才能让里面的方法被Spring容器管理
/**
-
Description: spring的配置类,它就相当于bean.xml
- @ComponentScan
-
作用:用于指定要扫描的包
-
属性:
-
value:指定包的名称。它等同于basePackages属性
- 相当于xml的扫描包
- @Bean(方法级别注解)
-
作用:把当前方法的返回值作为对象,存入spring的容器(添加的bean的id是被注解修饰的方法名)
-
属性:
-
@Bean(“runner”) //注入容器里面 容器里面就有这个id为runner的bean对象了value:也可以使用name属性。作用就是指定bean的id。默认值是当前方法名称
- @Configuration
-
作用:把当前类看成是一个spring的配置类
-
细节:
-
当创建容器时作为参数传入字节码之后,该注解就可以不写。
- @Import
-
作用:用于引入其他的配置类
-
属性:
-
value:用于指定配置类的字节码
*/
b) Spring 注解配置(没试过)
<context:component-scan base-package=“com.nm.spring.pojo”/>
使用该标签需要引入名字空间:
1
第 1 页
@Component(“名字”) : 组件注解,没有业务含义的注解使用,在类级别 名字可省略,如果省略,则为类名首字母小写后的类名作为名字相当于 在spring 配置文件中 使用标签注册一个对象到IOC容器
@Value(“值”):为属性赋值(基本类型及其包装类型和String)
@Service(“名字”):服务层组件注解,通常业务逻辑层使用,如UserServiceIml
@Repository(“名字”):持久层注解,通常用于数据访问层,如UserDaoImpl
@Controller(“名字”):控制器层注解,通常用于SpringMVC中的控制器类
@Autowired(required=true|false):自动注入,根据默认类型byType注入
required:表示是否为必须,如果为true, 又找不到注入的bean 则报错 默认为 true,该注解可以再字段,set方法,构造器上使用
@Qualifier(“名字”):和@Autowired配合使用,可以根据名字注入byName
@Resource(name=“bean名字”): JSR250提供的注解,根据名字注入
@Configuration:配置类,该注解表示这个类是一个配置类,其作用等价于Spring XML配置文件
@Bean(“名字”):等价于 xml 中书写 注册bean
6.依赖注入基本介绍总结
什么是依赖注入:
指的是在程序运行期间,让spring为我们提供类中所缺的对象。
通过配置文件的方式告知spring要把哪个对象传入。
简单的说:缺什么传什么
什么时候用构造函数注入
如果希望程序一运行的时候被注入的属性有值就用构造方法注入
如果不太强求程序一运行就得有值,只需要用的时候就有值就行用set方式注入(比构造方式好用)
注意:1.有些东西没有set方法就必须需要用构造方式注入: 比如说DataSource就没有set方法
只能用构造方式注入.
2.需要注意的是,必须先加载bean.xml才能使用在配置文件的配置,否则无法使用
3.静态方法被Spring容器注入会报错,因为静态是随着类加载而加载.
依赖注入(DI),spring使用javaBean对象的set方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程,就是依赖注入的思想。
简单说就是缺什么传什么,比如缺dao的实现类就传进来.
<!-- Spring的依赖注入:
注入的方式:只有三种
第一种:使用构造函数注入 不要使用,有比它还好用的
优势:每次都能保证想用的变量都有数据
弊端:创建对象时,必须知道每个参数的含义,还要准备参数数据
第二种:使用set方法注入 采用的更多的方式
第三种:使用注解注入(下次课的内容)
注入的数据类型:只有三类
第一类:基本类型和String
第二类:其他bean对象(要求必须是容器中的)
第三类:复杂类型(集合类型)
-->
<!-- 构造函数注入
涉及的标签:constructor-arg
出现的位置:bean标签的内部
标签的属性:
type:用于指定给哪个类型的参数赋值。它不能独立使用,因为构造函数中可能出现多个相同类型的参数。
index:用于指定给哪个索引的参数赋值。它要求我们必须知道有多少个参数。
name:用于指定给哪个名称的参数赋值。 以后就用它
value:用于指定基本类型和String类型数据的
ref:用于指定其他bean类型数据的
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="username" 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类型数据的
-->
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
<property name="username" value="test"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
<!-- 集合类型的注入
结构相同,标签可以互换
-->
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="myMap">
<props>
<prop key="testC">CCC</prop>
<prop key="testD">DDD</prop>
</props>
</property>
<property name="myProps">
<map>
<entry key="testA" value="AAA"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
</bean>
7.获取所有的bean的工具类
如果实在看不懂就看这个案例
(三)父子容器
在Spring整体框架的核心概念中,容器是核心思想,就是用来管理Bean的整个生命周期的,而在一个项目中,容器不一定只有一个,Spring中可以包括多个容器,而且容器有上下层关系,目前最常见的一种场景就是在一个项目中引入Spring和SpringMVC这两个框架,那么它其实就是两个容器,Spring是父容器,SpringMVC是其子容器,并且在Spring父容器中注册的Bean对于SpringMVC容器中是可见的,而在SpringMVC容器中注册的Bean对于Spring父容器中是不可见的,也就是子容器(SpringMVC)可以看见父容器(Spring)中的注册的Bean,反之就不行。
如果记不住可以仔细想想Controller注入了Service层, 说明SpringMVC可以访问Spring里面的bean.
我们可以使用统一的如下注解配置来对Bean进行批量注册,而不需要再给每个Bean单独使用xml的方式进行配置。
<context:component-scan base-package=“com.hafiz.www” />
从Spring提供的参考手册中我们得知该配置的功能是扫描配置的base-package包下的所有使用了@Component注解的类,并且将它们自动注册到容器中,同时也扫描@Controller,@Service,@Respository这三个注解,因为他们是继承自@Component。
在项目中我们经常见到还有如下这个配置,其实有了上面的配置,这个是可以省略掉的,因为上面的配置会默认打开以下配置。以下配置会默认声明了@Required、@Autowired、 @PostConstruct、@PersistenceContext、@Resource、@PreDestroy等注解。
context:annotation-config/
另外,还有一个和SpringMVC相关如下配置,经过验证,这个是SpringMVC必须要配置的,因为它声明了@RequestMapping、@RequestBody、@ResponseBody等。并且,该配置默认加载很多的参数绑定方法,比如json转换解析器等。
<mvc:annotation-driven />
而上面这句配置spring3.1之前的版本和以下配置方式等价
下面让我们来详细扒一扒Spring与SpringMVC的容器冲突的原因到底在那里?
我们共有Spring和SpringMVC两个容器,它们的配置文件分别为applicationContext.xml和applicationContext-MVC.xml。
1.在applicationContext.xml中配置了<context:component-scan base-package=“com.hafiz.www" />,负责所有需要注册的Bean的扫描和注册工作。
2.在applicationContext-MVC.xml中配置mvc:annotation-driven /,负责SpringMVC相关注解的使用。
3.启动项目我们发现SpringMVC无法进行跳转,将log的日志打印级别设置为DEBUG进行调试,发现SpringMVC容器中的请求好像没有映射到具体controller中。
4.在applicationContext-MVC.xml中配置
<context:component-scan base-package=“com.hafiz.www" />,重启后,验证成功,springMVC跳转有效。
下面我们来查看具体原因,翻看源码,从SpringMVC的DispatcherServlet开始往下找,我们发现SpringMVC初始化时,会寻找SpringMVC容器中的所有使用了@Controller注解的Bean,来确定其是否是一个handler。1,2两步的配置使得当前springMVC容器中并没有注册带有@Controller注解的Bean,而是把所有带有@Controller注解的Bean都注册在Spring这个父容器中了,所以springMVC找不到处理器,不能进行跳转。核心源码如下:
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
在方法isHandler中会判断当前bean的注解是否是controller,源码如下:
protected boolean isHandler(Class<?> beanType) {
return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;
}
而在第4步配置中,SpringMVC容器中也注册了所有带有@Controller注解的Bean,故SpringMVC能找到处理器进行处理,从而正常跳转。
我们找到了出现不能正确跳转的原因,那么它的解决办法是什么呢?
我们注意到在initHandlerMethods()方法中,detectHandlerMethodsInAncestorContexts这个Switch,它主要控制获取哪些容器中的bean以及是否包括父容器,默认是不包括的。所以解决办法就是在springMVC的配置文件中配置HandlerMapping的detectHandlerMethodsInAncestorContexts属性为true即可(这里需要根据具体项目看使用的是哪种HandlerMapping),让它检测父容器的bean。如下:
但在实际工程中会包括很多配置,我们按照官方推荐根据不同的业务模块来划分不同容器中注册不同类型的Bean:Spring父容器负责所有其他非@Controller注解的Bean的注册,而SpringMVC只负责@Controller注解的Bean的注册,使得他们各负其责、明确边界。配置方式如下
1.在applicationContext.xml中配置:
<context:component-scan base-package=“com.hafiz.www”>
<context:exclude-filter type=“annotation” expression=“org.springframework.stereotype.Controller”/>
</context:component-scan>
2.applicationContext-MVC.xml中配置
<context:component-scan base-package=“com.hafiz.www” use-default-filters=“false”>
<context:include-filter type=“annotation” expression=“org.springframework.stereotype.Controller” />
</context:component-scan>
关于use-default-filters="false"的作用,请参见另一篇博客
:context:component-scan标签的use-default-filters属性的作用以及原理分析
总结
这样我们在清楚了spring和springMVC的父子容器关系、以及扫描注册的原理以后,根据官方建议我们就可以很好把不同类型的Bean分配到不同的容器中进行管理。再出现Bean找不到或者SpringMVC不能跳转以及事务的配置失效的问题,我们就可以很快的定位以及解决问题了。很开心,有木有~
AOP
(一)概念
1.AOP术语
拿加事务来举例子(好理解)
Joinpoint(连接点):
所有接口中的方法就是连接点(没没增强的就是连接点,被增强的方法也是连接点)
(被代理对象中的所有方法)
Pointcut(切入点):
被代理对象中所有被增强的方法(被增强的就是切入点)
所有切入点都是连接点,但是连接点不一定是切入点
Advice(通知/增强):
增强部分的代码块(也可以想成是抽取出来的重复性的代码,比如加事务,事务就是通知)
增强的类就是通知类,里面就是增强方法
目标对象(Target)
增强逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,而在AOP的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上
引介(Introduction)
引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
织入:
比如给方法加事务, 把增强的代码加进来就是织入(涉及事务的代码)
用事务来举例介绍下面通知
前置通知:进入方法之前执行的代码块(比如开启事务)
后置通知:进入方法之后得到返回值执行的代码块(比如提交事务)
异常通知:抛出异常的时候执行的代码块(比如回滚事务)
最终通知:finally中执行的代码块(比如释放资源)
环绕通知 :环绕是靠代码指定什么时候执行, 环绕通知在Spring的另一种AOP写法.
写法上和上面不一样,比较特殊,就是把通知配到java代码里面了,
但是声明还需要在xml或者用注解声明有环绕通知
需要注意环绕通知和上面的四种通知有冲突,两种不能同时存在.
只能选一种,要么用环绕通知, 要么用上面的四种通知
Aspect(切面):是切入点和通知(引介)的结合
- 定义那个方法需要被怎样增强
引介(不用)
引入其它东西, 需要运行期的字节码增强 (一般开发中用不到)
2.注解方式配置注意事项
四大通知(前置,后置,异常,最终)在用注解的时候会有顺序问题,所以尽量不要用注解方式配置四大通知,注解配置AOP时候应该用环绕的通知.
3.AOP和代理模式
Spring框架的一大特点就是AOP,SpringAOP的本质就是动态代理,那么Spring到底使用的是JDK代理,还是cglib代理呢?
答:混合使用。如果被代理对象实现了接口,就优先使用JDK代理,如果没有实现接口,就用用cglib代理。
AOP(Aspect-OrientedProgramming,面向切面编程),AOP包括切面(aspect)、通知(advice)、连接点(joinpoint),实现方式就是通过对目标对象的代理在连接点前后加入通知,完成统一的切面操作。
实现AOP的技术,主要分为两大类:
一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。
默认的策略是如果目标类是接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理。
如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库,并在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)。
Spring两种代理方式
-
若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
优点:因为有接口,所以使系统更加松耦合
缺点:为每一个目标类创建接口 -
若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。
(二)使用
1.使用步骤(切入点表达式在里面)
必须导入依赖,否则报错
org.aspectj
aspectjweaver
1.8.13
spring基于xml的aop配置
第一步:把通知类(一般是程序员编写)也交给spring来管理
第二步:导入aop的约束和名称空间(官网有,或者去别的配置文件复制过来),
并使用aop:config标签开始aop的配置
第三步:在aop:config标签内部使用aop:aspect标签配置切面
id属性:用于给切面提供一个唯一标识。
ref属性:用于引用通知bean的id(一般就是第一步的程序员自己编写的通知类)。
第四步:在aop:aspect标签内部使用对应的标签配置通知的类型
前置通知:<aop:before
method属性:用于指定前置通知的方法
pointcut属性:用于指定切入点表达式
2.切入点表达式
https://docs.qq.com/doc/DQWJQTmRaYmZDT0JK
3.AOP示例
必须导入依赖,否则报错
aop:config
<aop:aspect id=“logAdvice” ref=“Logger”>
<aop:before method=“printLog” pointcut=“execution(* com.itheima.service..(…))”/>
</aop:aspect>
</aop:config>
4.前置后置异常最终通知(在上面基础上进行修改的)
必须导入依赖,否则报错
aop:config
<aop:aspect id=“logAdvice” ref=“Logger”>
<aop:before method=“beforePrintLog” pointcut=“execution(* com.itheima.service..(…))”/>
<aop:after-returning method=“afterReturningPrintLog” pointcut=“execution(* com.itheima.service..(…))”/>
<aop:after-throwing method=“afterThrowingPrintLog” pointcut=“execution(* com.itheima.service..(…))”/>
<aop:after method=“afterPrintLog” pointcut=“execution(* com.itheima.service..(…))”/>
</aop:aspect>
</aop:config>
结果
5.环绕通知介绍使用案例
必须导入依赖,否则报错
什么是环绕通知?是Spring给我提供了一种机制,一种可以在代码中手动控制通知类,通知何时执行的机制,这个环绕通知和前面四个通知有冲突,前面四个是在配置文件里面配置,是Spring确定什么时候来执行,这个环绕通知是我们自己靠代码来指定什么时候执行.
- 配置环绕通知 注意,在使用的时候一定要try catch 不要throw
- 问题:
- 当配置了环绕通知之后,执行切入点方法时,出现了环绕通知执行了,但是切入点方法却没有执行。
- 分析原因:
- 由动态代理的InvocationHandler中的invoke方法得知,环绕通知应该有明确的切入点方法调用。
- 而我们的环绕通知中没有明确的调用
- 解决:
- spring框架为我们提供了一个接口ProceedingJoinPoint,该接口可以作为环绕通知的方法参数来使用。
- 当环绕通知执行的时候,spring框架会为我们提供这个参数的具体实现类供我们使用。
- 该接口中有一个方法:proceed(),此方法就相当于method.invoke明确调用切入点方法
- 环绕通知:
- spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的机制
环绕通知配置
aop:config
<aop:pointcut expression=“execution(* com.itheima.service..(…))” id=“pt1”/>
<aop:aspect id="logAdvice" ref="Logger">
<!--配置环绕通知
method="aroundPrintLog" 是通知类的方法
pointcut-ref="pt1" 指定切入表达式(pt1是上面定义好的表达式id)
-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
配置的方法
/**
- 环绕通知
- @return
- @throws Throwable
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
try {
System.out.println(“前置”);
// proceed(),此方法就相当于method.invoke明确调用切入点方法
pjp.proceed();
System.out.println(“后置”);
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println(“异常”);
}
System.out.println(“最终”);
return null;
}
结果
a)环绕通知控制事务(架构Spring Jdbc)
IDEA的案例demo,里面有SQL表和代码
细节代码:
xml配置文件
aop:config
<aop:pointcut id=“pt1” expression=“execution(* com.itheima81.service..(…))”/>
<aop:aspect id=“logAdvice” ref=“TransactionManager”>
<aop:around method=“TransactionControl” pointcut-ref=“pt1”></aop:around>
</aop:aspect>
</aop:config>
java代码
/** 事务管理器
- 环绕通知 */
@Component
public class TransactionManager {
@Autowired private ConnectionUtils connectionUtils;
public Object TransactionControl(ProceedingJoinPoint pjp) throws Throwable {
//proceed 预先声明
Object proceed = null;
try {
connectionUtils.getCurrentConnection().setAutoCommit(false);
System.out.println(“已经开启事务(前置AOP)”); //前置
// proceed(),此方法就相当于method.invoke明确调用切入点方法
proceed = pjp.proceed();
connectionUtils.getCurrentConnection().commit();
System.out.println("已经提交事务(后置AOP)"); //后置
} catch (Throwable throwable) {
throwable.printStackTrace();
connectionUtils.getCurrentConnection().rollback();
System.out.println("已经回滚事务(异常AOP)");//异常
}
connectionUtils.getCurrentConnection().close();
// 让连接和当前线程解绑
connectionUtils.getTl().remove();
System.out.println("已经释放资源(最终AOP)"); //最终
/*这个返回的是源代码return的值
* 意思就是原来代码return的值如果是1
* 这个proceed 就是1
* */
return proceed;
}
}
结果(没有异常)
有异常
…
自己总结:
说白了就是不改变源代码的情况下给代码进行功能增强(通过配置切面和环绕通知的方式),
其主要的细节应该就是把共用的代码抽取出来. 比如事务,和打印日志.
底层是:
动态代理
使用AOP好处是:
1.减少开发量
2.维护简单
6.完成系统日志管理开发(使用aop技术)
a)简介
可以通过日志查看系统的情况,完成功能很简单,弄一张表,往里面加入数据就行了,重点是采用什么方法 来进行日志的添加,思考了一下使用AOP技术进行开发比较好.
使用AOP进行日志开发原因是因为AOP作用是可以不改变源代码的情况下对方法进行增强,如果以后不想使用日志记录了,只要修改自己编写的切面类的切入点表达式即可取消日志记录系统,十分的方便.
b)使用上的小坑,写之前行看这里
1.配置自己编写的切面类必须放入Controller层,因为可以被扫描, 放入SpringMVC容器(牵扯到父子容器问题)
2.注意扫描的时候 一定要指定是Controller结尾的类,目的是增强的时候排除自己编写的切面类, 因为切面类也在Controller层里面,如果不指定是Controller结尾的话,那么切面类也会被增强,然后就进入死循环,
c)SQL和实体类
具体的需要看业务需求进行SQL的设计
CREATE TABLE sys_log(
id varchar2(32) default SYS_GUID() PRIMARY KEY,
visitTime timestamp,
username VARCHAR2(50),
ip VARCHAR2(30),
method VARCHAR2(200)
)
实体类就根据SQL进行编写
d)编写切面类代码
在SpringMVC.xml开启AOP注解
<aop:aspectj-autoproxy proxy-target-class=“true”/>
在web.xml配置监听request 对象的监听器
import cn.itcast.domain.Log;
import cn.itcast.service.LogService;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
/**
*日志切面类
-
- 切面类 = 切入点表达式 + 通知类型
*/
@Aspect //当前是切面类
@Component //组件
public class LogAop {
@Autowired
private LogService logService;
@Autowired
private HttpServletRequest request;
//存储类.方法
private String methodName;/**
- 前置通知
*/
@Before(“execution(public * cn.itcast.controller.Controller.(…))”)
public void logBefore(JoinPoint p){
//获取执行的方法名
methodName = p.getSignature().getName();
// 获取到目标对象(SysUserController目标对象),获取类的名称
String classname = p.getTarget().getClass().getSimpleName();
// 拼接
methodName = classname+’.’+methodName;
}
/**
-
最终通知
*/
@After(“execution(public * cn.itcast.controller.Controller.(…))”)
public void logAfter() {
// 创建日志对象,属性设置进去,保存
Log log = new Log();
// 获取ip地址
String ip = request.getRemoteAddr();
log.setIp(ip);// 操作的类.方法
log.setMethod(methodName);// 获取到登录的名称
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.setUsername(user.getUsername());
log.setVisitTime(new Date());// 存入到数据库中(就是一条保存语句)
logService.save(log);
}
}
- 切面类 = 切入点表达式 + 通知类型
声明式事务
(一)概念
https://docs.qq.com/doc/DQWhmR3ZQRUxMenVr
(二)操作
1.xml声明式事务的操作案例
这里有案例和表,可以点进去参考
bean.xml
配置步骤
第一步:导入tx约束和名称空间(事务是基于aop的,aop的也得导入)
第二步:配置事务管理器(Spring框架的)DataSourceTransactionManager(需要在里面注入DataSource)
第三步:配置事务的通知(事务通知就表明让spring来处理何时提交和回滚)
3.1配置 transaction-manager="transactionManager
3.2配置事务属性(传播途径,是否只读事务)
第四步:配置aop的切入点表达式,并建立和通知的关联
第五步:配置事务的属性
在事务通知的内部
配置事务管理器
需要添加依赖切面类依赖
org.aspectj
aspectjweaver
1.8.13
用Spring框架的DataSourceTransactionManager
read-only:是否只读事务。默认值是:“false”,表示读写。只有查询方法才能设置为true。
isolation:指定事务的隔离级别。默认值:“DEFAULT”,表示使用数据库的默认隔离级别。
timeout:指定超时时间。默认值是:"-1",表示永不超时。如果设置了以秒为单位。
propagation:指定事务的传播行为。默认值是:“REQUIRED”,表示必须有事务,增删改方法的配置。查询方法使用SUPPORTS
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常事务不再回滚。没有默认值,即遇到任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,即遇到任何异常都回滚。
–>
tx:attributes
<tx:method name=“query*” read-only=“true” propagation=“SUPPORTS”/>
<tx:method name=“find*” read-only=“true” propagation=“SUPPORTS”/>
<tx:method name=“select*” read-only=“true” propagation=“SUPPORTS”/>
<tx:method name="*" read-only=“false” propagation=“REQUIRED”/>
</tx:attributes>
</tx:advice>
aop:config
<aop:pointcut id=“pt1” expression=“execution(* com.itXML9.service..(…))”></aop:pointcut>
<!-- 用aop:advisor标签建立切入点表达式和事务通知的关联
advice-ref 引入事务通知
pointcut-ref 引入切入点表达式 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
service层
@Service
public class AccountServiceImpl implements IAccountService {
@Autowired private IAccountDao iAccountDao;
public int transactionAccount(String sourceName, String targetName, Float money)
throws Exception {
// 查询姓名
Account source = this.iAccountDao.queryAccountByName(sourceName);
Account target = this.iAccountDao.queryAccountByName(targetName);
// 加钱减钱操作
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
// 更新账户
this.iAccountDao.updateAccountByName(source);
int a = 1/0;
int flag = this.iAccountDao.updateAccountByName(target);
return flag;
}
}
2.关于事务的基本概念
Spring的事务 我们重点使用的是xml配置的方式.(也可以在环绕通知使用,但是不如xml配置方式)
spring的事务管理:是通过spring的AOP实现的
* 事务管理器(切面类)
PlatformTransactionManager : 接口
事务管理器的实现类
DataSourceTransactionManager:仅对dbc操作数据库有效
HibernateTransactionManager :对hibernate操作有效
JpaTransactionManager :对jpa操作有效
3.事务的传播行为
方法间的嵌套调用的时候,对事务的支持情况,需要掌握这儿两个
1、PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
2、PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
增删改:使用传播行为是:REQUIRED
需要事务,原因:因为增删改需要对数据库进行更改,如果过程中异常需要回滚
是否只读 :false
查询:使用传播行为是:SUPPORTS
不需要事务,原因:因为查询不需要对数据库进行更改,不需要进行事务
是否只读:true
4.关于声明式事务和编程式事务
声明式事务管理的定义:
用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。
这样的好处是:事务管理不侵入开发的组件.
具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可,这样维护起来极其方便。
基于 TransactionInterceptor 的声明式事务管理:两个次要的属性: transactionManager,用来指 定 一 个 事务治理器 , 并 将 具 体 事 务 相 关 的 操 作 请 托 给 它 ; 其 他 一 个 是 Properties 类型的 transactionAttributes 属性,该属性的每一个键值对中,键指定的是方法名,方法名可以行使通配符, 而值就是表现呼应方法的所运用的事务属性。
编程式事务管理的定义:
在代码中显式挪用 beginTransaction()、commit()、rollback()等事务治理相关的方法,这就是编程式事务管理。 Spring 对事物的编程式管理有基于底层 API 的编程式管理和基于TransactionTemplate 的编程式事务管理两种方式。
基 于 底 层 API 的 编 程 式 管 理 : 凭 证 PlatformTransactionManager 、 TransactionDefinition 和TransactionStatus 三个焦点接口,来实现编程式事务管理。
基于 TransactionTemplate 的编程式事务管理:为了不损坏代码原有的条理性,避免出现每一个方法中都包括相同的启动事物、提交、回滚事物样板代码的现象,spring 提供了 transactionTemplate 模板来实现 编程式事务管理。
编程式事务与声明式事务这两者的区别:
编程式事务是自己写事务处理的类,然后调用
声明式事务是在配置文件中配置,一般搭配在框架里面使用!
5.如果事务没有作用看这里
SpringMVC扫描包的时候只扫描Controller层, 不要扫多了,如果SpringMVC容器扫多了,那么父子容器都有一套Service ,Controller肯定会用自己的Service容器, 结果就是父容器中配置的事务就不生效了.
http://www.cnblogs.com/hafiz/p/5875770.html
6.注解方式开启事务
1.在配置文件开启对 事务注解的支持
<tx:annotation-driven transaction-manager=“transactionManager” proxy-target-class=“true”/>
2.在需要事务的方法或者类上面添加注解
@Transactional
Spring整合Junit
(一)概念
1.为什么需要用Spring整合Junit
因为单元测试是 测试员来做的,而他们不会SSM的东西,我们需要把那些与测试无关的代码给屏蔽掉,比如下面的代码:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
as = ac.getBean("accountService",IAccountService.class);
这些代码不需要让测试看到,只需要把带有@Test修饰的方法给测试员看就可以了
(二)使用
1.整合前需要注意的事项
spring5整合junit时,要求junit的版本必须是4.12及以上(否则报错),
需要的坐标依赖下面有,如果是jar包需要自行下载
- java.lang.NoClassDefFoundError: org/springframework/web/context/request/RequestAttributes
- 错误信息!!!
- 解决办法是 依赖有问题.(重点检查依赖)
Junit单元测试不放test文件夹也可以使用
2.整合步骤(三步)
第一步 导入整合的jar包(还需要Spring的所有依赖jar包) - spring-test (还需要依赖原本的junit jar包)
或者Maven依赖
junit
junit
4.12
org.springframework
spring-test
5.0.2.RELEASE
第二步(测试有效)
/**Junit测试类
-
1.使用 @RunWith(SpringJUnit4ClassRunner.class) 注解替代原有的main函数
-
2.使用@ContextConfiguration注解告诉Spring配置的位置
locations:用于指定配置文件 -
classes:用于指定注解类
-
第一种方法可以注入多个xml文件,具体去百度看看,
@ContextConfiguration(locations = “classpath:bean.xml”) 里面填写的路径是
@ContextConfiguration(classes = SpringConfiguration.class) 里面填写的是配置类的.class 上面两种不能同时存在,只能选择其中之一注入. -
bean.xml(Springxml容器的路径)路径
-
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = “classpath:bean.xml”)//如果是使用xml的形式就用这个
//@ContextConfiguration(classes = SpringConfiguration.class)//如果是使用注解的形式就用这个注入 需要注意的是@ContextConfiguration两种注入 (xml注入和配置类注入)不可能同时存在,只能使用其中一种
public class AccountTest {
测试是否注入成功:
@Autowired IAccountService iAccountService; // 注入成功
@Test
public void TestHQ() {
System.out.println("iAccountService = " + iAccountService);
this.iAccountService.ceui();
}
可以打印注入完的接口名字,如果出现地址值就说明成功,否则就可能是其他原因.
其它
1.缓存注解
Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用
a)@Cacheable(添加缓存)
主要针对可缓存的方法配置,如查找方法
注解作用
先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中
value : 缓存的名称,在 spring 配置文件中定义,必须指定至少一个
key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合
condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存
b)@CachePut(更新缓存)
应用到写数据的方法上.比如 新增 修改 方法
调用方法的时候会自动把相应的数据放入缓存
value :缓存的名字,Spring配置文件中定义,必须指定至少一个
key: 缓存的key,可以为空,如果指定要按照SpELl表达式编写,如果不指定,则缺省按照方法的所有参数进行组合
condition: 缓存的条件,可以为空,使用SpEl编写,返回true或者false,只有true才进行缓存
c)@CacheEvict(删除缓存)
说明:
主要针对移除数据的方法配置,如删除方法
调用方法时,根据一定的条件会从缓存中移除相应的数据
参数:
value :
缓存的名称,在 spring 配置文件中定义,必须指定至少一个
key
缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合
condition
缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存
allEntries
是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存
beforeInvocation
是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存
d)缺陷
1.不支持TTL,也就是不能设置expires time。这点挺遗憾的,spring-cache认为这是各个cache实现自己去完成的事情,有方案但是只能设置统一的过期时间,这明显不够灵活。比如用户的抽奖次数、有效期等业务,当天有效,或者3天、一周有效,我们倾向于设置缓存有效期解决这个问题,而spring-cache却无法完成。
2.无法根据查询结果中的内容生成缓存key,比如getUser(uid)方法,想通过查询出来的user.email生成缓存key就无法实现了。
3.调试起来麻烦