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.總結
對於一個方法的實現我們要有以下要素:
權限 返回值 方法名(參數)
抽象為
權限 返回值 | 方法名 | (參數) |
---|---|---|
* | * | (..) |
不難發現*** (..)* 就是所有的方法的表達方式
無論需求是方法 類 還是包
第一個*就代表權限 第二個是路徑 第三個是方法,以此類推。
同時一個表達式可以由多個表達式實現,不要拘泥於某個表達方式