目录
spring简介
总所周知,spring是SSM框架之一,框架有什么作用呢我就不多说了,其实这种概念性的东西要等上手或者熟悉以后才能深刻体会,这里就讲一下最近的学习后我对spring的理解。
spring是一个框架,它有两个作用:控制反转和面向切面编程。
-
控制反转:IOC+DI
-
IOC: 控制反转
即控制权的转移,将我们创建对象的方式反转了,以前对象的创建是由我们开发人员自己维护,包括依赖关系也是自己注入。使用了spring之后,对象的创建以及依赖关系可以由spring完成创建以及注入,反转控制就是反转了对象的创建方式,从我们自己创建反转给了程序创建(spring)。 -
DI: Dependency Injection 依赖注入
spring这个容器中,替你管理着一系列的类,前提是你需要将这些类交给spring容器进行管理,然后在你需要的时候,不是自己去定义,而是直接向spring容器索取,当spring容器知道你的需求之后,就会去它所管理的组件中进行查找,然后直接给你所需要的组件,实现IOC思想需要DI做支持
这里有一篇博客写的很好,关于IOC和DI,受益颇深:
https://blog.csdn.net/sinat_21843047/article/details/80297951?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159678755519195264557189%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=159678755519195264557189&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v3~pc_rank_v2-1-80297951.first_rank_ecpm_v3_pc_rank_v2&utm_term=%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC%E5%92%8C%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5%E7%9A%84%E7%90%86%E8%A7%A3%28%E9%80%9A%E4%BF%97%E6%98%93%E6%87%82%29&spm=1018.2118.3001.4187
-
-
面向切面:AOP
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。spring里的面向切面原理就是利用了动态代理,动态代理就是利用了反射,这里有一篇关于AOP的博客:
https://blog.csdn.net/qq_32317661/article/details/82878679?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159695849319724848354305%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=159695849319724848354305&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v3~pc_rank_v2-2-82878679.first_rank_ecpm_v3_pc_rank_v2&utm_term=%E9%9D%A2%E5%90%91%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B&spm=1018.2118.3001.4187
上手spring
-
环境搭建:其实spring是有很多东西需要配置的,也叫做“配置地狱”,但现在有一个包就包括了我们需要的东西,我们只要导入这一个即可。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.10.RELEASE</version> </dependency>
-
编写实体类
public class Hello { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void show(){ System.out.println("Hello,"+ name ); } }
-
编写spring文件,这里叫做bean.xml
<?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"> <!--bean就是java对象 , 由Spring创建和管理--> <bean id="hello" class="com.kuang.pojo.Hello"> <property name="name" value="Spring"/> </bean> </beans>
-
编写测试类
@Test public void test(){ //解析beans.xml文件 , 生成管理相应的Bean对象 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //getBean : 参数即为spring配置文件中bean的id . Hello hello = (Hello) context.getBean("hello"); hello.show(); }
通过上面几步,便完成了一次IOC,或许会很疑惑,但下面会细讲的。对于上面的那个xml文件,也可以用注解替代,以后我们也是这么干的。
依赖注入:DI
IOC就是实现了控制权的反转,在spring里面,IOC的体现就是依赖注入,正如同上面的那个例子,不是由我们去直接new 出一个User 对象,而是由spring读取配置文件然后去创造出一个User 对象并给它的属性赋值,这一个过程就叫做依赖注入。对于依赖注入的学习,我们主要就是来学习它是怎么注入属性注入值的。
-
依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
-
注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
1.创建对象的时机
spring创建对象的时机,其实就是我们在测试类中的那句代码ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml")
,这句代码的意思是将我们的spring文件读取,然后他就会创建spring文件里面的对象了,每一个bean就是一个对象。我们可以来测试一下:
-
编写实体类,并在无参构造方法中输出一句话
public class User { private String name; public User() { System.out.println("user无参构造方法"); } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name="+ name ); } }
-
编写beans.xml
<?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"> <bean id="user" class="com.kuang.pojo.User"> <property name="name" value="kuangshen"/> </bean> </beans>
-
测试类,只执行加载文件那句代码
@Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); }
结果输出了无参构建里的那句话,这个结果表明:在加载文件的之后就创建了对象,且是通过无参构造来创建的。
2.构造器注入
上面我们通过例子得到,其实spring加载bean.xml文件是通过无参构造方法构建对象,如果没有无参构造方法会报错。如下:
-
没有无参构造的实体类
public class User { private String name; // public User() { // System.out.println("user无参构造方法"); // } public User(String name) { this.name = name; } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name="+ name ); } }
-
bean.xml配置不变
<?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"> <bean id="user" class="com.chy.bean.User"> </bean> </beans>
-
测试类
@Test public void test2(){ //解析beans.xml文件 , 生成管理相应的Bean对象 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //getBean : 参数即为spring配置文件中bean的id . User user = context.getBean(User.class); user.show(); }
这样会直接报错,那是不是就一定要有无参构造方法呢?不一定,肯定也是能存在没有无参构造也能创建对象的。没有无参构造为什么会报错呢?因为如果没有无参构造,它要创建对象就要调用有参构造,但调用有参构造需要参数,我们没给它参数,它肯定报错,所以只要我们给它参数,即使没有无参构造方法也不会报错。
当然,给参数我们也是很有多种方式的:
-
通过属性名字
<!-- 第一种根据参数名字设置 --> <bean id="user" class="com.kuang.bean.User"> <!-- name指参数名 --> <constructor-arg name="name" value="xixi"/> </bean>
-
通过属性在构造器里的下标
<!-- 第二种根据index参数下标设置 --> <bean id="user" class="com.kuang.bean.User"> <!-- index指构造方法 , 下标从0开始 --> <constructor-arg index="0" value="xixi"/> </bean>
-
通过属性类型,如果有两个相同的参数类型则不能使用这种方式
<!-- 第三种根据参数类型设置 --> <bean id="user" class="com.kuang.bean.User"> <constructor-arg type="java.lang.String" value="xixi"/> </bean>
3.spring文件的其他配置
上面我们讲了spring文件里bean标签的一些作用,其实上面只是简单提到下,依赖注入就是利用这个bean标签,下面再仔细讲一下,现在来看一下除了spring配置文件除了bean标签还有什么标签。
除开bean标签,还有其他四个,其中description标签不用了解,顾名思义就是描述,还有beans标签也不用理解,表示里面包含多个bean的意思。
所以主要就有另外两个:alias 和 import
-
alias:别名标签,我们可以改掉bean的ID,用成我们想要的别名。
<bean id="hello" class="com.chy.dao.Hello"> <property name="name" value="Spring"/> </bean> <alias name="hello" alias="sad56as4d56as"/>
比如上面这样,我们就为hello这个bean改了别名,我们在使用任何与hello这个bean相关的东西时,都可以使用这个别名。
其实在改别名这个功能上,bean标签也给我们提供了一个属性,而且它比alias更强大,能一次性起多个别名,所以alias这个标签了解即可。
<!-- id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符 如果配置id,又配置了name,那么name是别名 name可以设置多个别名,可以用逗号,分号,空格隔开 如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象; class是bean的全限定名=包名+类名 --> <bean id="hello" name="hello2 h2,h3;h4" class="com.chy.dao.Hello"> <property name="name" value="Spring"/> </bean>
-
import:这个标签就比较重要了,它可以用来进行团队开发,它是用来导入别的spring的xml文件的。
那它怎么用来团队开发呢?比如一个团队开发一个项目,分给三个人,他们都用spring来写项目,所以三个人就都各自有一份spring文件,这时在项目汇总的时候就编写一个总的spring文件,用import标签导入三份spring文件即可。
<import resource="{path}/beans.xml"/>
4.属性注入
在上面我们的例子中,我们通过在spring的配置文件中,加上一句:<property name="name" value="Spring"/>
就完成了对创建的bean对象进行了属性赋值,这就是属性注入。
首先我们要注意一个点:若想使用属性注入,属性必须要有set方法,否则会报错,如果属性是boolean类型 , 没有set方法 , 是 is。属性注入也分很多种,有注入常量、注入bean对象、注入map等等,下面一一讲解。
-
注入常量
<bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> </bean>
-
注入bean对象:这里的值是一个引用,ref
<bean id="addr" class="com.kuang.pojo.Address"> <property name="address" value="重庆"/> </bean> <bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> <property name="address" ref="addr"/> </bean>
-
注入数组
<bean id="student" class="com.kuang.pojo.Student"> <property name="name" value="小明"/> <property name="address" ref="addr"/> <property name="books"> <array> <value>西游记</value> <value>红楼梦</value> <value>水浒传</value> </array> </property> </bean>
-
注入map
<property name="card"> <map> <entry key="中国邮政" value="456456456465456"/> <entry key="建设" value="1456682255511"/> </map> </property>
-
注入list
<property name="hobbys"> <list> <value>听歌</value> <value>看电影</value> <value>爬山</value> </list> </property>
-
注入set
<property name="games"> <set> <value>LOL</value> <value>BOB</value> <value>COC</value> </set> </property>
-
注入NULL
<property name="wife"><null/></property>
-
注入properties
<property name="info"> <props> <prop key="学号">20190604</prop> <prop key="性别">男</prop> <prop key="姓名">小明</prop> </props>
上面的这些方式都是利用了set方法,当对象创建以后,就会自动调用它的set方法去注入值。
<bean id="user" class="com.chy.bean.User">
<property name="name" value="haha"/>
<constructor-arg value="xixi" name="name"/>
</bean>
比如上面这段代码,结果name的值是haha,而不是xixi,执行顺序是先用有参构造传入了xixi这个值,然后调用set方法注入haha这个值。
这里拓展一下,还有两种注入方式:P命名空间注入和C命名空间注入。
-
P命名空间注入 : 要有无参构造器
导入约束 : xmlns:p="http://www.springframework.org/schema/p" <!--P(属性: properties)命名空间 , 属性依然要设置set方法--> <bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>
-
C命名空间注入:要有有参构造器
导入约束 : xmlns:c="http://www.springframework.org/schema/c" <!--C(构造: Constructor)命名空间 , 属性依然要设置set方法--> <bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>
5.bean作用域
在bean标签下有一个属性scope,表示一个bean对象的生命周期,分别有以下值:
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
-
Singleton
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton"> 测试: @Test public void test03(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("user"); User user2 = (User) context.getBean("user"); System.out.println(user==user2); }
-
Prototype
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/> 或者 <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
-
Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
-
Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
6.bean自动装配
自动装配是使用spring满足bean依赖的一种方法,spring会在应用上下文中为某个bean寻找其依赖的bean。Spring中bean有三种装配机制,分别是:
-
在xml中显式配置;
-
在java中显式配置;
-
隐式的bean发现机制和自动装配。
这里我们主要讲第三种:自动化的装配bean。Spring的自动装配需要从两个角度来实现,或者说是两个操作:
-
组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
-
自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。有两种自动装配的方式,先给下环境:
<?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">
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="str" value="qinjiang"/>
</bean>
</beans>
-
byName:通过类名装配
<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User" autowire="byName"> <property name="str" value="qinjiang"/> </bean>
这样就完成了装配,不用再去配Dog和Cat,会自动帮我们装配好。当一个bean节点带有 autowire byName的属性时。将查找其类中所有的set方法名。
例如setCat,获得将set去掉并且首字母小写的字符串,即cat。去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。
-
byType:通过类的数据类型装配
<bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="cat2" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User" autowire="byType"> <property name="str" value="qinjiang"/> </bean>
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。上面有两个cat所以会报错,删掉一个就可以了。
7.使用注解完成自动装配
spring也可以用注解来实现依赖注入,但在此之前,要配置一下,允许使用注解:
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
-
@Autowired:先按byType匹配,如果有两个以上的同类型bean,则按byName匹配
<bean id="cat5454" class="com.chy.bean.Cat"/> <bean id="dog454" class="com.chy.bean.Dog"/> 按类型查找 <bean id="people" class="com.chy.bean.People">
<bean id="cat5454" class="com.chy.bean.Cat"/> <bean id="dog454" class="com.chy.bean.Dog"/> 按类型查找 <bean id="dog" class="com.chy.bean.Dog"/> 按名字查找 <bean id="people" class="com.chy.bean.People">
public class People { @Autowired(require = false) 表示这个属性可以为空 private Cat cat; @Autowired private Dog dog; private String str; 构造方法 get、set方法
-
@Qualifier:@Autowired的小弟,必须和@Autowired在一起使用,它的作用是可以指定名字自动装配
<bean id="cat5454" class="com.chy.bean.Cat"/> <bean id="dog454" class="com.chy.bean.Dog"/> <bean id="dog12" class="com.chy.bean.Dog"/> <bean id="people" class="com.chy.bean.People">
@Autowired @Qualifier("dog12") private Dog dog;
-
@Resource:这个注解在jdk11以后取消了,作用和@Autowired一样,不过实现按byName再按byType
8.使用注解开发
上面已经讲了自动装配注解,下面在讲一下一些注解,当然,在实现这些注解之前需要配置好:
- 导入aop包,当然我们刚开始导入的包里已经包含了aop包了
- 开启注解:
<context:annotation-config/>
- 扫描包下的注解,使其生效,自动装配不用扫描包注解:
<context:component-scan base-package="包的全路径"/>
-
bean注入:使用@Component注解,用来进行bean注入
<bean id="user" class="com.chy.bean.User"/>
@Component pubilc class User
注解的作用就相当于在配置文件中加上了那句话,默认id是类的名字小写,当然也可以自己指定名字。
@Component("user") //默认 @Component("sad") //自定义id pubilc class User
在实际项目中,我们都知道web项目有MVC分层,有dao,有service,有controller,@Component有一些衍生注解,分别表示是哪个层的组件,它们的作用都一样,目的就是为了易于区分。
-
@Controller:web层
-
@Service:service层
-
@Repository:dao层
-
-
属性注入:使用@value来注入对象属性的值
@Component("user") // 相当于配置文件中 <bean id="user" class="当前注解的类"/> public class User { @Value("嘻嘻") // 相当于配置文件中 <property name="name" value="嘻嘻“/> public String name; }
如果提供了set方法,在set方法上添加@value(“值”);
@Component("user") public class User { public String name; @Value("秦疆") public void setName(String name) { this.name = name; } }
-
作用域:使用@scope来标记作用域,上面已经讲了bean的作用域了,这里也是用注解来替代xml配置文件
@Controller("user") @Scope("prototype") public class User { @Value("嘻嘻") public String name; }
9.使用注解替代配置文件
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
测试:
1、编写一个实体类,Dog
@Component //将这个类标注为Spring的一个组件,放到容器中!
public class Dog {
public String name = "dog";
}
2、新建一个config配置包,编写一个MyConfig配置类
@Configuration //代表这是一个配置类
public class MyConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public Dog dog(){
return new Dog();
}
}
3、测试
@Test
public void test2(){
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(MyConfig.class);
Dog dog = (Dog) applicationContext.getBean("dog");
System.out.println(dog.name);
}
4、成功输出结果!导入其他配置如何做呢?
1、我们再编写一个配置类!
@Configuration //代表这是一个配置类
public class MyConfig2 {
}
2、在之前的配置类中我们来选择导入这个配置类
@Configuration
@Import(MyConfig2.class) //导入合并其他配置类,类似于配置文件中的 inculde 标签
public class MyConfig {
@Bean
public Dog dog(){
return new Dog();
}
}
关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!
面向切面:AOP
注意:由于AOP原理就是使用了动态代理,在学习AOP之前,请务必先了解好动态代理,将会事半功倍。
1.AOP中的一些定义
- 切面中的定义
-
通知(Advice)
就是你想要的功能,也就是上面说的 安全,事物,日志等。你给先定义好把,然后在想用的地方用一下。 -
连接点(JoinPoint)
这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。
-
切入点(Pointcut)
上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。 -
切面(Aspect)
切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。 -
引入(introduction)
允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗 -
目标(target)
引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。 -
代理(proxy)
怎么实现整套aop机制的,都是通过代理,也就是动态代理机制。 -
织入(weaving)
把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时。
-
对于这些概念,其实不是很好理解,但是通过实际例子我们将他们一一带入就比较好理解了,接下来我们通过实际例子来体会spring中三种实现AOP的方式。
2.spring原生接口实现AOP
我们首先要构建下环境和自定义一些需求,现在有一个web应用中的service层的接口UserService
,它有一个实现类UserServiceImpl
,它里面有一些方法,现在有一个需求:增加一个日志类,每当调用实现类里面的方法,就会在日志里记录调用了这个方法。
-
导入AOP依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
-
接口UserService
public interface UserService { public void add(); public void delete(); public void update(); public void search(); }
-
实现类UserServiceImpl
public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加用户"); } @Override public void delete() { System.out.println("删除用户"); } @Override public void update() { System.out.println("更新用户"); } @Override public void search() { System.out.println("查询用户"); } }
-
日志类Log:一个前置增强 一个后置增强
public class Log implements MethodBeforeAdvice { //method : 要执行的目标对象的方法 //objects : 被调用的方法的参数 //Object : 目标对象 @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了"); } }
public class AfterLog implements AfterReturningAdvice { //returnValue 返回值 //method被调用的方法 //args 被调用的方法的对象的参数 //target 被调用的目标对象 @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法," +"返回值:"+returnValue); } }
-
配置spring
<?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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册bean--> <bean id="userService" class="com.kuang.service.UserServiceImpl"/> <bean id="log" class="com.kuang.log.Log"/> <bean id="afterLog" class="com.kuang.log.AfterLog"/> <!--aop的配置--> <aop:config> <!--切入点 expression:表达式匹配要执行的方法--> <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点--> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config> </beans>
-
测试类
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //一定要用接口,代理代理的是接口 UserService userService = (UserService) context.getBean("userService"); userService.search(); } }
3.自定义类实现AOP
现在我们来自定义类,通过动态代理来实现AOP,代码将会简化很多,但同时功能也没有原生方式那么强大。
- 写我们自己的一个切入类
public class DiyPointcut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
- 去spring中配置
<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.chy.config.DiyPointcut"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyPonitcut" method="before"/>
<aop:after pointcut-ref="diyPonitcut" method="after"/>
</aop:aspect>
</aop:config>
- 测试
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
}
4.注解实现AOP
- 编写一个注解实现的增强类
package com.kuang.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
- 在Spring配置文件中,注册bean,并增加支持注解的配置
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
aop:aspectj-autoproxy说明
-
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
-
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class=“true”/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
整合mybatis
spring是个大杂烩,里面可以融合很多环境,融合以后有很大的好处就是可以利用spring的IOC和AOP,在实现某些业务的时候能起到很大作用,现在我们就来整合mybatis,其实就是去除mybatis的配置文件,把它化为spring的配置文件里,融合以后既能有spring的ioc和aop,也能有mybatis的简化jdbc和sql语句。
-
首先配置好环境,导入相关jar包
junit <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> mybatis <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> mysql-connector-java <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> spring相关 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.10.RELEASE</version> </dependency> aspectJ AOP 织入器 <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> mybatis-spring整合包 【重点】 <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency> 配置Maven静态资源过滤问题! <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
编写sping配置文件:整合mybatis就是要删除mybatis的存在,所以要去除mybatis的配置文件和工具类。
-
去除配置文件:下面就是一个标准的mybatis配置文件,所谓去除mybatis配置文件,其实就是将mybatis配置文件里的东西转移到spring配置文件里
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="db.properties"/> <settings> <setting name="logImpl" value="LOG4J"/> </settings> <typeAliases> <typeAlias type="com.chy.bean.User" alias="user"></typeAlias> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/chy/dao/UserMapper.xml"/> <mapper resource="com/chy/dao/StudentMapper.xml"/> <mapper resource="com/chy/dao/TeacherMapper.xml"/> </mappers> </configuration>
1.首先配备数据源
<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean>
2.配置SqlSessionFactory,关联MyBatis
`<!--配置SqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!--关联Mybatis--> <!--导入mybatis配置文件,我还是保留有mybatis的配置文件,在里面设置一些别名属性什么的,这一步也可以不要,直接在spring配置里面实现--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!--这个相当于mybatis配置文件里的mapper标签--> <property name="mapperLocations" value="classpath:com/chy/dao/*.xml"/> </bean>
注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。
SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。
一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。
需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。
SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。
模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
- 去除工具类:mybatis工具类就是创建了一个sqlsessionFactory,用来创建sqlsession,现在我们要将他们变成用spirng配置文件+依赖注入来实现,因为在spring中不是直接new对象,而是通过依赖注入来创建对象
public class MybatisUtill { private static SqlSessionFactory sqlSessionFactory; static { try { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //获取SqlSession连接 public static SqlSession getSession(){ return sqlSessionFactory.openSession(true); } }
1.注册sqlSessionTemplate,关联sqlSessionFactory;
<!--注册sqlSessionTemplate , 关联sqlSessionFactory--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--利用构造器注入--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>
2.增加Dao接口的实现类;私有化sqlSessionTemplate
public class UserDaoImpl implements UserMapper { //sqlSession不用我们自己创建了,Spring来管理 private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } }
3.注册bean实现
<bean id="userDao" class="com.kuang.dao.UserDaoImpl"> <property name="sqlSession" ref="sqlSession"/> </bean>
4.测试
@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List<User> user = mapper.selectUser(); System.out.println(user); }
-
结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!我习惯留下这两个东西:属性和别名,也可以不留,仁者见仁智者见智,如果不留第2步关联mybatis那里也就没必要写了。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
<typeAliases>
<package name="com.kuang.pojo"/>
</typeAliases>
</configuration>
第二种方式
上面我们整合mybatis的工具类的时候,发现很麻烦,新创建的实现类还要有set方法,就感觉巨烦,下面有一个更加好的办法。
mybatis-spring1.2.3版以上的才有这个,官方文档截图 :
dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看
1、将我们上面写的UserDaoImpl修改一下
public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.selectUser();
}
}
2、修改bean的配置
<bean id="userDao" class="com.kuang.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
3、测试
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserMapper mapper = (UserMapper) context.getBean("userDao");
List<User> user = mapper.selectUser();
System.out.println(user);
}
事务
事务很重要,那在spring中如何开启事务呢?上面我们已经学到过aop了,其实开启事务就是利用了aop,不过切面spring已经帮我们写好了,就等我们去配置开启事务了。
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
-
编程式事务管理
将事务管理代码嵌到业务方法中来控制事务的提交和回滚缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
-
声明式事务管理
一般情况下比编程式事务好用。将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。
1.使用Spring管理事务,注意头文件的约束导入 : tx
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
2.事务管理器
-
无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
-
就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
下面来JDBC事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
4.配置好事务管理器后我们需要去配置事务的通知
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
spring事务传播特性:事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为。
-
propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
-
propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
-
propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
-
propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
-
propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
-
propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
-
propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!
5.配置AOP,导入aop的头文件!
<!--配置aop织入事务-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.kuang.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>