Spring简介
2002年:首次推出了Spring框架的雏形:Interface21框架
2004年3月24号:Spring框架以Interface21框架为基础,经过重新设计不断丰富其内涵,首次发布1.0版本
Rod Johnson,Spring的创始人,他是悉尼大学的博士,并且他的专业不是计算机而是音乐学。
Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架
Spring框架是一个基于分布式轻量级的应用程序
核心:IOC(控制反转)和app(面向切面编程)两大技术
Spring IOC(控制反转,重点)
Spring AOP(面向切面,重点)
Spring JDBC + 事务
Spring拓展
SpringBoot
- 一个快速开发的脚手架
- 基于SpringBoot可以快速开发的单个微服务
- 约定大于配置
SpringCloud
- 基于SpringBoot实现的
学习SpringBoot的前提是完全掌握Spring和SpringMVC
IOC理论指导
1.IOC是一种思想,以前写业务,所有的控制权都在程序员手中,那么当用户需求改变时,程序员就需要改写Service的部分代码
2.当我们使用set注入时,把这种控制权交给客户,客户需要什么功能,让他自己set即可,这种思想就叫做控制反转
3.所谓控制反转:获取依赖对象的方式反转了
4.最大的作用就是解耦:解除对象管理和程序员之间的耦合
环境搭建
导入jar包
spring-beans-4.1.6.RELEASE.jar
spring-context-4.1.6.RELEASE.jar
spring-core-4.1.6.RELEASE.jar
spring-expression-4.1.6.RELEASE.jar
commons-logging-1.1.3.jar
编写一个实体类
package pojo;
public class User {
private String name = "嘿嘿";
public void showName(){
System.out.println(name);
}
}
新建applicationContext.xml文件
<beans>
<bean id="userObj" class="pojo.User"></bean>
</beans>
Spring的配置文件是基于schem
打开schem/jee里面存放着一些.xsd文件这些都是schem
schem是dtd的升级版,具有更好的拓展性
以前的dtd是另起一个标签,而xsd是被作为属性存在于根节点的
创建对象
package pojo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
//从类路径下面加载配置文件
ApplicationContext aContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//第二个参数可以是User.class
User user = (User)aContext.getBean("userObj");
//另外还有一个方法可以获取Spring当前管理的所有对象:getBeanDefinitionNames();
user.showName();
Spring创建对象的三种方式
1.0 有参数构造(需要配置)
首先编写实体类
package pojo;
public class User {
private String name = "嘿嘿";
public User(String name) {
super();
System.out.println("执行了有参数构造");
this.name = name;
}
public void showName(){
System.out.println(name);
}
}
配置文件:一旦配置完成,那么Spring就会默认创建含有参数的对象
<bean id="userObj" class="pojo.User">
<constructor-arg index="0" value="哈哈">
</constructor-arg>
</bean>
标签中的参数都是用来描述构造器的,用来确定Spring需要调用的构造方法
constructor-arg:有几个参数就用几个标签
index:表示是第几个参数(下标从0开始)
value:基本数据类型的参数值
ref:引用一个bean(参数是对象的时候)
name:描述参数的名称:变量名称
type:描述构造参数类型:控制执行的构造
2.0 实例工厂
假如有一个People类,其有多个子类,那么我们既可以通过new子类获得,也可以把People当作工厂去生产子类对象
package pojo;
public class UserFctory {
public User getInstence() {
return new User("我是工厂创建的");
}
}
配置工厂对象
<bean id="factory" class="pojo.UserFctory"/>
<bean id="user1" factory-bean="factory" factory-method="getInstence"/>
调用
ApplicationContext aContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) aContext.getBean("user1");
user.showName();
3.0 静态工厂
改写为static方法
public static User getInstence() {
return new User("我是工厂创建的");
}
编写配置文件
<bean id="user2" class="pojo.UserFctory" factory-method="getInstence"/>
测试
ApplicationContext aContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) aContext.getBean("user2");
user.showName();
Bean属性注入
第一种就是通过构造方法(参照之前)
第二种就是Set注入
package pojo;
public class User {
private String name = "嘿嘿";
public String toString() {
return "User [name=" + name + "]";
}
public User() {}
public User(String name) {
super();
System.out.println("执行了有参数构造");
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void showName(){
System.out.println(name);
}
}
配置文件
property是由Spring调用类的setXxx()方法
<bean id="userObj" class="pojo.User">
<property name="name" value="张三"></property>
</bean>
获取
//从类路径下面加载配置文件
ApplicationContext aContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) aContext.getBean("userObj");
user.showName();
接下来注入set集合的属性
//在User类中添加集合
private Set<String> sets;
public Set<String> getSets() {
return sets;
}
编写配置文件
<bean id="userObj" class="pojo.User">
<property name="name" value="随便的名字"></property>
<property name="sets">
<set>
<value>str1</value>
<value>str2</value>
<value>str3</value>
<value>str4</value>
</set>
</property>
</bean>
如此类推:当数据类型是List时使用list标签,数组就用array标签,如果是一个map:
<map>
<entry>
<key>键</key>
<value>值</value>
</entry>
<entry>
<key>键</key>
<value>值</value>
</entry>
</map>
或者:
<map>
<entry key="键" value="值"/>
<entry key="键" value="值"/>
<entry key="键" value="值"/>
</map>
当属性是propertise时:
<property name="demo">
<props>
<prop key="键">值</prop>
</props>
</property>
DI依赖注入
DI和IoC是一样,当一个类(A)中需要依赖另一个对象(B)时,把B赋值给A的过程就叫做DI
<bean id="desk" class="pojo.Desk"></bean>
<bean id="userObj" class="pojo.User">
<property name="demo" ref="desk"/>
</bean>
Spring整合MyBatis
在使用MyBatis的时候,首先需要配置mybatis-config.xml的配置文件,然而Spring给我们提供了一个类,用来对应该配置文件。
数据库相关的连接信息是在< dataSource>里面:
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
打开spring.jdbc.jar包:找到:org.springframework.jdbc.datasource.DriverManagerDataSource的类,就是对应DataSource的,用来获取数据库连接,进入后发现该类没有全局变量,于是进入其父类AbstratDriverManagerDataSource:中有三个变量:url,username,password,然后我们就可以在src下面的applicationContext.xml配置这些参数:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
但是还缺少driver,其实在DriverManagerDataSource类中有一个setSriverClassName的方法去设置该值,于是我们可以这样配置:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
以往在获取Session的过程总是有很多代码重复的写,那么我们引入新的包mybatis-spring.jar,里面有一个类:org.mybatis.spring.SqlSessionFactoryBean专门在Spring中生成SqlSession对象的类,在该类中有一个全局变量dataSource刚好是DataSource类型的,于是我们可以使用ref注入上面名为dataSource的< bean>:
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
到此,我们就配置出了SqlSessionFactory对象,但是在mybatis当中还需要一个mapper标签去引用配置文件,于是我们可以使用一个类:org.mybatis.spring.mapper.MapperScannerConfigurer去扫描一个包,并且在MapperScannerConfigurer类中有两个全局属性比较重要:String basePackage,SqlSessionFactory sqlSessionFactory
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.jxb.mapper"></property>
<property name="sqlSessionFactory" ref="factory"></property>
</bean>
basePackage相当于MyBatis< mapper>标签里面的< package>标签,到此mybatis中的配置文件算是完成啦,看一下完整代码:
<?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"
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">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.jxb.mapper"></property>
<property name="sqlSessionFactory" ref="factory"></property>
</bean>
</beans>
编写测试类
package cn.jxb.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import cn.jxb.pojo.Student;
public interface StudentMapper {
@Select("select * from student")
List<Student> selAll();
}
ApplicationContext aContext = new ClassPathXmlApplicationContext("applicationContext.xml");
String names[] = aContext.getBeanDefinitionNames();
SqlSessionFactory sFactory = (SqlSessionFactory)aContext.getBean("factory");
SqlSession sqlSession = sFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(cn.jxb.mapper.StudentMapper.class);
List<Student> list = mapper.selAll();
System.out.println(list);
我们可以通过names可以看到Spring帮我们装再了StudentMapper的对象,所以我们可以先编写一个实现类,把StudentMapper做为全局变量,然后配置bean标签,ref为studentMapper
<bean id="studentMapperImpl" class="cn.jxb.mapper.impl.StudentMapperImpl">
<property name="sMapper" ref="studentMapper"></property>
</bean>
这样就可以直接获取sMapper调用.selAll方法啦
ApplicationContext aContext = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentMapperImpl impl = aContext.getBean("studentMapperImpl",cn.jxb.mapper.impl.StudentMapperImpl.class);
System.out.println(impl.selAll());
Tomcat启动加载applicationContext.xml
Tomcat监听器帮助加载Spring的配置文件
Tomcat在启动或者关闭监听器的时候,都会去处理Spring的root容器,与web整合后为WebApplicationContext该类继承自ApplicationContext,说明也是一个容器,在spring-web.jar包中存在一个类:org.springframework.web.context.ContextLoaderListener类,当tomcat启动监听器时,会实例化该类创建WebContextLoaderListener对象所需参数是基于< context-param>标签配置的,我们现在去web.xml中配置:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
因为Spring和Web整合后的所有信息都存放在WebApplicationContext当中,所有Spring为我们封装了一个工具类WebApplicationContextUtils:在Servlet中编写:
ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
StudentMapperImpl impl = context.getBean("studentMapperImpl",cn.jxb.mapper.impl.StudentMapperImpl.class);
注意:要这样配置,必须保证tomcat的jdk版本和java一致,否则将会报错,导致无法访问资源
AOP简介与实现
正常的程序都是纵向的执行流程,所谓的面向切面编程,是在某一个流程的前面或者后面添加流程,称之为:前置通知或者后置通知。不需要修改原有的程序,横向的添加流程。
- 高扩展性(给程序添加流程体现了高拓展性)
- 原有功能相当于释放了部分逻辑,让职责更加明确。(比如将某个类的相关功能分别提取到前置或者后置,那么该类的功能就更加的明确)
面向切面编程是指:在原有的只需流程中,针对某一个或者方法添加通知,形成横切面的过程就叫做面向切面编程
常用的概念:
- 原有的功能:切点(Pointcut)
- 前置通知:在切点之前执行的通知(Before Advice)
- 后置通知:在切点之后执行的通知(After Advice)
- 所有的功能的总和叫做:切面
- 嵌入切面的过程叫做:植入
Schema-based
Spring的aop功能并不是Spring编写的,而是依赖于aspects编写的,所以在导包时,需要导入该包
配置aop所需要的schema
<?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">
</beans>
准备好一个Demo案例,中间写三个demo方法
package cn.jxb.test;
public class Demo {
public void demo1() {
System.out.println("demo1");
}
public void demo2() {
System.out.println("demo2");
}
public void demo3() {
System.out.println("demo3");
}
}
接下来配置一个切点
首先需要新建一个< aop:config>标签,表示使用面向切面的技术,然后需要在里面配置切点< aop:pointcut>注意其中必须要配置类以及说明方法名称和参数,例如:
<aop:config>
<aop:pointcut expression="execution(* cn.jxb.test.Demo.demo2())" id="mypoint"/>
</aop:config>
expression表达了一个类加方法名称和参数说明,假如有一个参数:
expression="execution(* cn.jxb.test.Demo.demo2(String)) and arg(name)"
假如有多个参数:
expression="execution(* cn.jxb.test.Demo.demo2(String,int) )and args(name,age)"
光有切点是不可以的,我们需要准备一个切面才能看出效果来,使用< aop:advisor>标签:
<bean id="mybefore" class="cn.jxb.advice.MyBeforeAdvice"/>
<aop:config>
<aop:pointcut expression="execution(* cn.jxb.test.Demo.demo2())" id="mypoint"/>
<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/>
</aop:config>
给这个切点添加了一个前置通知,其中< bean>里面是用来实现前置通知功能的类,需要在< aop:advisor>中的advice-ref属性引用,并且在pointcut-ref属性中指明切点id名称,我们先实现一下前置通知类:
package cn.jxb.advice;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class MyBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("执行前置通知");
}
}
首先需要是实现MethodBeforeAdvice类,并且复写before方法,由于我们的Demo类必须7要交给Spring管理时,才能够看到aop的效果呢,所以最后需要配置< bean>:
<bean id="mybefore" class="cn.jxb.advice.MyBeforeAdvice"/>
<aop:config>
<aop:pointcut expression="execution(* cn.jxb.test.Demo.demo2())" id="mypoint"/>
<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/>
</aop:config>
<bean id="demo" class="cn.jxb.test.Demo"/>
开始测试一下
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = context.getBean("demo",cn.jxb.test.Demo.class);
demo.demo1();
demo.demo2();
demo.demo3();
测试结果:
demo1
执行前置通知
demo2
demo3
同理我们编写后置通知:
package cn.jxb.advice;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class MyAfterAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
System.out.println("执行后置通知");
}
}
然后配置一下通知:
<bean id="mybefore" class="cn.jxb.advice.MyBeforeAdvice"/>
<bean id="myafter" class="cn.jxb.advice.MyAfterAdvice"/>
<aop:config>
<aop:pointcut expression="execution(* cn.jxb.test.Demo.demo2())" id="mypoint"/>
<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/>
<aop:advisor advice-ref="myafter" pointcut-ref="mypoint"/>
</aop:config>
<bean id="demo" class="cn.jxb.test.Demo"/>
最后的运行结果:
demo1
执行前置通知
demo2
执行后置通知
demo3
通配符
看一下之前的例子:
execution(* cn.jxb.test.Demo.demo2())
如果想使cn.jxb.test.Demo类中的所有方法形成切面:
execution(* cn.jxb.test.Demo.*())
再此基础上使得所有有参数方法形成切面:
execution(* cn.jxb.test.Demo.*(..))
如果想使得cn.jxb.test包下的所有类生效:
execution(* cn.jxb.test.*.*(..))
其他的配置同理,注意第一个号指的是:申请通配符*
AspectJ异常通知
首先随意编写一个异常处理类
package cn.jxb.throwe;
public class MyThrow {
public void show(){
System.out.println("触发异常啦");
}
}
要想要Spring捕获到异常,我们的Demo必须抛出异常,不可以捕获处理:
package cn.jxb.test;
public class Demo {
public void demo1() {
System.out.println("demo1");
}
public void demo2() throws Exception{
int a = 1;
int b = 0;
int c = a/b;
}
public void demo3() {
System.out.println("demo3");
}
}
配置文件
<bean id="mythrow" class="cn.jxb.throwe.MyThrow"/>
<aop:config>
<aop:aspect ref="mythrow">
<aop:pointcut expression="execution(* cn.jxb.test.Demo.demo2())" id="mypoint"/>
<aop:after-throwing method="show" pointcut-ref="mypoint"/>
</aop:aspect>
</aop:config>
<bean id="demo" class="cn.jxb.test.Demo"/>
< aop:aspect>标签内ref属性填写我们自定义的异常处理类,然后在< aop:after-throwing>中的method属性里面填写异常类的方法,如果切点发生异常则会调用我们自定义的异常方法。
事务回滚
以后在做开发的过程中,Spring-Aop都拦截Service的方法,一旦触发异常,那么就会执行事务回滚,所以不可以在Service当中try-catch进行异常处理,否则Spring将无法捕获异常进行事务回滚
配置异常处理参数
package cn.jxb.throwe;
public class MyThrow {
public void show(Exception e){
System.out.println("触发异常啦"+e);
}
}
只需要在throwing属性当中写入参数名称
<aop:after-throwing method="show" throwing="e" pointcut-ref="mypoint"/>
环绕通知(Schema-based)
前置通知和后置通知都写在同一个类当中,这就是一个环绕通知
编写环绕通知
package cn.jxb.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAround implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("前置通知");
invocation.proceed();//放行切点
System.out.println("后置通知");
return null;
}
}
MethodInterceptor是一个拦截器,实现invoke方法的时候,需要调用invocation.proceed();这是指调用切点要执行的方法,那么在其前面的代码就是前置,后面的代码就是后置
编写切点
package cn.jxb.test;
public class Demo {
public void demo1() {
System.out.println("demo1");
}
}
配置aop
<bean id="myAround" class="cn.jxb.advice.MyAround"/>
<aop:config>
<aop:pointcut expression="execution(* cn.jxb.test.Demo.demo1())" id="mypoint"/>
<aop:advisor advice-ref="myAround" pointcut-ref="mypoint"/>
</aop:config>
<bean id="demo" class="cn.jxb.test.Demo"/>
测试运行
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = context.getBean("demo",cn.jxb.test.Demo.class);
demo.demo1();
AspectJ简单实现
直接看代码
两个标签都可以配置后置通知:其区别是:< aop:after>标签可以在出现异常的时候继续执行;< aop:after-returning>只有在程序正常运行的时候才会继续运行
package cn.jxb.advice;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAdvice {
public void before(){
System.out.println("前置");
}
public void after(){
System.out.println("后置");
}
public void around(ProceedingJoinPoint point) throws Throwable{
System.out.println("环绕前置");
Object result = point.proceed();
System.out.println("环绕后置");
}
}
Demo
package cn.jxb.test;
public class Demo {
public void demo1() {
System.out.println("demo1");
}
}
applicationContext.xml
<bean id="demo" class="cn.jxb.test.Demo"/>
<bean id="myadvice" class="cn.jxb.advice.MyAdvice"/>
<aop:config>
<aop:aspect ref="myadvice">
<aop:pointcut expression="execution(* cn.jxb.test.Demo.demo1())" id="mypoint"/>
<aop:before method="before" pointcut-ref="mypoint"/>
<aop:after method="after" pointcut-ref="mypoint"/>
<aop:around method="around" pointcut-ref="mypoint"/>
</aop:aspect>
</aop:config>
测试
package cn.jxb.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = context.getBean("demo",cn.jxb.test.Demo.class);
demo.demo1();
}
}
通知中获取切点参数(AspectJ)
首先需要在Demo的方法中传递参数
package cn.jxb.test;
public class Demo {
public void demo1(String name,int age) {
System.out.println("demo1姓名"+name+"年龄:"+age);
}
}
前置通知也需要配置参数
package cn.jxb.advice;
public class MyAdvice {
public void before(String name,int age){
System.out.println("前置通知获取的参数姓名:"+name+"年龄:"+age);
}
}
配置文件
<bean id="demo" class="cn.jxb.test.Demo"/>
<bean id="myadvice" class="cn.jxb.advice.MyAdvice"/>
<aop:config>
<aop:aspect ref="myadvice">
<aop:pointcut expression="execution(* cn.jxb.test.Demo.demo1(String,int)) and args(name,age)" id="mypoint"/>
<aop:before method="before" pointcut-ref="mypoint" arg-names="name,age"/>
</aop:aspect>
</aop:config>
注意上面的两个属性:
- arg-names="name,age"
- execution(* cn.jxb.test.Demo.demo1(String,int)) and args(name,age)
当中第一个name和第二个name名称要一致,age也是如此,并且必须与前置通知的参数名称一致,但是可以和demo方法中的参数名称不一致
配置多个切点和参数
<aop:aspect ref="myadvice">
<aop:pointcut expression="execution(* cn.jxb.test.Demo.demo1(String,int)) and args(name,age)" id="mypoint"/>
<aop:pointcut expression="execution(* cn.jxb.test.Demo.demo1(String)) and args(name)" id="mypoint1"/>
<aop:before method="before" pointcut-ref="mypoint" arg-names="name,age"/>
<aop:before method="before1" pointcut-ref="mypoint1" arg-names="name"/>
</aop:aspect>
匹配方法:
package cn.jxb.advice;
public class MyAdvice {
public void before(String name,int age){
System.out.println("前置通知获取的参数姓名:"+name+"年龄:"+age);
}
public void before1(String name){
System.out.println("前置通知1获取的参数姓名:"+name);
}
}
匹配Demo:
package cn.jxb.test;
public class Demo {
public void demo1(String name,int age) {
System.out.println("demo1姓名"+name+"年龄:"+age);
}
public void demo1(String name) {
System.out.println("demo1姓名"+name);
}
}
开始测试:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = context.getBean("demo",cn.jxb.test.Demo.class);
demo.demo1("张三",19);
demo.demo1("李四");
注解通知(基于Aspect)
平常很少使用,为了面试还是要了解一下的
配置扫描包
首先引入context的schem
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
Spring不会自己寻找注解,必须告诉Spring哪个包下有注解
<context:component-scan base-package="cn.jxb.advice"></context:component-scan>
如果是多个包,使用逗号分隔。
替代< bean>标签
在类上注解:Component
package cn.jxb.test;
import org.springframework.stereotype.Component;
//类名称默认首字母变小写,变成被Spring管理的组件
@Component()
public class Demo {
public void demo1(String name,int age) {
System.out.println("demo1姓名"+name+"年龄:"+age);
}
public void demo1(String name) {
System.out.println("demo1姓名"+name);
}
}
自定义名称:@Component("demo123")
配置切点
package cn.jxb.test;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//类名称默认首字母变小写,变成被Spring管理的组件
@Component()
public class Demo {
@Pointcut("execution(* cn.jxb.test.Demo.demo1())")
public void demo1() {
System.out.println("调用切点");
}
}
完善自己的Advice
package cn.jxb.advice;
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;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Before("cn.jxb.test.Demo.demo1()")
public void before(){
System.out.println("前置通知");
}
@After("cn.jxb.test.Demo.demo1()")
public void after(){
System.out.println("后置通知");
}
@Around("cn.jxb.test.Demo.demo1()")
public void myAround(ProceedingJoinPoint point) throws Throwable{
point.proceed();//调用切点
}
}
配置文件添加动态代理和扫描
<context:component-scan base-package="cn.jxb.advice,cn.jxb.test"></context:component-scan>
<!--
true表示使用cglib动态代理
false表示JDK的动态代理
-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
梳理一下步骤(导包完成的情况下只需要如下四步)
1.配置applicationContext.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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="cn.jxb.test"></context:component-scan>
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>
2.编写切点
package cn.jxb.test;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
public class Demo {
//配置切点:必须写一个名称
@Pointcut("execution(* cn.jxb.test.Demo.show())")
public void show(){
System.out.println("我是切点");
}
}
3.编写通知
package cn.jxb.test;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
//表示该类是一个通知切面的类,这里的方法都是通知方法
@Aspect
public class Advice {
//配置前置切点:放入切点名称
@Before("cn.jxb.test.Demo.show()")
public void myBefore(){
System.out.println("我是前置通知");
}
}
4.编写测试类
package cn.jxb.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext aContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = aContext.getBean("demo",cn.jxb.test.Demo.class);
demo.show();
}
}
静态代理设计模式
学习静态代理的设计模式主要是为了学习aop的底层原理
比如我们现在需要找马云聊聊人生,你肯定不能直接找他,而是先联系他的秘书。那么代理设计模式中的几个角色:
- 真实对象:马云
- 代理对象:秘书
- 抽象对象:聊聊人生
这个关系很明显了,代理设计模式的优点:
- 保护真实对象
- 让真实对象职责更加明确:比如马云的事情很多,如果行程之类的都是马云一个人承担,就会很累,所以这些事情交给秘书处理,就如同AOP中的切点只负责主要功能就行,其他的交给前置切点或者后置切点完成,使得职责更加明确
- 扩展:秘书可以做很多事情,除了职责还可以提供联系方式等等。
我们形象的创建几个类:
public interface 抽象功能 {
void 谈谈理想();
}
不管是马云还是秘书,都需要实现抽象功能:
public class 马云 implements 抽象功能{
@Override
public void 谈谈理想() {
System.out.println("马云:理想很美好,现实很残酷!");
}
}
下面设计秘书:你可以很清楚的看到秘书所做的事情还有自己的拓展。
public class 秘书 implements 抽象功能{
private 马云 my = new 马云();
@Override
public void 谈谈理想() {
System.out.println("请问你有预约吗?");
//如果有预约那么就调用马云的谈谈理想
my.谈谈理想();
//谈完了
System.out.println("秘书送客");
}
}
我们正真要访问马云的时候,其实是对秘书进行操作,所以保护了真实的对象。
public class 我 {
public static void main(String[] args) {
秘书 m = new 秘书();
m.谈谈理想();
}
}
静态代理的缺点
显而易见:当抽象功能比较多时,代理类中的方法也要编写很多方法。动态代理就解决了该现象。
JDK动态代理
常用的动态代理有两种:Jdk提供、cglib提供。动态代理的出现就是为了解决静态代理的缺陷
JDK代理优点:
- jdk自带,不需要引入jar包
JDK代理缺点:
- 真实对象必须实现接口
- 效率不高(利用反射机制)
cglib刚好相反,哈哈。
在java.lang.reflect中有个Proxy的类:文档中告诉我们需要实现InvocationHandler:
public interface 抽象功能 {
void 谈谈理想();
}
public class 马云 implements 抽象功能{
@Override
public void 谈谈理想() {
System.out.println("马云:理想很美好,现实很残酷!");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class 秘书 implements InvocationHandler{
马云 小马云 = new 马云();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//参数:proxy:代理对象,method:方法,args:参数
System.out.println("秘书:接收预约");
//传入真实对象
Object result = method.invoke(小马云,args);
System.out.println("秘书:记录访客信息");
return result;
}
}
import java.lang.reflect.Proxy;
public class 我 {
public static void main(String[] args) {
/**
* 秘书因为实现了InvocationHandler接口后其实是一个处理程序
*/
秘书 ms = new 秘书();
抽象功能 cxgn = (抽象功能) Proxy.newProxyInstance(
我.class.getClassLoader(),//提供一个类加载器:JVM任何类的加载器其实都是一样的
new Class[]{抽象功能.class},//Proxy需要的是是实现接口
ms//传入处理程序:实际上cxgn.谈谈理想();是传给秘书回调函数的参数:method了,再通过反射调用真实对象的谈谈理想();
);
cxgn.谈谈理想();
}
}
cglib动态代理
基于字节码机制的,运行效率高于jdk动态代理
他的原理是生成真实代理对象的子类:例如:
public class 马云子类 extends 马云{
@Override
public void 谈谈理想() {
System.out.println("马云:你好啊!");
super.谈谈理想();//调用父类!
System.out.println("马云:拜拜啦~");
}
}
看看上面的代码:其实它生成的子类对象就这么回事(原理)。
导入jar包
导入:cglib-2.2.2.jar,这个jar包并不陌生,在使用MyBatis的时候遇到过这个包,除了这个包以外,还需要asm-3.3.1.jar字节码解析工具包!
开始实现
public interface 抽象功能 {
void 谈谈理想();
}
真实类:
public class 马云 implements 抽象功能{
@Override
public void 谈谈理想() {
System.out.println("马云:理想很美好,现实很残酷!");
}
}
代理类(处理程序):
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class 秘书 implements MethodInterceptor{
@Override
public Object intercept(
Object 生成的子类对象,
Method 父类代理的方法,
Object[] 参数,
MethodProxy 子类生成的代理方法
) throws Throwable {
System.out.println("秘书:预约时间");
//这两个方法调用都是一样的效果
父类代理的方法.invoke(生成的子类对象, 参数);
子类生成的代理方法.invokeSuper(生成的子类对象, 参数);
//如果这么写:就是死循环
子类生成的代理方法.invoke(生成的子类对象, 参数);
System.out.println("秘书:登记访客记录");
return null;
}
}
测试类:
import net.sf.cglib.proxy.Enhancer;
public class 我 {
public static void main(String[] args) {
/**
* 秘书因为实现了InvocationHandler接口后其实是一个处理程序
*/
Enhancer enhancer = new Enhancer();
//告诉它:我要生成哪个类的子类?
enhancer.setSuperclass(马云.class);
//告诉他:处理程序是谁?
enhancer.setCallback(new 秘书());
//产生真实对象的子类:
马云 my = (马云)enhancer.create();
//调用方法:
my.谈谈理想();
}
}
在使用SpringAOP时:只要出现Proxy和真实对象转换异常,我们一般会采用cglib的动态代理的方式:true为cglib,false(默认)使用jdk动态代理
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
自动注入
在Spring配置文件中对象名称和ref="id"的id名称相同就可以使用自动注入,可以不配置< property>
我们先编写两个类:
public class Teacher {
@Override
public String toString() {
return "老师";
}
}
public class People {
private Teacher teacher;
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
}
如果按照我们之前的方法,在People中注入Teacher对象,是这么写的:
<bean id="teacher" class="cn.jxb.people.Teacher"></bean>
<bean id="people" class="cn.jxb.people.People">
<property name="teacher" ref="teacher"></property>
</bean>
如果使用自动注入:
使用:autowire="byName":指的是People中的teacher对象名称和配置文件全局中< bean>的id名称一致时,自送注入:
<bean id="teacher" class="cn.jxb.people.Teacher"></bean>
<bean id="people" class="cn.jxb.people.People" autowire="byName"></bean>
使用:autowire="byType":指的是People中需要注入的随对象类型和配置文件中< bean>标签的class属性对应时,也会自动注入:
<bean id="teacher" class="cn.jxb.people.Teacher"></bean>
<bean id="people" class="cn.jxb.people.People" autowire="byType"></bean>
使用:autowire="constructor":首先必须提供构造方法:
public class People {
private Teacher teacher;
public People(Teacher teacher){
this.teacher = teacher;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return teacher.toString();
}
}
然后使用构造方法自动注入的时候,必须有一个< bean>中的id和构造参数名称一致,其实也相当于byName的方式:
<bean id="teacher" class="cn.jxb.people.Teacher"></bean>
<bean id="people" class="cn.jxb.people.People" autowire="constructor"></bean>
配置全局自动注入
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
default-autowire="byName"
>
<bean id="teacher" class="cn.jxb.people.Teacher"></bean>
<bean id="people" class="cn.jxb.people.People"></bean>
</beans>
关键就是:default-autowire="byName"这句代码,设置的好处:如果不设置,来一个类型我们要自己配一个标签很麻烦。
加载属性文件
首先在src下面创建一个p.properties的文件
ttt.name=Hellow
然后让属性文件交给Spring容器进行管理
<context:property-placeholder location="classpath:p.properties"/>
编写实体类,获取这个属性值:
public class Teacher {
@Value("${ttt.name}")
private String name;
public void showName(){
System.out.println("属性值:"+this.name);
}
}
注意:只要用到注解的地方,必须要让Spring扫描到,不然它不知道那个类使用了注解:
<context:component-scan base-package="cn.jxb.people"></context:component-scan>
最后我们把Teacher类交给Spring管理:
<bean id="teacher" class="cn.jxb.people.Teacher"></bean>
测试代码:
ApplicationContext aContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher t = aContext.getBean("teacher",cn.jxb.people.Teacher.class);
t.showName();
最后我们整理一下全部代码:
cn.jxb.people.People:
package cn.jxb.people;
import org.springframework.beans.factory.annotation.Value;
public class Teacher {
@Value("${ttt.name}")
private String name;
public void showName(){
System.out.println("属性值:"+this.name);
}
}
/src/p.properties
ttt.name=Hellow
/src/applicationContext.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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:p.properties"/>
<context:component-scan base-package="cn.jxb.people"></context:component-scan>
<bean id="teacher" class="cn.jxb.people.Teacher"></bean>
</beans>
Scope属性
这是bean标签的属性:控制对象的有效范围(单例,多例等)
< bean>对应的对象默认是单例的,无论获取多少次都是同一个对象,我们做一个实验:
配置文件:
<bean id="teacher" class="cn.jxb.people.Teacher"></bean>
测试,获取两次该对象:
ApplicationContext aContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher t1 = aContext.getBean("teacher",Teacher.class);
Teacher t2 = aContext.getBean("teacher",Teacher.class);
System.out.println(t1==t2);//true
如果我们加入scope="prototype",取一次对象,new 一次。
<bean id="teacher" class="cn.jxb.people.Teacher" scope="prototype"></bean>
再来测试:
ApplicationContext aContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher t1 = aContext.getBean("teacher",Teacher.class);
Teacher t2 = aContext.getBean("teacher",Teacher.class);
System.out.println(t1==t2);//false
scope其他可取值(下面的几乎用不上,了解一下)
- request:每次请求重新实例化
- session:每个会话对象内,对象是单例的
- application:application中的对象内是单例
- global session:spring推出的对象
单例设计模式:懒汉式
之所以叫做懒汉式:只有对象调用方法的时候才会获取到实例对象,所以懒。
大致雏形
public class SingleTon {
private static SingleTon singleTon;
public SingleTon(){}
public static SingleTon getInstence(){
if(singleTon==null){
singleTon = new SingleTon();
}
return singleTon;
}
}
采用DCL双重检测的写法,第一层if是提高效率,synchronized是为了保证实例化的对象在多线程并发访问的时候保持唯一性,第二层if则是判断是否需要创建实例对象。
最终成品
public class SingleTon {
private static SingleTon singleTon;
public SingleTon(){}
public static SingleTon getInstence(){
if(singleTon==null){
synchronized (SingleTon.class) {
if(singleTon==null){
singleTon = new SingleTon();
}
}
}
return singleTon;
}
}
弊端就是:由于添加了锁,所以降低了效率。
单例设计模式:饿汉式
public class SingleTon {
private static SingleTon singleTon = new SingleTon();
public SingleTon(){}
public static SingleTon getInstence(){
return singleTon;
}
}
即解决了多线程同时访问造成对象不唯一,也解决了加锁使得效率低的问题。
声明式事务
首先了解一下编程式事务:比如我们之前get到一个session后,执行事务然后commit如果出现异常就是事务回滚rollback,这就是编程式事务。
声明式事务:事务控制的代码以及由Spring写好,程序员只需要声明出哪些方法拥有事务或者控制。
- 声明式事务针对的是业务实现类的方法,比如:ServiceImpl类、
- 事务管理器是基于通知(advice)的,也就是说我们在声明事务的时候就是AOP的过程,无非是加了事务控制而已
配置声明式事务:
首先需要tx的命名空间:
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
假如我们的业务类是:
public interface UserService {
int insert();
}
注意:dataSource是和之前整合MyBatis一样的:
<!-- 配置事务声明 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 哪些方法需要事务? -->
<tx:method name="insert"/>
<!-- 只要以ins开头的都需要事务管理 -->
<tx:method name="ins*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!-- 哪些方法是切点? -->
<aop:pointcut expression="execution(* cn.jxb.service.impl.*.*(..))" id="mypoint" />
<aop:advisor advice-ref="txAdvice" />
</aop:config>
属性解释:
属性:read-only:是否是只读事务,true就是只读,可能速度比较快
<tx:method name="*" read-only="true"/>
所以我们平时这么配置:
<tx:method name="ins*"/>
<tx:method name="upd*"/>
<tx:method name="del*"/>
<tx:method name="*" read-only="true"/>
言外之意:除了上面三个会用到非只读操作,其他情况全是只读的,起到一个优化作用。
属性:propagation:控制事务传播行为,指的是当一个具有事务控制的方法被另一个具有事务控制的方法调用时,此时事务如何管理?
- REQUIRED:如果当前有事务,就在事务中执行,否则就新建一个事务
- SUPPORTS:如果当前有事务,就在事务中执行,否则就在非事务状态下执行
- MANDATORY:必须在事务中执行,没有事务就报错(傲娇!)
- REQUIRES_NEW:没有事务就新建,有就挂起(你靠一边去,我先执行完了再执行你)
- NOT_SUPPORTED:必须在非事务状态下,当前没事务就正常执行,否则就挂起
- NEVER:没事务就正常执行,有事务就报错(不讲道理的小公主啊!)
- NESTED:必须在事务状态下执行,没有就新建,有事务就创建嵌套事务
注意:以上的属性并不是Spring的概念,而是数据库的事务属性特征!
事务隔离级别
在多线程或者并发访问下,如何保证访问到的数据是具有完整性的?
首先需要了解几个概念?
- 脏读:一个事务A读取到另一个事务B修改数据后未提交的数据,此时A事务读取的数据可能和数据库中的数据不一致,此时:数据是脏数据,该过程就是脏读
- 不可重复读:主要针对的是某行数据(或行中某列)、修改操作。举个例子:老公去银行取钱,查询一次余额为:5000元,此时他老婆取出3000元,老公再次查询时只剩下2000元了,原本老公打算取3000的,但是现在不能取了,余额不足了,这就是重复都,解决方案:加锁:老公在取钱的时候上锁,此时他老婆就不能取了。
- 幻读:主要针对新增和删除(两次事务的结果)。举个例子:公司有两个会计,第一个会计查询现在需要给10个人发工资,此时第二个会计新增了一个需要被发工资的员工,而对于会计一而言他还不知道,这就是幻读。此时就需要限制整块数据(整个表),而不可重复读只限制了一行数据。
事务隔离级别的意义:解决上面三个问题。
属性:isolation值:
- DEFAULT:由底层数据库自己默认事务隔离级别
- READ_UNCOMMITTED:能读取到未提交数据(啥问题都能出现,效率高,但是安全最差)
- READ_COMMITTED:只能读取已提交数据(防止脏读,但是其他两个问题不能解决)
- REPEATABLE_READ:读取的数据会被添加锁,防止其他事务修改数据(防止脏读、不可重复度)
- SERIALIZABLE:排队操作,对整个表加锁,一个事务在操作时,另一个事务要等待他完成,最安全,但是效率最低(跟钱相关的业务,全部要最安全!)
事务回滚
属性:rollback-for="java.lang.Exception"表示出现任何异常时都要回滚事务。
<tx:method name="upd*" rollback-for="java.lang.Exception"/>
属性:no-rollback-for="":当出现指定异常时不回滚。
Spring常用注解
加载类:
- @Component:配置一个< bean>
- @Service与@Component功能相同,但是它写在ServiceImpl类上的(这只是建议我们这么写)
- @Repository与@Component功能相同,建议写在数据访问层类上
- @Controller与@Component功能相同,建议我们写在控制器类上(之所以现在用不上,是因为Srping不能管理Servlet,但是SpringMVC就要天天写这个玩意儿)
注入属性:
- @Resource:这是javax提供的,默认按照byName注入,如果没有就按照byType注入。(类中无需提供get/set方法)
- @Autowird:这是Spring提供的,默认按照byType注入。(类中无需提供get/set方法)
属性文件:
- @Value:获取属性文件的内容
AOP:
- @Pointcut:定义切点
- @Aspect:定义切面
- @Before:前置通知
- @After:后置通知
- @AfterReturning:后置通知:必须切点正确执行
- @AfterThrowing:异常通知
- @Arround:环绕通知
网友评论