Spring5学习总结
博客:
地址:https://www.cnblogs.com/ShanYu-Home/
共计18540字(不计MD标识) |
---|
目录表
- Spring5学习总结
- 1.Spring注入小结
- 2.Spring的对象创建
- 3.工厂创建的对象的生命周期
- 4.自定义类型转换器
- 5Spring动态代理开发小结
- 6 Spring和Mybaits的整合
- 7 Spring控制事务的开发
- 8 Spring的事务处理
- 9 Spring基本注解编程
- 10.Spring 高级注解编程一
- 11 Spring 高级注解编程二
- 12.对一些问题的研究
- 13.spring 动态代理开发的切入点表达式
1.Spring注入小结
1.基本注入类型注入
注入前的准备
<bean id="#配置文件的唯一标识" class="#Spring工厂将要创建的类的全限名命名">
<property name="#填写所赋予的变量">
#注入值
</property>
</bean>
JDK类型注入
1.8种基本类型加Stirng
#注入值
<value>#值</value>
2.数组
<list>
<value>#值1</value>
<value>#值2</value>
<value>#值3</value>
</list>
3.set集合
- set集合是无序的,取出值不会像数组一样有序
- set集合有泛型约束如set
- 如果没有泛型约束 set可以装任何类型的值
#有泛型约束 8种基本类型加Stirng
<set>
<value>#值1</value>
<value>#值2</value>
<value>#值3</value>
</set>
#无约束
<set>
<ref bean #用户自定义类型
<set #还可存放set类型
</set>
4.list集合
<list>
<value>#值1</value>
<value>#值2</value>
</list>
#和set类似
<list>
<ref bean #用户自定义类型
<set #还可存放set类型
</list>
5.Map集合
<map>
#entry代表一个键值对
<entry>
<key>
<value>#键值</value>#如果Map是字符串泛型则内嵌<value></value>
</key>
<value>#值</value>
</entry>
</map>
6.Properties
Properties是一种特殊的Map 键值都是String
<props>
<prop key="#键1">#直接写值1</prop>
<prop key="#键2">#直接写值2</prop>
</props>
2.自定义类型注入
方式一
步骤
1. 在实现类中实现自定义的类型
2. 在实现类中提供set和get方法
3. 在配置文件中注入
例如:
实现类MySpringTest:
实现自定义类型UserDAO
//实现自定义类型
private UserDAO testDao;
//提供set和get方法
public UserDAO getTestDao() {
return testDao;
}
public void setTestDao(UserDAO testDao) {
this.testDao = testDao;
}
配置文件中注入
<bean id="testDao" class="org.MySpringTest">
<property name="testDao">
<bean class="org.UserDAOImpl"/>
</property>
</bean>
注:在property里的bean class是实现了testDao这个自定义类型,但是要实现这个自定义类的的实现。此处为UserDAO接口的实现类UserDAOImpl
存在的问题
1.配置文件的冗余,如果需要多次使用UserDAO则需要多次使用注入
2.多次创建对象会使得JVM内存紧张
方式二
先实现出自定义类的实现
<bean id="DAO"class="org.UserDAOImpl"/>
在实现类的配置文件里面引用自定义类
<bean id="testDao" class="org.MySpringTest">
<property name="testDao">
<ref bean="DAO"/>
</property>
</bean>
实现了只创建一次多次使用节约内存
3.简化写法注入
8种基本类型加Stirng
<bean id="#配置文件的唯一标识" class="#Spring工厂将要创建的类的全限名命名">
<property name="#填写所赋予的变量" value="#注入值">
</bean>
用户自定义类型
<bean id="#配置文件的唯一标识" class="#Spring工厂将要创建的类的全限名命名">
<property name="#填写所赋予的变量">
<ref bean="#引用的id"/>
</property>
</bean>
P命名空间
在使用P命名空间以前要在标头beans加入
xmlns:p="http://www.springframework.org/schema/p"
8种基本类型加Stirng
<bean id="#配置文件的唯一标识"
class="#Spring工厂将要创建的类的全限名命名"
p:#变量名="#值"/>
自定义
<bean id="#配置文件的唯一标识"
class="#Spring工厂将要创建的类的全限名命名"
p:#变量名-ref="#值"/>
4.构造注入
1.普通构造方法
开发步骤:
1.提供有参数构造方法(有参数!)
2.提供配置文件注入
例如:参数构造方法
public class Customer implements Serializable {
private String name;
private int age;
public Customer(String name,int age){
this.age=age;
this.name=name;
}
参数顺序从左到右
在配置文件注入
<bean id="customer" class="org.constructer.Customer">
<constructor-arg value="SY"/>
<constructor-arg value="21"/>
</bean>
结果为name=SY age=21
2.重载的构造方法
1.不同的参数数量
Spring通过constructor-arg的数量辨别
2.相同的参数数量,Spring通过参数类型判断,引入type
<constructor-arg type="java.lang.String" value="SY"/>
2.Spring的对象创建
原理:工厂设计模式,通过反射创建对象。
Spring工厂分类
非web环境:ClassPathXmlApplicationContext
web环境:XmlWebApplicationContext
简单原理
//String Key是从Properties文件读取的键值对,类似Spring的<bean>
public Object getBean (String Key){
Object ret=null;
try {
//通过反射获取对象
Class clazz=Class.forName(env.getProperty(Key));
//底层调用了new
ret =clazz.newInstance();
}catch (Exception e){
e.printStackTrace();
}
return ret;
1.简单对象的创建
简单对象指的是直接通过new就可以得到的对象
步骤:
1.读取配置文件和写入配置文件
2.获取Key-value
3.获取对象内容,一般是为对象内容提供set/get方法
读取配置文件
ApplicationContext context=new ClassPathXmlApplicationContext("/ApplicationContext.xml");
Object object= (Object) context.getBean("object");
写入配置文件
<bean id="object" class="#object的实现类的全限名命"></bean>
获取内容
//直接调用object的get方法
Object object.getxxx();
2.复杂对象的创建
不能通过直接new的对象比如Connection SessionFactory 等
实现复杂对象的方式:
1.FactoryBean
开发步骤
1.实现FactoryBean接口
重写三个方法
2.在配置文件中配置
3.创建和使用
实现FactoryBean接口(例如实现Connection )
//给FactoryBean指定泛型为Connection
public class FactoryBean implements org.springframework.beans.factory.FactoryBean<java.sql.Connection> {
//提供对应的set/get方法方便注入
private String Driver;
private String Url;
private String user;
private String password;
//创建连接复杂对象
@Override
public java.sql.Connection getObject() throws Exception {
Class.forName(Driver);
java.sql.Connection conn = DriverManager.getConnection(Url,user,password);
return conn;
}
//返回值类型是Connection
@Override
public Class<?> getObjectType() {
return Connection.class;
}
//指定是否多次创建
@Override
public boolean isSingleton() {
return false;
}
public String getDriver() {
return Driver;
}
public void setDriver(String driver) {
Driver = driver;
}
public String getUrl() {
return Url;
}
public void setUrl(String url) {
Url = url;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
配置文件中注入,和之前一样
<bean id="conn" class="org.FactoryBean.FactoryBean"> <property name="Driver" value="com.mysql.jdbc.Driver"/> <property name="password" value="123456"/> <property name="Url" value="jdbc:mysql://localhost:3306/jdbc_test"/> <property name="user" value="root"/> </bean>
创建和使用
注意:在获取conn时必须加&
public void connectionFactoryBean(){ ApplicationContext context =new ClassPathXmlApplicationContext("/ApplicationContext.xml"); FactoryBean conn= (FactoryBean)context.getBean("&conn"); System.out.println("Conn= "+conn); }
2.实例工厂创建
目的: 使得老系统整合进Spring 使得Spring没有侵入性
开发步骤: 1.老系统代码 2.在配置文件中配置
例如:
老系统代码,简单的创建连接代码
public class OldFactory { public Connection getconn() throws ClassNotFoundException, SQLException { Connection connection=null; Class.forName("com.mysql.cj.jdbc.Driver"); connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test,"root","atgxstuSf2<e"); return connection; }}
在配置文件中
俩步骤
1.配置老代码的工厂
2.在整合配置中使用老代码工厂的id和指定方法
<bean id="oldconn" class="org.FactoryBean.OldFactory"/><bean id="Newconn" factory-bean="oldconn" factory-method="getconn"/>
3静态工厂
<bean id="XXX" Class="老代码的类" factory-method="老代码方法"/>
3.工厂创建的对象的生命周期
1.工厂创建对象的阶段
在创建对象时同时会优先调用无参构造方法
工厂在何时创建对象
1.bean标签内指定scope=“singleton”
Spring将会在工厂创建时创建对象
例如
在xml文件中
<bean id="product" class="org.LifeCricle.LifeTestObject"scope="singleton"/>
实现类中
public class LifeTestObject {
public LifeTestObject(){
//构造方法
System.out.println("LifeTestObject has product");
}
//自定义的方法在创建对象时打印出语句
public void MyIntMethod(){
System.out.println("LifeTestObject.MyIntMethod");
}
}
那只需要创建工厂即可创建对象
ApplicationContext context = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
结果为
2.bean标签内指定scope=“prototype”
对象将在获取对象时候创建
例如
在XML中
<bean id="product" class="org.LifeCricle.LifeTestObject"scope="prototype"/>
实现类依然如上
我们只创建工厂发现并没有打印出语句,所以对象并没有被创建
ApplicationContext context = new ClassPathXmlApplicationContext("/ApplicationContext.xml");
//LifeTestObject lifeTestObject= (LifeTestObject) context.getBean("product");
接着取消注释发现对象被创建了
2初始化阶段
Spring创建完成对象之后,才会调用对象的初始化方法。
开发步骤
- 实现InitializingBean接口,重写afterPropertiesSet()
- 提供普通方法
1.实现InitializingBean接口
public class IntMethodTest implements InitializingBean {
//构造方法
public IntMethodTest(){
System.out.println("IntMethodTest.IntMethodTest");
}
// 初始化普通方法
public void intmethod(){
System.out.println("IntMethodTest.intmethod");
}
//初始化方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("IntMethodTest.afterPropertiesSet");
}
}
2在XML中配置init-method
<bean id="intmethod" class="org.LifeCricle.IntMethodTest" init-method="intmethod" scope="prototype"/>
注意:如果不使用init-method则不会初始化普通方法
运行结果
细节:
我们可以看到优先度构造方法>初始化方法>普通方法
注入发生在初始化前面
应用场景:IO 数据库 网络配置
3.销毁阶段
Spring 销毁对象发生在工厂关闭时
在关闭工厂我们需要使用Application的父接口ClassPathXmlApplicationContext
例如
1.在实现类继续实现DisposableBean接口
public class IntMethodTest implements InitializingBean, DisposableBean {
public IntMethodTest(){
System.out.println("IntMethodTest.IntMethodTest");
}
public void intmethod(){
System.out.println("IntMethodTest.intmethod");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("IntMethodTest.afterPropertiesSet");
}
//工厂关闭时候的操作
@Override
public void destroy() throws Exception {
System.out.println("IntMethodTest.destroy");
}
//自定义关闭操作
public void Mydestory(){
System.out.println("IntMethodTest.Mydestory");
}
}
2.在XML中指定销毁方法destroy-method
为了验证,所以把对象设置在创建工厂时候创建
<bean id="intmethod" class="org.LifeCricle.IntMethodTest" init-method="intmethod" destroy-method="Mydestory" scope="singleton"/>
3.销毁操作
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("/ApplicationContext.xml");
context.close();
结果如下
使用场景:资源释放,IO关闭,数据库连接关闭
4.自定义类型转换器
1.为什么要自定义类型转换器
在注入时,我们能把配置文件里面<value>标签内的数据赋值给目标类,存储在<value>内的数据是String类型的,那为什么能赋值给如Integer等非字符串类型?
而某些数据类型不可以,比如把java.lang.String的类型转为java.util.Date。
2.类型转换器
把java.lang.String的类型转为java.util.Date,通常做法是使用转换函数
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");//注意月份是MM
Date date = simpleDateFormat.parse("2099-010-02");
3自定义Spring类型转换器
开发步骤
1.实现org.springframework.core.convert.converter.Converter的Converter<s,t>接口
泛型Converter<s,t>指从s类型转为t类型
public class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
return null;
}
实现转换方法
public class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
Date date=null;
try {
SimpleDateFormat trans =new SimpleDateFormat("yyyy-MM-dd");
date=trans.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
//返回值为转换类型
return date;
}
2在XML配置文件中注册
- 先实现刚才创建的类
<bean id="dateConverter" class="org.Coverter.DateConverter"/>
-
再在ConversionServiceFactoryBean里面注册
注意id必须为conversionService
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="dateConverter"/>
</set>
</property>
</bean>
在使用从配置文件注入给Date类型就不会报类型转换异常了
5Spring动态代理开发小结
1.为什么要有动态代理?
-
好处
1.利于程序维护 2.利于原始类功能的增强 3.得益于JDK或者CGlib等动态代理技术使得程序扩展性很强
-
为什么说使得程序扩展性很强?
静态代理运行一个增强类需要编译为.class文件,再进入到虚拟机之中运行,如果增加一个功能,就需要重新编译文件造成维护上的灾难 动态代理会使用JDK或者CGlib等动态代理技术在JVM直接生成字节码文件也称为动态字节码文件,直接可以在虚拟机中运行,且可以在不重新编译前提下运行
2.如何开发动态代理对象
1.MethodBeforeAdvice
1.需要原始对象,被代理对象(Target)
被代理对象的接口
import org.User;
public interface UserService {
//这个User只是象征性的传入个对象
public void register(User user);
public Boolean login(String name, String password);
}
原始对象
import org.User;
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register");
}
@Override
public Boolean login(String name, String password) {
System.out.println("UserServiceImpl.login "+name+" "+password );
return true;
}
}
2.编写额外功能,它实现MethodBeforeAdvice接口(增强类)
实现MethodBeforeAdvice 的运行在目标类之前
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class UserPoxyBefore implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("UserPoxyBefore.before");
}
}
3.在配置文件定义切入点
首先得实现原始类和增强类
<bean id="UserServicePiont" class="org.Service.UserServiceImpl"/>
<bean id="UserPoxyBefore" class="org.Service.UserPoxyBefore" />
再定义切入点
- pointcut表示切入的地方,而里面的expression指定切入的方法,也就是使用这个增强类的地点
- advisor指定用哪个增强类在哪个切入点
<aop:config >
<aop:pointcut id="UserPoxyPC" expression="execution(* *(..))"/>
<aop:advisor advice-ref="UserPoxyBefore" pointcut-ref="UserPoxyPC"/>
</aop:config>
3.调用
调用时的注意事项可以看这个https://www.cnblogs.com/ShanYu-Home/p/14806203.html
@Test
public void test2() {
ApplicationContext context=new ClassPathXmlApplicationContext("/ApplicationContext2.XML");
UserService userService= (UserService) context.getBean("UserServicePiont");
userService.login("SY", "123456");
userService.register(new User());
}
结果,可见代理类确实使用了
UserPoxyBefore.before
UserServiceImpl.login SY 123456
UserPoxyBefore.before
UserServiceImpl.register
2.MethodInterceptor(方法拦截器)
实现的MethodInterceptor可以运行在原始方法前中后
1.实现MethodInterceptor接口
在前面准备好了原始类接着直接开发增强功能就好,开发步骤和上面的一致,只不过第二步变为实现MethodInterceptor接口,如下
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class Intercepter implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return null;
}
}
之后添加
- invocation.proceed() 基本低效为原始方法
Object ret=invocation.proceed();
System.out.println("Intercepter.invoke");//增强的功能
组装起来
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class Intercepter implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object ret=invocation.proceed();
System.out.println("Intercepter.invoke");
return ret;
}
}
2.配置文件
实现Intercepter 即可,后续配置和上面一致
<bean id= "intercepter" class="org.Service.Intercepter"/>
<aop:config >
<aop:pointcut id="UserPoxyPC" expression="execution(* *(..))"/>
<aop:advisor advice-ref="intercepter" pointcut-ref="UserPoxyPC"/>
</aop:config>
3.运行
直接运行上面的调用代码,不用改动,也体现了程序扩展性
结果
UserServiceImpl.login SY 123456
Intercepter.invoke
UserServiceImpl.register
Intercepter.invoke
4.如何让运行intercepter在原始方法的任意位置
由于invocation.proceed() 基本低效为原始方法,所以只需要把invocation.proceed() 放在不同位置即可
如调换位置
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class Intercepter implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Intercepter.invoke");
Object ret=invocation.proceed();
return ret;
}
}
运行结果,可以看到运行位置不同了
Intercepter.invoke
UserServiceImpl.login SY 123456
Intercepter.invoke
UserServiceImpl.register
3.对MethodBeforeAdvice的before方法参数分析
对于before有三个参数
- Method method
- Object[] args
- Object target
我们在它的接口实现设置断点
接着DEBUG 测试方法
看到method就是原始类的方法,也就是在配置文件定义的目标方法(pointcut里的expression)args 就是原始类的方法传输的参数target就是目标类,和JDK动态代理极其类似
接着继续DEBUG
情况和上面一样,User我没有注入数据所以为null
对于MethodInterceptor也大致相同,就不再过多分析
6 Spring和Mybaits的整合
一,整合概述
将MyBatis与Spring进行整合,主要解决的问题就是将SqlSessionFactory对象交由Spring容器来管理,所以,该整合,只需要将SqlSessionFactory的对象生成器SqlSessionFactoryBean注册在Spring容器中,再将其注入给Dao的实现类即可完成整合。、
Mybatis开发过程出现的问题:
- 配置实体别名时候繁琐
- 注册Mybatis主配置文件繁琐
- MybaitsAPI的调用繁琐,即使封装了一次也存在大量代码冗余
二,整合开发步骤
1.开发回顾
Mybatis 常规开发:
- 建立表
- 建立实体
- 创建DAO
- 实现对应DAOMapper文件
- 配置主配置文件
2.Mybaits-Spring整合开发步骤
- 建立表
- 建立实体
- 创建DAO
- 实现对应DAOMapper文件
- 配置Spring配置文件
3.环境配置
需要的额外的Jar包有(只是整合)
- druid
- spring-tx
- mybatis-spring
- spring-jdbc
- mysql-connector-java
- mybatis
4.整合编码
Mybatis-Spring.xml
连接池配置
指定阿里巴巴的Druid连接池
数据库验证信息字段是固定的
<bean id="dataSourse" class="com.alibaba.druid.pool.DruidDataSource" >
<property name="username" value="root"/>
<property name="url" value="jdbc:mysql://localhost:3306/jdbc_test?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"/>
<property name="password" value="123456"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
sqlseesion工厂创建
类型别名使用typeAliases指定,日后使用实体直接使用类名
mapperLocations使用List集合,指定目录,会自动寻找,此处是src/Mapper目录下的的符合*Mapper.xml命名规范的所有文件
注:
org.Mybatis.StudentEntity是实体类
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--指定数据源-->
<property name="dataSource" ref="dataSourse"/>
<!--类型别名,日后直接用类名充当-->
<property name="typeAliases" value="org.Mybatis.StudentEntity"/>
<!--指定Mapper位置-->
<property name="mapperLocations" >
<list>
<value>
<!--通配写法,按照这个命名规范书写-->
classpath:Mapper/*Mapper.xml
</value>
</list>
</property>
</bean>
创建DAO 对象
basePackage指定的是DAO类所在的包
在通过Spring工厂获取对象时使用的时接口名首字母小写,我们在接口命名时就需要约定接口名首字母大写
注:
org.Mybatis是DAO所在的包
<bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--对应上面的SqlSessionFactoryBean的名字-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
<!--MapperScannerConfigurer到DAO下的包自动的找到对应DAO接口来创建对象-->
<!--注意!在通过Spring工厂获取对象时使用的时接口名首字母小写,我们在接口命名时就需要约定接口名首字母大写-->
<property name="basePackage" value="org.Mybatis"/>
</bean>
调用
只需要new出工厂和实体类就可以对Mybtis操作
ApplicationContext context=new ClassPathXmlApplicationContext("Mybatis-Spring.xml");
StudentDAO studentDAO= (StudentDAO) context.getBean("studentDAO");
StudentEntity studentModle = new StudentEntity();
studentModle.setId(1);
studentModle.setEmail("123@before.com");
studentModle.setAge(22);
studentModle.setName("test");
studentDAO.InsertStuinfo(studentModle);
对比原来直接使用mybatisapi确实少了不少代码、
完整代码:
<?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="dataSourse" class="com.alibaba.druid.pool.DruidDataSource" >
<property name="username" value="root"/>
<property name="url" value="jdbc:mysql://localhost:3306/jdbc_test?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"/>
<property name="password" value="123456"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<!--sqlseesion工厂创建-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--指定数据源-->
<property name="dataSource" ref="dataSourse"/>
<!--类型别名,日后直接用类名充当-->
<property name="typeAliases" value="org.Mybatis.StudentEntity"/>
<property name="mapperLocations" >
<list>
<value>
<!--通配写法,按照这个命名规范书写-->
classpath:Mapper/*Mapper.xml
</value>
</list>
</property>
</bean>
<!--创建DAO 对象-->
<bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--对应上面的SqlSessionFactoryBean的名字-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
<!--MapperScannerConfigurer到DAO下的包自动的找到对应DAO接口来创建对象-->
<!--注意!在通过Spring工厂获取对象时使用的时接口名首字母小写,我们在接口命名时就需要约定接口名首字母大写-->
<property name="basePackage" value="org.Mybatis"/>
</bean>
</beans>
7 Spring控制事务的开发
1.常见的控制事务的方法
-
JDBC
Connection.setAutoCommit(fales); Connection.commit(); Connection.rollback();
-
Mybaits
Mybaits自动开启事务 SqlSession(Connection).commit(); SqlSession(Connection).rollback();
相同点:都是使用Connection对象操作事务
2.Spring控制事务
1.传统的事务开发,使用拦截器
public Object invoke(MethodInvocation invocation){
try{
Connection.setAutoCommit(fales);
Connection.commit();
Object ret =invocation.proceed();
Connection.rollback();
}catch(Exception e){
Connection.rollback();
}
return ret;
}
2.使用DataSourceTransactionManager开发
原理:Spring是通过AOP的方式控制事务
那我们就通过AOP的开发步骤来为我们的方法添加事务
AOP的开发步骤
- 原始对象
- 额外方法
- 切入点
- 组装切面
1.原始对象
就是普通业务层的Service的接口实现类
例如:
Service接口
注:StudentEntity是实体
package org.StudentService;
import org.Mybatis.StudentEntity;
public interface Service {
void InsertStudentinfo(StudentEntity studentEntity);
}
Service实现类
注意点:
要调用DAO层的对象的话要声明为成员变量
为其提供ser/get方法
@Transactional注解后面解释
package org.StudentService;
import org.Mybatis.StudentDAO;
import org.Mybatis.StudentEntity;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class ServiceImpl implements Service{
private StudentDAO studentDAO;
@Override
public void InsertStudentinfo(StudentEntity studentEntity){
studentDAO.InsertStuinfo(studentEntity);
//throw new RuntimeException();
}
public StudentDAO getStudentDAO() {
return studentDAO;
}
public void setStudentDAO(StudentDAO studentDAO) {
this.studentDAO = studentDAO;
}
}
2.额外方法
使用Spring提供的DataSourceTransactionManager
它提供的实现类似于传统的事务开发,使用拦截器。
由于是事务开发所以必有Connection对象,那么我们就需要指定出Connection,这指定之前创建的连接池dataSourse的连接对象
在配置文件中如下配置:
<!--为这个DAO实现添加事务-->
<bean id="service" class="org.StudentService.ServiceImpl">
<property name="studentDAO" ref="studentDAO"/>
</bean>
<!--配置事务-->
<bean id="dataSourceTransationManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourse"/>
</bean>
3.切入点
在需要添加事务的方法添加@Transactional注解
4.组装切面
必须注意这个标签是schema/tx下的
在配置文件如下配置DataSourceTransationManager的ID
也就是指定上面的
<tx:annotation-driven transaction-manager="dataSourceTransationManager" proxy-target-class="false"/>
proxy-target-class指定是JDK 还是CGlib动态代理
5.配置文件完整代码
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--连接池配置-->
<bean id="dataSourse" class="com.alibaba.druid.pool.DruidDataSource" >
<property name="username" value="root"/>
<property name="url" value="jdbc:mysql://localhost:3306/jdbc_test?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"/>
<property name="password" value="123456"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<!--sqlseesion工厂创建-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--指定数据源-->
<property name="dataSource" ref="dataSourse"/>
<!--类型别名,日后直接用类名充当-->
<property name="typeAliases" value="org.Mybatis.StudentEntity"/>
<property name="mapperLocations" >
<list>
<value>
<!--通配写法,按照这个命名规范书写-->
classpath:Mapper/*Mapper.xml
</value>
</list>
</property>
</bean>
<!--创建DAO 对象-->
<bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--对应上面的SqlSessionFactoryBean的名字-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
<!--MapperScannerConfigurer到DAO下的包自动的找到对应DAO接口来创建对象-->
<!--注意!在通过Spring工厂获取对象时使用的时接口名首字母小写,我们在接口命名时就需要约定接口名首字母大写-->
<property name="basePackage" value="org.Mybatis"/>
</bean>
<!--为这个DAO实现添加事务-->
<bean id="service" class="org.StudentService.ServiceImpl">
<property name="studentDAO" ref="studentDAO"/>
</bean>
<!--配置事务-->
<bean id="dataSourceTransationManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourse"/>
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransationManager" proxy-target-class="false"/>
</beans>
至此配置完成了,之后直接编写代码即可
很简单就三步
- 指定要处理的方法(用注解方式)
- 配置事务
- 组装(说是通知也行)
8 Spring的事务处理
1. 什么是事务?
保证业务操作完整性的一种数据库机制
事务的4特点: A C I D
1. A 原子性
2. C 一致性
3. I 隔离性
4. D 持久性
2. 如何控制事务
JDBC:
Connection.setAutoCommit(false);
Connection.commit();
Connection.rollback();
Mybatis:
Mybatis自动开启事务
sqlSession(Connection).commit();
sqlSession(Connection).rollback();
结论:控制事务的底层 都是Connection对象完成的。
3.Spring控制事务的开发
Spring是通过AOP的方式进行事务开发
1. 原始对象
public class XXXUserServiceImpl{
private xxxDAO xxxDAO
set get
1. 原始对象 ---》 原始方法 ---》核心功能 (业务处理+DAO调用)
2. DAO作为Service的成员变量,依赖注入的方式进行赋值
}
2. 额外功能
1. org.springframework.jdbc.datasource.DataSourceTransactionManager
2. 注入DataSource
1. MethodInterceptor
public Object invoke(MethodInvocation invocation){
try{
Connection.setAutoCommit(false);
Object ret = invocation.proceed();
Connection.commit();
}catch(Exception e){
Connection.rollback();
}
return ret;
}
2. @Aspect
@Around
3. 切入点
@Transactional
事务的额外功能加入给那些业务方法。
1. 类上:类中所有的方法都会加入事务
2. 方法上:这个方法会加入事务
4 组装切面
1. 切入点
2. 额外功能
<tx:annotation-driven transaction-manager=""/>
4. Spring控制事务的编码
-
搭建开发环境 (jar)
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.1.14.RELEASE</version> </dependency>
-
编码
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl"> <property name="userDAO" ref="userDAO"/> </bean> <!--DataSourceTransactionManager--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> @Transactional public class UserServiceImpl implements UserService { private UserDAO userDAO; <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
-
细节
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"/> 进行动态代理底层实现的切换 proxy-target-class 默认 false JDK true Cglib
Spring中的事务属性(Transaction Attribute)
1. 什么是事务属性
属性:描述物体特征的一系列值
性别 身高 体重 ...
事务属性:描述事务特征的一系列值
1. 隔离属性
2. 传播属性
3. 只读属性
4. 超时属性
5. 异常属性
2. 如何添加事务属性
@Transactional(isloation=,propagation=,readOnly=,timeout=,rollbackFor=,noRollbackFor=,)
3. 事务属性详解
1. 隔离属性 (ISOLATION)
-
隔离属性的概念
概念:他描述了事务解决并发问题的特征 1. 什么是并发 多个事务(用户)在同一时间,访问操作了相同的数据 同一时间:0.000几秒 微小前 微小后 2. 并发会产生那些问题 1. 脏读 2. 不可重复读 3. 幻影读 3. 并发问题如何解决 通过隔离属性解决,隔离属性中设置不同的值,解决并发处理过程中的问题。
-
事务并发产生的问题
-
脏读
一个事务,读取了另一个事务中没有提交的数据。会在本事务中产生数据不一致的问题 解决方案 @Transactional(isolation=Isolation.READ_COMMITTED)
-
不可重复读
一个事务中,多次读取相同的数据,但是读取结果不一样。会在本事务中产生数据不一致的问题 注意:1 不是脏读 2 一个事务中 解决方案 @Transactional(isolation=Isolation.REPEATABLE_READ) 本质: 一把行锁
-
幻影读
一个事务中,多次对整表进行查询统计,但是结果不一样,会在本事务中产生数据不一致的问题 解决方案 @Transactional(isolation=Isolation.SERIALIZABLE) 本质:表锁
-
总结
并发安全: SERIALIZABLE>REPEATABLE_READ>READ_COMMITTED 运行效率: READ_COMMITTED>REPEATABLE_READ>SERIALIZABLE
-
-
数据库对于隔离属性的支持
隔离属性的值 MySQL Oracle ISOLATION_READ_COMMITTED ✅ ✅ IOSLATION_REPEATABLE_READ ✅ ❎ ISOLATION_SERIALIZABLE ✅ ✅ Oracle不支持REPEATABLE_READ值 如何解决不可重复读 采用的是多版本比对的方式 解决不可重复读的问题
-
默认隔离属性
ISOLATION_DEFAULT:会调用不同数据库所设置的默认隔离属性 MySQL : REPEATABLE_READ Oracle: READ_COMMITTED
-
查看数据库默认隔离属性
-
MySQL
select @@tx_isolation;
-
Oracle
SELECT s.sid, s.serial#, CASE BITAND(t.flag, POWER(2, 28)) WHEN 0 THEN 'READ COMMITTED' ELSE 'SERIALIZABLE' END AS isolation_level FROM v$transaction t JOIN v$session s ON t.addr = s.taddr AND s.sid = sys_context('USERENV', 'SID');
-
-
-
隔离属性在实战中的建议
推荐使用Spring指定的ISOLATION_DEFAULT 1. MySQL repeatable_read 2. Oracle read_commited 未来实战中,并发访问情况 很低 如果真遇到并发问题,乐观锁 Hibernate(JPA) Version MyBatis 通过拦截器自定义开发
2. 传播属性(PROPAGATION)
-
传播属性的概念
概念:他描述了事务解决嵌套问题的特征 什么叫做事务的嵌套:他指的是一个大的事务中,包含了若干个小的事务 问题:大事务中融入了很多小的事务,他们彼此影响,最终就会导致外部大的事务,丧失了事务的原子性
-
传播属性的值及其用法
传播属性的值 外部不存在事务 外部存在事务 用法 备注 REQUIRED 开启新的事务 融合到外部事务中 @Transactional(propagation = Propagation.REQUIRED) 增删改方法 SUPPORTS 不开启事务 融合到外部事务中 @Transactional(propagation = Propagation.SUPPORTS) 查询方法 REQUIRES_NEW 开启新的事务 挂起外部事务,创建新的事务 @Transactional(propagation = Propagation.REQUIRES_NEW) 日志记录方法中 NOT_SUPPORTED 不开启事务 挂起外部事务 @Transactional(propagation = Propagation.NOT_SUPPORTED) 及其不常用 NEVER 不开启事务 抛出异常 @Transactional(propagation = Propagation.NEVER) 及其不常用 MANDATORY 抛出异常 融合到外部事务中 @Transactional(propagation = Propagation.MANDATORY) 及其不常用 -
默认的传播属性
REQUIRED是传播属性的默认值
-
推荐传播属性的使用方式
增删改 方法:直接使用默认值REQUIRED 查询 操作:显示指定传播属性的值为SUPPORTS
3. 只读属性(readOnly)
针对于只进行查询操作的业务方法,可以加入只读属性,提供运行效率
默认值:false
4. 超时属性(timeout)
指定了事务等待的最长时间
1. 为什么事务进行等待?
当前事务访问数据时,有可能访问的数据被别的事务进行加锁的处理,那么此时本事务就必须进行等待。
2. 等待时间 秒
3. 如何应用 @Transactional(timeout=2)
4. 超时属性的默认值 -1
最终由对应的数据库来指定
5. 异常属性
Spring事务处理过程中
默认 对于RuntimeException及其子类 采用的是回滚的策略
默认 对于Exception及其子类 采用的是提交的策略
rollbackFor = {java.lang.Exception,xxx,xxx}
noRollbackFor = {java.lang.RuntimeException,xxx,xx}
@Transactional(rollbackFor = {java.lang.Exception.class},noRollbackFor = {java.lang.RuntimeException.class})
建议:实战中使用RuntimeExceptin及其子类 使用事务异常属性的默认值
4. 事务属性常见配置总结
1. 隔离属性 默认值
2. 传播属性 Required(默认值) 增删改 Supports 查询操作
3. 只读属性 readOnly false 增删改 true 查询操作
4. 超时属性 默认值 -1
5. 异常属性 默认值
增删改操作 @Transactional
查询操作 @Transactional(propagation=Propagation.SUPPORTS,readOnly=true)
5. 基于标签的事务配置方式(事务开发的第二种形式)
基于注解 @Transaction的事务配置回顾
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
<property name="userDAO" ref="userDAO"/>
</bean>
<!--DataSourceTransactionManager-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
@Transactional(isolation=,propagation=,...)
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
基于标签的事务配置
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
<property name="userDAO" ref="userDAO"/>
</bean>
<!--DataSourceTransactionManager-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
事务属性
<tx:advice id="txAdvice" transacation-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="register" isoloation="",propagation=""></tx:method>
<tx:method name="login" .....></tx:method>
等效于
@Transactional(isolation=,propagation=,)
public void register(){
}
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.service.UserServiceImpl.register(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
</aop:config>
-
基于标签的事务配置在实战中的应用方式
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl"> <property name="userDAO" ref="userDAO"/> </bean> <!--DataSourceTransactionManager--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> 编程时候 service中负责进行增删改操作的方法 都以modify开头 查询操作 命名无所谓 <tx:advice id="txAdvice" transacation-manager="dataSourceTransactionManager"> <tx:attributes> <tx:method name="register"></tx:method> <tx:method name="modify*"></tx:method> <tx:method name="*" propagation="SUPPORTS" read-only="true"></tx:method> </tx:attributes> </tx:advice> 应用的过程中,service放置到service包中 <aop:config> <aop:pointcut id="pc" expression="execution(* com.baizhiedu.service..*.*(..))"></aop:pointcut> <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor> </aop:config>
9 Spring基本注解编程
环境配置:在进行注解的使用之前我们需要在配置文件中加入
<context:component-scan base-package="#包的全限命名"/>
开发目录
1.创建对象相关@Component
基本功能
用于替代配置文件中的
例如:我们原来创建对象的方式是这个样子
<bean id="#ID" class="#类名的全限命名"/>
现在只需要在需要创建的类上面加入@Component
@Component
public class User {
xxxx
}
(重要)我们就直接可以在工厂使用类名的首字母小写作为ID创建出来对象
指定标签
我们可在括号中给它标注需要的ID例如:
@Component("user1")
我们就把它ID改为了user1
覆盖标签
我们觉得不满意,可以在配置文件中进行覆盖处理,但是我们需要让id和注解的id保持一致,否则,Spring会创建同名不同类的对象,也就起不到覆盖的作用
例如:我改了上面对应注解的类,并改了创建这个类时的一些属性
<bean id="user" class="#类名的全限命名" scope="prototype"/>
其他相同功能的注解
和@Component功能一致,但是是为了专门标注执行不同业务的类
- @Controller 标注Controller
- @Repository 标注 DAO
- @Repository 标注 Service
但是在和Mybaits 整合的时候我们不用@Repository 以及@Repository
2.@Scope
传统开发中我们指定何时创建对像,在标签中指定scope
在注解开发中使用@Scope("prototype")或者@Scope("singleton")都是一样的
例如:对象将在使用时创建
@Scope("prototype")
public class User {
xxxx
}
3.@Lazy,@PostConstruct,@PreDestroy
推后(延迟)创建对象,在工厂创建时如果没有指定延迟创建,那么此对象的无参构造方法和初始化方法都会执行
@Lazy指定这个类是否延迟加载,在使用时加载
@PostConstruct 指定这个方法为初始化方法
@PreDestroy指定这个方法为销毁方法
例如:
@Lazy(true)
public class UserServiceImpl implements UserService {
//无参构造方法
public UserServiceImpl() {
System.out.println("UserServiceImpl.UserServiceImpl");
}
//初始化方法
@PostConstruct
public void MyinitMehtond(){
System.out.println("UserServiceImpl.MyinitMehtond");
}
//销毁方法
@PreDestroy
public void MyDestory(){
System.out.println("UserServiceImpl.MyDestory");
}
}
使用了@Lazy(true)那么在创建工厂时它被稍后使用时加载
例如创建工厂代码
public void test2(){
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("ApplicationContext-Annotation.xml");
//UserService service= (UserService) context.getBean("userServiceImpl");
}
运行它发现没任何反应
我们使用它取消使用它的注释,可以看到这个方法被加载了包括初始化方法
那么@PreDestroy是在工厂关闭时启动销毁方法,销毁工厂context.close();
可以看到销毁方法被调用
4.注入的相关注解@Autowired,@PropertySource,@Value
自定义类型
@Autowired将为放置位置的变量进行注入
例如:我们有了一个DAO的对象,要使用它的save方法,我们得首先创建他
@Repository
public class UserDAOImpl implements UserDAO {
@Override
public void save() {
System.out.println("UserDAOImpl.save");
}
}
在Service层中调用它就得把他创建的对象赋值给对应方法
@Service
public class UserServiceImpl implements UserService {
//声明的DAO变量
//方式一
private UserDAO userDAO;
public UserDAO getUserDAO() {
System.out.println("UserServiceImpl.getUserDAO");
return userDAO;
}
//提供SET和GET方法
//方式二
//@Autowired
public void setUserDAO(UserDAO userDAO) {
System.out.println("UserServiceImpl.setUserDAO");
this.userDAO = userDAO;
}
//调用DAO方法
@Override
public void register() {
System.out.println("UserServiceImpl.register");
userDAO.save();
}
@Override
public void login(String name, String password) {
System.out.println("UserServiceImpl.login");
}
方式一:在成员变量上注解
Spring会自动的通过反射为对应的变量进行注入[推荐方式]
方式二:在set方法上面进行注解
Spring会自动的寻找与之对应的成员变量(也就是上面声明的DAO变量)的相同类型或者子类
JDK类型
这个方法比较麻烦、
开发步骤:
- 在properties文件中设置键值对
- 在工厂或者注解读取配置文件
- 写注解
-
在properties文件中设置键值对
例如我要为实体类的两个变量注入两个值
public class User implements Serializable { private String name; private String password; //为其提供SET/GET方法这里就不写出来了,节约篇幅 }
在配置文件DataBase.properties中如下书写
JDBC_User=root JDBC_Password=atgxstuSf2e
-
在工厂或者注解读取配置文件
在类上使用@PropertySource注解表明配置文件
@PropertySource("classpath:/DataBase.properties") public class User implements Serializable { private String name; private String password; }
-
写注解
格式为@Value("${键值}")
@PropertySource("classpath:/DataBase.properties") public class User implements Serializable { @Value("${JDBC_User}") private String name; @Value("${JDBC_Password}") private String password; }
注意点:
这个注入方法不能用在静态成员变量以及不可以注入集合等复杂类型,使用还是比较繁琐
5.注解扫描
在上面的环境配置配置了Spring需要扫描的包的注解,那么用起来必须有局限性,Spring还提供了自定义的对注解扫描的方式
总体来说,Spring为我们提供了两种自定义注解扫描方式
- 包含形式
- 排除形式
包含形式和排除形式都大同小异
大同在有相同的扫描方法和对扫描方法的配置
- assignable 基于特定类型的方式
- aspectj 基于切入点表达式的方式
- annotation 基于注解的方式
- custom 用于Spring框架底层开发(没到那个水平)
- regex 基于正则表达式的方式(和切入点表达式比起来肯定切入点表达式好用)
1.排除形式
配置形式
<context:component-scan base-package="#某个包">
<context:exclude-filter type="#扫描方法" expression="#扫描方法的配置"/>
</context:component-scan>
一,assignable
我把org.AnnotationProgramming.Entity.User这个类排除了,不作为扫描的类,所以也就不会被Spring创建出来(事先加入了@Component注解)
<context:exclude-filter type="assignable" expression="org.AnnotationProgramming.Entity.User"/>
测试代码:
getBeanDefinitionNames是Spring创建的对象名字
public void test3(){
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("ApplicationContext-Annotation.xml");
String[]strings=context.getBeanDefinitionNames();
for (String string : strings) {
System.out.println("string = " + string);
}
}
运行结果:
可以看到User类并没有被创建出来说明被排除了
二,aspectj
排除了org.AnnotationProgramming.DAO这个包下的所有类
具体演示就不弄了
<context:exclude-filter type="aspectj" expression="org.AnnotationProgramming.DAO..*"/>
三,annotation
排除了Repository这个注解,需要填写注解的全限命名
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
四,custom和regex使用频率极低就不介绍
2.包含属性
所有的内容和排除一致,只不过扫描的类从排除变为扫描了
3.混合使用
包含和排除属性可以任意搭配,不过排除之后不可以使用包含,注意选择逻辑,自由搭配
6.对注解开发的其他注意事项
1.注解配置的类和配置文件是互通的,也就是说注解中设置的ID在配置文件中可以使用
2.使用注解的时机:多在自己开发程序时候,也就是自己写的类,但是在使用其他人提供的类,比如SqlsessionFactoryBean还是得在配置文件中进行配置,所以开发是混合开发
还有更多高级注解(Spring3以后)在下一篇笔记将会提到,这些都属于基础注解,也就是早期Spring提供的注解
10.Spring 高级注解编程一
1.@Configuration
一,作用
@Configuration用于定义配置类,替换掉xml配置文件
也就是说这个配置类就相当于配置文件
比如我创建一个Appconfig类指定为定义配置类就如下
@Configuration
public class Appconfig {
xxx
}
二,使用
创建对象的工厂也有所不同
之前都是ClassPathXmlApplicationContext等等
现在变为了:AnnotationConfigApplicationContex
使用方法有两种:
-
反射
ApplicationContext context =new AnnotationConfigApplicationContext(Appconfig.class);
-
指定所在的包(Appconfig在AnnotationProgramming下面)
ApplicationContext context= new AnnotationConfigApplicationContext("org/AnnotationProgramming");
2.@Bean
1.创建对象相关
@Bean注解在配置bean中进⾏使⽤,等同于XML配置⽂件中的
也有点类似@Component注解
一,使用@Bean创建简单对象
直接new出返回即可
@Bean
public User user(){
return new User();
}
二,使用@Bean创建复杂对象
-
在@Bean中直接完成
例如我要创建一个connetion对象
@Bean public Connection connection() throws ClassNotFoundException, SQLException { Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test","root","123456"); return connection; }
-
使用FactoryBean
声明一个ConnectionFactory类实现 FactoryBean接口
public class ConnectionFactory implements FactoryBean<Connection> { @Override public Connection getObject() throws Exception { Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test","root","123456"); return connection; } @Override public Class<?> getObjectType() { return Connection.class; } @Override public boolean isSingleton() { return false; } }
在配置Bean中如下实现
简单来说步骤如下:
- 配置FactoryBean
- 在配置Bean中获取FactoryBean
- 从FactoryBean获得对象
- 返回对象
@Bean public Connection connectionFactoryConfig() { ConnectionFactory connectionFactory=new ConnectionFactory(); Connection connection= null; try { connection = connectionFactory.getObject(); } catch (Exception e) { e.printStackTrace(); } return connection; }
三,使用@Scope控制生命周期
和之前的一样@Scope中的两个属性可以控制创建的时机
2.注入相关
1.自定义类型注入
我们得把需要注入的类型给创建出来
比如我要注入UserDAO,那就得先创建出来
@Bean
public UserDAO userDAO(){
UserDAO userDAO=new UserDAOImpl();
return userDAO;
}
方式一:
直接声明为形式参数,前面必须先为其设置SET/GET方法
@Bean
public UserService service(UserDAO userDAO){
UserServiceImpl userService=new UserServiceImpl();
userService.setUserDAO(userDAO);
return userService;
}
方式二:
直接调用方法注入
@Bean
public UserService service1(){
UserServiceImpl userService=new UserServiceImpl();
userService.setUserDAO(userDAO());//userDAO()相当于返回的userDAO
return userService;
}
2.JDK注入
方式一:
直接手动设置,不过会有耦合问题
@Bean
public Customer customer(){
Customer customer= new Customer();
customer.setId("1");
customer.setName("SY");
return customer;
}
方式二:
通过配置文件设置,使用@PropertySource读取配置文件达到解耦
@PropertySource("classpath:/init.properties")
public class Appconfig {
@Value("${id}")
private String id;
@Value("${name}")
private String name;
@Bean
public Customer customer(){
Customer customer= new Customer();
customer.setId(id);
customer.setName(name);
return customer;
}
3.@ComponentScan
@ComponentScan注解在配置bean中进⾏使⽤,等同于XML配置⽂件中的context:component-scan标签
用于扫描相关注解
原理和之前一摸一样
一,基本使用
1.如果要指定扫描的包则如下设置
@Configuration
@ComponentScan(basePackages = "org.AnnotationProgramming.Scan")
public class Appconfig_Scan {
}
2.也可以在工厂创建时指定
Spring源码:
public AnnotationConfigApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}
二,排除使用
1.basePackages 指定包
2.excludeFilters 排除方案
3.@ComponentScan.Filter 指定排除类型 有下面4种
- .ANNOTATION value
- .ASSIGNABLE_TYPE value
- .ASPECTJ pattern
- .REGEX pattern
- .CUSTOM value
4.value和pattern
- value指定注解(反射)
- pattern指定表达式
@Configuration
@ComponentScan(basePackages = "org.AnnotationProgramming.Scan",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Repository.class}),
@ComponentScan.Filter(type = FilterType.ASPECTJ,pattern = "*..san1")})
public class Appconfig_Scan {
}
三,包含使用
和上面一样只是excludeFilters 变为了includeFilters
@Configuration
@ComponentScan(basePackages = "org.AnnotationProgramming.Scan",
includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Repository.class})})
public class Appconfig_Scan {
}
注意:若只写@ComponentScan则默认扫描所有配置类,在创建工厂时也会把所有的配置类的类给创建出来
4.配置的优先级
一,配置的应用场景
到目前位置有多种配置形式均可进行创建对象,那么这些配置形式的应用场合有哪些?
- @Component 用于自行开发,也就是自己开发的类
- @Bean 用于二次开发,比如别人开发的类
- 配置文件的
标签 - @Import 用于对Spring底层开发
二,配置的优先级
在@Component,@Bean,配置文件存在优先级
@Component<@Bean<配置文件
优先级高的可以覆盖优先级低的
例如:
提供一个Customer类
它id和name应该是NULL
@Component
public class Customer {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
接着在配置Bean中如下
@Configuration
@ComponentScan(basePackages = "org.AnnotationProgramming.Service")
@ImportResource("Applicontext_adv.xml")//后续会提到
public class Appconfig_adv {
@Bean
public Customer customer(){
Customer customer=new Customer();
customer.setName("SU");
customer.setId("1");
return customer;
}
}
按照之前说的应该会覆盖掉,那么创建工厂获取customer打印出运行结果为
2021-07-21 16:35:35 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'appconfig_adv'
2021-07-21 16:35:35 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'customer'
Customer{id='1', name='SU'}
再接着再配置文件如下设置
<bean class="org.AnnotationProgramming.Service.Customer" id="customer">
<property name="id" value="2"/>
<property name="name" value="SY"/>
</bean>
按照之前说的应该会覆盖掉,那么创建工厂获取customer打印出运行结果为
2021-07-21 16:37:54 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'appconfig_adv'
2021-07-21 16:37:54 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'customer'
Customer{id='2', name='SY'}
最重要的一点:每个配置形式的ID必须保持一致,比如我把@Component设置为id为CO
运行如下:
它会创建出@Bean对象而不是CO,也就不能进行覆盖
2021-07-21 16:39:59 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'CO'
2021-07-21 16:39:59 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'customer'
Customer{id='2', name='SY'}
三,配置的解耦
在上面配置Bean发现@ImportResource("Applicontext_adv.xml")有耦合,不符合开闭原则
也就是说,若以后要对配置Bean进行修改则要改动源代码
那么有以下解决方案
1.新建一个配置类,
Applicontext_adv.xml包含了需要覆盖的信息
如:
@Configuration
@ImportResource("Applicontext_adv.xml")
public class Appconfig_advNEW {
}
那么在获取工厂时如下:
ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig_adv.class,Appconfig_advNEW.class);
设置多个参数,就可以整合在一起了同时在配置文件中自由设置。
最终解耦还是得用配置文件完成,但是配置Bean开发也有很多好处,都是相辅相成同时开发
5.跨配置整合
一,为什么要整合
-
为什么会有多个配置信息?
拆分多个配置bean的开发,是⼀种模块化开发的形式,也体现了⾯向对象各司其职的设计思想
-
可以整合哪几种?
- @Component相关注解的
- 配置Bean
- XML配置文件
-
关注点
- 如何使多配置的信息 汇总成⼀个整体
- 如何实现跨配置的注⼊
二, 多个配置Bean的整合
一,创建对象
方式一:直接在AnnotationConfigApplicationContext中填入多个配置Bean
如:
Appconfig
@Configuration
public class Appconfig {
@Bean
public User user1(){
User user=new User();
return user;
}
}
Appconfig1
@Configuration
public class Appconfig1 {
@Bean
public User user2(){
User user=new User();
return user;
}
}
工厂使用
ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class,Appconfig1.class);
方式二:直接在AnnotationConfigApplicationContext中填入配置Bean所在的包
如:
ApplicationContext context = new AnnotationConfigApplicationContext("org.AnnotationProgramming.Configs");
方式三:使用@Import
如:在Appconfig3上面使用@Import
@Configuration
@Import(Appconfig1.class)
public class Appconfig3 {
@Bean
public User user3(){
User user=new User();
return user;
}
}
那么在使用的时候会获取Appconfig1的内容
工厂中只需要这么使用
ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig3.class);
一二测试代码:
//测试多个配置文件整合
@Test
public void test() {
ApplicationContext context = new AnnotationConfigApplicationContext("org.AnnotationProgramming.Configs");
User user1= (User) context.getBean("user1");
User user2= (User) context.getBean("user2");
System.out.println("user1 = " + user1);
System.out.println("user2 = " + user2);
方式一二的结果都是一样的
都创建了user1和user2
2021-07-22 00:35:09 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'user1'
2021-07-22 00:35:09 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'user2'
方式三的测试代码
//测试@Improt
@Test
public void test11() {
ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig3.class);
User user3= (User) context.getBean("user3");
User user2= (User) context.getBean("user2");
System.out.println("user3 = " + user3);
System.out.println("user2 = " + user2);
可以看见创建了user2,user3
2021-07-22 00:36:45 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'user2'
2021-07-22 00:36:45 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'user3'
二,注入
方式一:不跨配置的注入
直接使用@Autowired在需要的变量上面注解就好
如:
Appconfig4
@Configuration
//不跨配置用
@Import(Appconfig5.class)
//跨配置时用
//@ImportResource("Applicontext_adv.xml")
public class Appconfig4 {
@Autowired
private UserDAO userDAO;
@Bean
public UserService userService(){
UserServiceImpl userService=new UserServiceImpl();
userService.setUserDAO(userDAO);
return userService;
}
}
Appconfig5
@Configuration
public class Appconfig5 {
@Bean
UserDAO userDAO(){
UserDAO userDAO=new UserDAOImpl();
return userDAO;
}
}
方式二:跨配置的注入
注释掉@Import 使用@ImportResource指定配置文件中创建的UserDAO对象
<bean id="userDAO" class="org.AnnotationProgramming.DAO.UserDAOImpl"/>
测试代码:
//测试注入
@Test
public void test12() {
ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig4.class);
UserServiceImpl service = (UserServiceImpl) context.getBean("userService");
service.register();
}
运行结果:
都是一样的,可以看到UserDAOImpl.save被打印说明被执行,也就是注入成功
2021-07-22 00:45:28 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'appconfig4'
2021-07-22 00:45:28 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'userDAO'
2021-07-22 00:45:28 DEBUG DefaultListableBeanFactory:225 - Creating shared instance of singleton bean 'userService'
UserDAOImpl.save
6.4种配置文件的使用总结
一,@PropertySource()
如下:在之前有过,所以不过多介绍
@Configuration
//功能注解用
//@PropertySource("classpath:/init.properties")
//配置文件用context:property-placeholder
//@ImportResource("Applicontext_adv.xml")
public class Appconfig1 {
@Value("${id}")
private String id;
@Value("${name}")
private String name;
@Bean
public Customer customer(){
Customer customer= new Customer();
customer.setId(id);
customer.setName(name);
return customer;
}
二,context:property-placeholder
这是在Spring配置文件中使用
如下设置;
<context:property-placeholder location="classpath:init.properties"/>
代码层面把上面的注释取消即可
当然了使用ClassPathXmlApplicationContext工厂也是可以的,不过需要先创建好Customer
配置互通
注意:
记得加上DTD约束
xmlns:context="http://www.springframework.org/schema/context"
#在xsi:schemaLocation加入
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
三,PropertySourcesPlaceholderConfigurer类在配置文件
如下:其他和第二条一样
<bean id="holder" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="location" value="init.properties"/>
四,PropertySourcesPlaceholderConfigurer类在@Bean
如下:
需要指出的是必须是整合其他配置类,要不然读取不到配置文件内容
可能和配置类的CGlib代理有关,暂时不研究
@Configuration
@Import(Appconfig1.class)
public class holder {
@Bean
public PropertySourcesPlaceholderConfigurer configurer(){
PropertySourcesPlaceholderConfigurer configurer=new PropertySourcesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("init.properties"));
return configurer;
}
}
测试代码:
@Test
//测试4种proerties的注入
public void test13() {
ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig1.class);
// ApplicationContext context=new ClassPathXmlApplicationContext("/Applicontext_adv.xml");
Customer customer= (Customer) context.getBean("customer");
System.out.println(customer.toString());
}
结果都是
Customer{id='123', name='sy'}
11 Spring 高级注解编程二
一,使用注解开发AOP
1.AOP开发的基本要素
根据之前的总结,动态代理开发有以下要素
- 原始对象
- 额外功能
- 切入点
那么在注解开发中,其中额外功能和切入点整合为了一个类
对注解开发AOP的总体描述:提供原始对象和切面类之后由配置Bean整合使用
2.开发步骤
1.提供原始对象
需要提供@Component及其衍生注解来创建出这个类提供给切面类
@Service
public class UserServiceImpl implements UserService {
@Override
public void register() {
System.out.println("UserServiceImpl.register");
}
@Override
public void login(String name, String password) {
System.out.println("UserServiceImpl.login");
}
}
2.切面类开发
要提供@Aspect注解指明这是切面类
同样需要提供@Component创建代理类
使用 @Around提供切入点表达式
其余实现和之前的一致
@org.aspectj.lang.annotation.Aspect
@Component
public class Aspect {
@Around("execution(* *(..))")
public Object Arround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("Aspect.Arround");
Object ret=proceedingJoinPoint.proceed();
return ret;
}
}
3.配置Bean的开发
必须指明了切面类和原始对象所在的地方然后才能被配置Bean创建出来
@EnableAspectJAutoProxy指明自动扫描代理,等同于配置文件的<aop:aspectj-autoproxy />
@Configuration
@ComponentScan(basePackages = "org.AnnotationProgramming.AOP")
@EnableAspectJAutoProxy
public class Appconfig_Aop {
}
4.运行
获取配置Bean的信息即可
@Test
//测试注解开发AOP
public void test14() {
ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig_Aop.class);
UserService userService= (UserService) context.getBean("userServiceImpl");
userService.register();
userService.login("SY","123");
}
效果:
Aspect.Arround
UserServiceImpl.register
Aspect.Arround
UserServiceImpl.login
注意:不需要再次在配置Bean中编写创建原始对象的方法了
二,使用注解整合Mybatis
和之前的整合流程一致,只不过是变换成了注解
整合代码如下:
使用配置文件解决了耦合
@Configuration
@ComponentScan(basePackages = "")
@MapperScan(basePackages = "")
public class MyBatisAutoConfiguration {
@Autowired
private MybatisProperties mybatisProperties;
//连接池
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(mybatisProperties.getDriverClassName());
dataSource.setUrl(mybatisProperties.getUrl());
dataSource.setUsername(mybatisProperties.getUsername());
dataSource.setPassword(mybatisProperties.getPassword());
return dataSource;
}
//sqlsession的创建
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
//一组Mapper文件
sqlSessionFactoryBean.setTypeAliasesPackage(mybatisProperties.getTypeAliasesPackages());
//一个Mapper文件
//sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("UserDAOMapper.xml"));
try {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(mybatisProperties.getMapperLocations());
sqlSessionFactoryBean.setMapperLocations(resources);
} catch (IOException e) {
e.printStackTrace();
}
return sqlSessionFactoryBean;
}
}
注意点:MapperLocations编码时通配的写法
依然可以使用/*Mapper.xml表示一个文件夹下面的所有Mapper文件
mybatisProperties代码如下(省略了SET/GET方法):
@Component
@PropertySource("classpath:mybatis.properties")
public class MybatisProperties {
@Value("${mybatis.driverClassName}")
private String driverClassName;
@Value("${mybatis.url}")
private String url;
@Value("${mybatis.username}")
private String username;
@Value("${mybatis.password}")
private String password;
@Value("${mybatis.typeAliasesPackages}")
private String typeAliasesPackages;
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
}
三,使用注解开发事务
事务开发时使用aop开发的所以需要:
-
原始对象
并标注上@Transactional注解
事务属性就在这里指定
@Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserDAO userDAO; public UserDAO getUserDAO() { return userDAO; } public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } @Override public void register(User user) { userDAO.save(user); } }
-
事务额外功能
需要加上@EnableTransactionManagement
@Configuration
@EnableTransactionManagement
public class TransactionAutoConfiguration {
@Autowired
private DataSource dataSource;
//此处只是作为演示不必看内容
@Bean
public DataSourceTransactionManager dataSourceTransactionManager() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
四,Spring5中YML的使用
1.YML是什么
YAML不是一种标记语言,通常以.yml,.yaml为后缀的文件,是一种直观的能够被电脑识别的数据序列化格式,并且容易被人类阅读,容易和脚本语言交互的,可以被支持YAML库的不同的编程语言程序导入,一种专门用来写配置文件的语言。
2.YML语法
- 大小写敏感
- 松散表示,java中对于驼峰命名法,可用原名或使用-代替驼峰,如java中的lastName属性,在yml中使用lastName或 last-name都可正确映射。
1.基本语法
k: v 表示键值对关系,冒号之后有一个空格
例如:
id: 32
name: SY
password: 123456
2.对象语法,或者是Map语法
使用空格的缩进表示层级关系,只要是左对齐的一列数据,都是同一个对象的
缩进时不允许使用Tab键,只允许使用空格
例如:
Info:
id: 32
name: SY
password: 123456
表示为一行为(元素依然带有空格):
Info: {id: 32,name: SY,password: 123456}
3.数组,集合,list,set
用- 值表示数组中的一个元素
例如:
Info:
-32
-SY
-123456
表示为一行为
Info:[32,SY,123456]
4.数组对象,list对象,set对象
Info:
- id: 32
name: SY
password: 123456
- id: 0
name: SYS
password: 123
- {id: 32,name: SY,password: 123456}
3.Java中使用
1.处理集合问题
YML内容如下:
Arrays: 'A,B,C'
java处理如下:
使用#而不是处理properties的$,使用split分割
@Value("#{'${arrays}'.split(',')}")
private String[] arrays;
2.处理普通的注入
YML内容如下:
id: 1
java处理如下:
@Value("#{'${id}')
private Integerid;
4.Spring和YML的整合
1.配置MAVEN
版本需要在1.18以上
<!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.29</version>
</dependency>
2.配置读取YML的工具
由于Spring无法直接读取YML所以需要把YML变为properties形式
@Bean
public PropertySourcesPlaceholderConfigurer configurer(){
//配置yamlPropertiesFactoryBean读取YML文件
YamlPropertiesFactoryBean yamlPropertiesFactoryBean=new YamlPropertiesFactoryBean();
yamlPropertiesFactoryBean.setResources(new ClassPathResource("init.yml"));
//使得Properties对象拿到转变的YML文件
Properties properties =yamlPropertiesFactoryBean.getObject();
//读取Properties文件
PropertySourcesPlaceholderConfigurer configurer =new PropertySourcesPlaceholderConfigurer();
configurer.setProperties( properties);
return configurer;
}
3.使用:
1.基本使用
和之前使用Properties一样,同样的方法注入(省略了SET和GET方法)
@Service
public class Info
{
@Value("${id}")
private String id;
@Value("${name}")
private String name;
}
使用的YML内容为:
id: 32
name: SY
2.配置形式为对象
使用的YML内容为:
Info:
id: 32
name: SY
注入值得时候得跟着改变,加上了对象
@Service
public class Info
{
@Value("${Info.id}")
private String id;
@Value("${Info.name}")
private String name;
}
3.配置形式为集合
使用的YML内容为:
Info:
id: 32
name: SY
lists: A,B,C
由于Properties不可以解析集合类型得数据,所以就使用EL表达式
@Service
public class Info
{
@Value("${Info.id}")
private String id;
@Value("${Info.name}")
private String name;
@Value("#{'${lists}'.split(',')}")
private List<String> list;
}
12.对一些问题的研究
此处作为了解篇和自己的一些探究
一,初识CGlib动态代理技术
之前在JDK代理技术提到代理设计模式的三要素:
- 有原始类
- 额外的方法
- 和原始类实现相同的方法
对于CGlib也是一样的
1.和JDK代理模式对比
JDK代理模式
例如:
他们都实现了相同的接口,看代理类和原始类的区别,区别在被代理的对象的方法中添加了额外功能。
同时从这个图可以看出代理类和原始类是兄弟关系。
JDK代理是基于接口实现的,也就是说实现JDK必须要实现接口
而CGlib是基于继承关系,例如
如果一个类没有实现接口,那么如何实现动态代理呢?
CGlib提供了解决方案
可以看出CGlib是基于父子关系,至此,也就解答了之前为啥要使用接口接受代理对象的原因。
2.CGlib动态代理技术的实现
由于是基于继承关系,所以不用在代理实现中实现接口,只需要实现原始类即可
原始类
public class UserServiceImplNew implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImplNew.register");
}
@Override
public void login(String name, String password) {
System.out.println("UserServiceImplNew.login");
}
}
1.在代理实现中实现原始类
直接new即可
final UserService userService=new UserServiceImplNew();
2.声明增类,以及对对应的方法设置
Enhancer enhancer=new Enhancer();
//和JDK一样,依然需要提供类加载器
enhancer.setClassLoader(userService.getClass().getClassLoader());
//指定类为父类
enhancer.setSuperclass(userService.getClass());
3.实现MethodInterceptor接口
基本等效于InvocationHandler
MethodInterceptor interceptor =new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGlibModle.intercept");
Object ret=method.invoke(userService,objects);
return ret;
}
};
其中的intercept函数和JDK所需要内容是高度一致的
- Object o 返回的对象
- Method method 传入的方法
- Object[] objects 参数列表
- MethodProxy methodProxy 代理的方法
根据代理设计模式的三要素中的和原始类实现相同的方法,使用invoke函数传入实现类和参数列表
注意:有的CGlib版本intercept的第二个参数是args这里是objects,其实都一样
4.把代理类创建出来和使用
enhancer.setCallback(interceptor);
UserService userServiceProxy= (UserService) enhancer.create();
userServiceProxy.register(new User());
userServiceProxy.login("SY","123456");
运行结果:
完整代码:
package org.PoxyModle;
import org.User;
import org.UserService;
import org.UserServiceImplNew;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGlibModle {
public static void main(final String[] args) {
final UserService userService=new UserServiceImplNew();
Enhancer enhancer=new Enhancer();
enhancer.setClassLoader(userService.getClass().getClassLoader());
enhancer.setSuperclass(userService.getClass());
MethodInterceptor interceptor =new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGlibModle.intercept");
Object ret=method.invoke(userService,objects);
return ret;
}
};
enhancer.setCallback(interceptor);
UserService userServiceProxy= (UserService) enhancer.create();
userServiceProxy.register(new User());
userServiceProxy.login("SY","123456");
}
}
大概就是这个流程
二,初识Java动态代理—JDK代理
代理设计模式的三要素:
- 有原始类
- 额外的方法
- 和原始类实现相同的方法
那么就针对上面三个要求分析和实现动态代理
1.newProxyInstance
newProxyInstance是JDK为我们提供的类,用于创建动态代理对象,参数为
Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
- ClassLoader loader 是类加载器
- Class<?>[] interfaces 是原始实现的接口
- InvocationHandler h 额外方法的实现
那从以上入手
1.获得原始类
原始类很容易获得,通过最简单的new即可例如:
UserService userService =new UserServiceImplNew();
2.和原始类实现相同的接口 interfaces
在上面我们创建出来了userService对象,那么就可以使用class提供的方法获得userService的接口,例如:
userService.getClass().getInterfaces()
3.和原始类实现相同的方法 InvocationHandler
这个将在下面的InvocationHandler中分析
4.类加载器 ClassLoader
- 普通的创建出对象的流程是:
从Java文件到字节码文件,字节码通过ClassLoader(CL)加载到JVM中,从而我们可以把对象创建出来
- 而对于动态代理的方法有所不同
代理对象创建我们并没有new它,或者创建它,那么JVM如何获得代理对象的呢?
JDK提供的动态代理方法有创建字节码的的技术,从interfaces 和InvocationHandler传入的接口信息和方法信息就可以实现出一个代理类
然后用JDK提供的动态代理方法创建出动态字节码
最后使用一个CL加载到JVM中
那么如何实现它呢?
在创建userService时JVM为它的字节码加载设置了一个CL,所以我们可以使用这个CL,也可以称作借用
userService.class.getClassLoader()
2.InvocationHandler
首先实现InvocationHandler接口(ideal自动生成)
该接口实现了一个内部类,我们关注的是invoke方法
InvocationHandler handler=new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
};
对于Object invoke(Object proxy, Method method, Object[] args)
- Object proxy 是代理的对象
- Method method 是被代理的对象
- Object[] args 是被代理的对象的参数
前面提到和原始类实现相同的方法,那么怎么实现?
method.invoke(userService,args);
在Object invoke(Object obj, Object... args) 传入想被代理的对象和args即可,它就代表的原始对象,将被执行
现在添加额外功能
详细实现如下
InvocationHandler handler=new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDKModle.invoke");
Object ret= method.invoke(userService,args);
return ret;
}
};
3.使用代理对象
UserService userServiceProxy= (UserService) Proxy.newProxyInstance(JDKModle.class.getClassLoader(),userService.getClass().getInterfaces(),handler);
和实现接口一样,只需要代从newProxyInstance得到对象即可
现在把所有代码组装起来
对于下面三个类具体实现,不用关心具体内容,只需要认识方法名字就好
org.User;
org.UserService;
org.UserServiceImplNew;
User
public class User {
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
UserService
public interface UserService {
public void register(User user);
public void login(String name, String password);
}
UserServiceImplNew
public class UserServiceImplNew implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImplNew.register");
}
@Override
public void login(String name, String password) {
System.out.println("UserServiceImplNew.login");
}
}
package org.PoxyModle;
import org.User;
import org.UserService;
import org.UserServiceImplNew;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKModle {
public static void main(String[] args) {
final UserService userService =new UserServiceImplNew();
InvocationHandler handler=new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDKModle.invoke");
Object ret= method.invoke(userService,args);
return ret;
}
};
UserService userServiceProxy= (UserService) Proxy.newProxyInstance(JDKModle.class.getClassLoader(),userService.getClass().getInterfaces(),handler);
userServiceProxy.login("SY","1123");
userServiceProxy.register(new User());
}
}
运行如下
3.总结:
JDK动态代理和Spring的动态代理就是一个意思,只不过对JDK封装了,使得更容易使用,了解JDK动态代理对理解AOP编程有好处
newProxyInstance InvocationHandler相互配合才能完成代理操作
初步分析和实现到此为止
三,对spring创建对象时为何要使用接口,而使用接口的实现类会报错
接上一篇问题的解答:
[https://www.cnblogs.com/ShanYu-Home/p/14806203.html](Spring AOP获取不了增强类(额外方法)和无法通过getBean()获取对象)
此问题发生在动态代理时,比如对实现类做增强
对于动态代理分为:JDK动态代理和CGlib动态代理,出现此问题多是这两个代理方式的差异性导致的
Spring默认的类型时JDK动态代理
对实现类对象做增强得到的增强类与实现类是兄弟关系,所以不能用实现类接收增强类对象,只能用接口接收
如果直接使用自然会报错
UserServiceImpl userService= (UserServiceImpl) context.getBean("UserServicePiont");
但是如果让Spring强制使用CGlib代理
在配置文件aop:config添加proxy-target-class="true则不会报错
<aop:config proxy-target-class="true" >
CGlib代理类和实现类之间是父子关系,自然可以用父类(实现类)去接收子类对象(代理类对象即增强类对象)。
不过应该不会需要这么做,使用接口本来就是解耦的,你直接用实现类接收注入对象岂不是失去了注入的意义。(为什么不直接new一个呢?)
关于动态代理的JDK动态代理和CGlib动态代理还需继续学习
参考:
四,使用注解对Spring动态代理进行开发的补充
进行AOP编程有一下要素:
- 原始类
- 增强方法
- 切入点
- 组装它们
1.准备原始类
UserService接口
public interface UserService {
public void register(User user);
public Boolean login(String name, String password);
}
UserServiceImpl实现类
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("UserServiceImpl.register");
}
@Override
public Boolean login(String name, String password) {
System.out.println("UserServiceImpl.login "+name+" "+password );
return true;
}
}
2.增强方法
将使用到@Aspect,@Around两个注解
1.先搭架子
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
//@Aspect 指定这个类切面为切面实现
@Aspect
public class MyAspect {
//@Around() 指定为这个方法为增强方法,在括号里添加切入点表达式
@Around()
public Object ServiceAspect(ProceedingJoinPoint joinPoint) throws Throwable {
Object ret=joinPoint.proceed();
return ret;
}
}
可以和之前的JDK方式对比
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret= method.invoke(userService,args);
return ret;
}
也可以和MethodInterceptor方式对比
public Object invoke(MethodInvocation invocation) throws Throwable {
Object ret=invocation.proceed();
return ret;
}
就那么回事儿,所以不过多解释和研究其中的内容
2.填入需要添加的方法和切入点表达式
完整代码如下
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class MyAspect {
@Around("execution(* *(..))")
public Object ServiceAspect(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("MyAspect.ServiceAspect");
Object ret=joinPoint.proceed();
return ret;
}
}
3.在Spring配置文件中声明对应的原始类和增强类
<bean id="AspectService" class="org.Aspect.UserServiceImpl"/>
<bean id="AspectCP" class="org.Aspect.MyAspect"/>
然后把它组装起来
<aop:aspectj-autoproxy/>
4.测试
public class TestAspect {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("/ApplicationContext.XML");
UserService userService= (UserService) context.getBean("AspectService");
userService.login("SY","1223456");
userService.register(new User());
}
可以看到都被代理了
3.如果有多个增强方法且不同的切入点表达式那么怎么统一管理?
需要使用@Pointcut注解
在增强方法之前声明@Pointcut
添加一个方法不需要实现如何东西,在@Pointcut的括号中添加表达式
@Pointcut()
public void Expres(){
}
例如:
分别为所有register方法和任意方法
@Pointcut("execution(* register(..))")
public void Expres1(){
}
@Pointcut("execution(* *(..))")
public void Expres2(){
}
增强方法的实现只需要在@Around()中填写上注解即可
完整代码如下
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class AspectAnn {
@Pointcut("execution(* register(..))")
public void Expres1(){
}
@Pointcut("execution(* *(..))")
public void Expres2(){
}
@Around("Expres1()")
public Object CPannotation1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("AspectAnn.CPannotation1");
Object ret= proceedingJoinPoint.proceed();
return ret;
}
@Around("Expres2()")
public Object CPannotation2(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("AspectAnn.CPannotation2");
Object ret= proceedingJoinPoint.proceed();
return ret;
}
}
在配置文件中声明下
<bean id="AnnAspect" class="org.Aspect.AspectAnn"/>
执行测试
五,Spring AOP开发时如何得到某个方法内调用的方法的代理对象?
问题阅读起来拗口,看代码
在方法中调用其他方法很常见,也经常使用,如果在一个方法内部调用其他方法,比如
public class UserServiceImpl implements UserService{
@Override
public void register(User user) {
System.out.println("rg.Service.UserServiceImpl.register");
this.login("SY","45452");
}
@Override
public Boolean login(String name, String password) {
System.out.println("rg.Service.UserServiceImpl.login "+name+" "+password );
return true;
}
}
我在这里调用了register中调用了login方法,那么我能获得login()这个被代理过的方法吗?
这是执行的代理方法
对所有login都执行代理
@Aspect
public class MyAspect {
@Around("execution(* login(..))")
public Object ServiceAspect(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("MyAspect.ServiceAspect");
Object ret=joinPoint.proceed();
return ret;
}
}
Spring配置文件,声明下类
<bean id="ExMethod" class="org.ExMerhod.UserServiceImpl"/>
如果得到了login()这个被代理过的方法会打印出MyAspect.ServiceAspect
执行下测试
public class TestExMethod {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("/ApplicationContext2.XML");
UserService userService= (UserService) context.getBean("ExMethod");
userService.login("SY","1223456");
userService.register(new User());
}
}
结果
我们只看到了rg.Service.UserServiceImpl.login的方法被代理,并没有看到我们在register中的方法被代理
这就时是问题所在
从测试中的工厂得到的ExMethod的是被代理过的方法,而在代理过的方法内的方法是被调用的那个类的方法,而不是代理过的方法,所以不会被执行增强方法。
所以我们需要让这个方法得到代理的对象即可
由于Spring工厂是一个超级重量级资源,所以我们使用ApplicationContextAware获得已经创建的工厂
具体代码如下
package org.ExMerhod;
import org.User;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class UserServiceImpl implements UserService,ApplicationContextAware{
private ApplicationContext contextAware;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.contextAware= applicationContext;
}
@Override
public void register(User user) {
System.out.println("rg.Service.UserServiceImpl.register");
//注意,方法得到了工厂的创建的对象
UserService userService= (UserService) contextAware.getBean("ExMethod");
userService.login("SY","45452");
}
@Override
public Boolean login(String name, String password) {
System.out.println("rg.Service.UserServiceImpl.login "+name+" "+password );
return true;
}
}
这次我们从工厂获得了代理对象再执行一次试试
可以看到,方法中的方法被代理了,问题也就解决了
13.spring 动态代理开发的切入点表达式
在前面总结了spring的动态代理开发的步骤,指定增强类(额外功能)的使用,那有了功能,还需要指定使用功能的地方,就需要切入表达式来指定切入的地点
1,演示文件结构和类实现
先看下文件目录和类实现,可以更好的演示切入点表达式所达到的效果
cutexpress包
UserPoxyBefore
用于指定增强的方法,如果实现了代理则可以看到打印出的增强类方法名
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class UserPoxyBefore implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("UserPoxyBefore.before");
}
}
userservice2接口
用于实现UserServiceImpl_type
public interface UserService2 {
void login(String id);
}
UserServiceImpl_type
用于测试同方法名不同参数个数
public class UserServiceImpl_type implements UserService2 {
@Override
public void login(String id) {
System.out.println("org.Service.Cutexpress.UserServiceImpl_type.login"+id);
}
}
cutexpress.UserServiceImpl
用于测试不同包的同类名,同方法名
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("org.Service.Cutexpress.UserServiceImpl.register");
}
@Override
public Boolean login(String name, String password) {
System.out.println("org.Service.Cutexpress.UserServiceImpl.login "+name+" "+password );
return true;
}
}
UserServiceImpl_FB
用于测试同包不同类名
public class UserServiceImpl_FB implements UserService {
@Override
public void register(User user) {
System.out.println("org.Service.Cutexpress.UserServiceImpl_FB.register");
}
@log
@Override
public Boolean login(String name, String password) {
System.out.println("org.Service.Cutexpress.UserServiceImpl_FB.login "+name+" "+password );
return true;
}
}
service 包
实现各种UserService接口的类
public interface UserService {
public void register(User user);
public void login(String name, String password);
}
UserServiceImpl
用于测试不同包同方法名
public class UserServiceImpl implements UserService {
@Override
public void register(User user) {
System.out.println("rg.Service.UserServiceImpl.register");
}
@Override
public Boolean login(String name, String password) {
System.out.println("rg.Service.UserServiceImpl.login "+name+" "+password );
return true;
}
}
配置文件
把各个类的bean写进去
expression暂时为空,等下填表达式测试
用的增强类UserPoxyBefore
<bean id="UserServicePiont" class="org.Service.Cutexpress.UserServiceImpl"/>
<bean id="UserServicePiont2" class="org.Service.UserServiceImpl"/>
<bean id="UserPoxyBefore" class="org.Service.Cutexpress.UserPoxyBefore" />
<bean id="FB" class="org.Service.Cutexpress.UserServiceImpl_FB"/>
<bean id="type" class="org.Service.Cutexpress.UserServiceImpl_type"/>
<aop:config >
<aop:pointcut id="UserPoxyPC" expression=""/>
<aop:advisor advice-ref="UserPoxyBefore" pointcut-ref="UserPoxyPC"/>
</aop:config>
测试类
@Test
public void test2() {
ApplicationContext context=new ClassPathXmlApplicationContext("/ApplicationContext2.XML");
//org.Service.UserServiceImpl 对比包
UserService userService_= (UserService) context.getBean("UserServicePiont2");
userService_.login("SY", "123456");
userService_.register(new User());
//org.Service.Cutexpress.UserServiceImpl 同类不同包
UserService userService= (UserService) context.getBean("UserServicePiont");
userService.login("SY", "123456");
userService.register(new User());
//org.Service.Cutexpress.UserServiceImpl_FB 同包不同类
UserService userService2=(UserService) context.getBean("FB");
userService2.login("SY", "123456");
//org.Service.Cutexpress.UserServiceImpl_type 实现的方法只有一个
UserService2 userService3=(UserService2) context.getBean("type");
userService3.login("32");
}
2.切入点表达式
1.expression函数(方法->类->包)
对于方法:
对所有方法都代理:*** (..)*
执行结果:所有方法均被代理
对于所有login方法: *** login(..)**
指定参数 数量,类型
对于只有一个String参数的:login(String)
对于有二个String参数的 :*** login(String,String)**
参数和..连用:*** login(String,..)**
表示第一个参数是String的所有方法
指定非Java.lang包中的对象:*** register(org.User)**
精确切入某个方法(权限名命)特别注意星号
*** org.Service.Cutexpress.UserServiceImpl_FB.login(..)**
对于类:
指定某个类的所有方法
*** org.Service.Cutexpress.UserServiceImpl.*(..)**
对于多级包(搜索所有包中符合方法名字的类)
*** ..UserServiceImpl.(..)**
对于包:
对于某个包的所有类的方法
*** org.Service..(..)**
对于某个包的子包
$$
- org.Service...(..)
$$
2.其他函数、
args
args主要用于参数的匹配,填写参数就可以,注意在expression中的expression替换为args 例如:
<aop:pointcut id="UserPoxyPC" expression=" args(String) "/>
args(String),匹配一个String参数的方法
within
within 用于对包,类的匹配(*代表类或者包)
within(*..UserServiceImpl) 匹配所有的UserServiceImpl类
within(org.Service.Cutexpress..*) 匹配所有的org.Service.Cutexpress包下的类
3.对于注解
自定义一个注解
package org;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface log {
}
在想实现代理的类上注解,如UserServiceImpl_FB
4.对于不同函数的运算
配置文件如:
<aop:pointcut id="UserPoxyPC" expression="execution(* login(..)) and args(String)"/>
and
比如 所有的login方法 同时满足 一个String参数都代理
execution(* login(..)) and args(String)
or
比如所有的login方法 和 一个String参数的方法都代理
login(..) or args(String)
3.总结
对于一个方法的实现我们要有以下要素:
权限 返回值 方法名(参数)
抽象为
权限 返回值 | 方法名 | (参数) |
---|---|---|
* | * | (..) |
不难发现*** (..)* 就是所有的方法的表达方式
无论需求是方法 类 还是包
第一个*就代表权限 第二个是路径 第三个是方法,以此类推。
同时一个表达式可以由多个表达式实现,不要拘泥于某个表达方式