簡介
此項目使用Spring+SpringMVC+MyBatis框架整合,用於企業后台權限管理。數據庫使用MySQL,前端頁面使用Jsp基於AdminLTE模板進行改寫。
頁面展示



功能介紹
- 商品查詢
- 基於SSM整合基礎上完成商品查詢,實現主頁頁面main.jsp以及商品顯示頁面product-list.jsp頁面的創建。
- 商品添加
- 進一步鞏固SSM整合,並完成商品添加功能。實現頁面product-add.jsp的創建。
- 訂單查詢
- 訂單的查詢操作,它主要完成簡單的多表查詢操作,查詢訂單時,需要查詢出與訂單關聯的其它表中信息。
- 訂單分頁查詢
- 訂單分頁查詢,這里使用的是mybatis分頁插件PageHelper。
- 訂單詳情查詢
- 訂單詳情是用於查詢某一個訂單的詳細信息,主要涉及復雜的多表查詢操作。
- Spring Security
- Spring Security是 Spring 項目組中用來提供安全認證服務的框架。此項目中只涉及Spring Security框架的配置及基本的認證與授權操作。
- 用戶管理
- 用戶管理中實現了基於Spring Security的用戶登錄、退出操作,以及用戶查詢、添加、詳情等操作,和訂單模塊類似。
- 角色管理
- 角色管理主要完成角色查詢、角色添加。角色擁有對應的權限。
- 資源權限管理
- 資源權限管理主要完成查詢、添加操作,它的操作與角色管理類似,角色管理以及資源權限管理都是對權限管理的
補充。
- 資源權限管理主要完成查詢、添加操作,它的操作與角色管理類似,角色管理以及資源權限管理都是對權限管理的
- 權限關聯與控制
- 完成用戶角色關聯、角色權限關聯,這兩個操作是為了后續完成授權操作的基礎。
- AOP日志處理
- 使用Spring AOP切面來完成系統級別的日志收集。
數據庫介紹
數據庫使用MySQL
- 產品表

- 訂單表

- 會員表

- 旅客表

- 訂單旅客表

- 用戶表

- 角色表

- 用戶角色表
由 userId 和 roleId 構成,分別為users表 以及 role表的外鍵,用來關聯用戶與角色的多對多關系
- 資源權限表

- 權限角色表
由 perimissionId 和 roleId 構成,分別為permission表 以及 role表的外鍵,用來關聯資源權限與角色的多對多關系。
- 日志表

SSM整合

Spring環境搭建
- 編寫Spring配置文件applicationContext.xml
- 配置spring創建容器時要掃描的包,開啟注解掃描,管理service和dao。
- 使用注解配置業務層
Spring MVC環境搭建
- web.xml配置Spring MVC核心控制器
-
配置初始化參數,用於讀取springmvc的配置文件 -
配置 servlet 的對象的創建時間點:應用加載時創建。取值只能是非 0 正整數,表示啟動順序 - 配置SpringMVC編碼過濾器等
-
- 配置Spring MVC配置文件springmvc.xml
- 配置掃描controller的注解
- 配置視圖解析器
- 設置靜態資源不過濾
- 開啟對SpringMVC注解的支持
- 編寫Controller
Spring 與 Spring MVC 整合
在 web.xml 中
- 配置加載類路徑的配置文件,加載 applicationContext.xml 以及 用於權限認證的 spring-security.xml
- 配置監聽器
Spring 與 MyBatis 整合
整合思路:將mybatis配置文件(mybatis.xml)中內容配置到spring配置文件中。
- Spring接管mybatis的Session工廠
-
創建 db.properties 存放數據庫連接屬性
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=root -
在 applicationContext.xml 中配置連接池
-
將 SqlSessionFactory 交給IOC管理
-
自動掃描所有Mapper接口和文件
- 掃描dao接口
-
配置Spring事務
配置Spring的聲明式事務管理
SSM產品操作
主要包括查詢所有產品以及添加產品兩個功能,下面是兩個功能的流程圖。


商品的狀態屬性數據庫存放的為int數據 productStatus,0代表關閉1代表開啟,實體類中多添加了一個String類型的變量為productStatusStr,在該變量的getter中對productStatus進行判斷並處理成對應屬性以放到頁面中展示。
出發時間的屬性通過 @DateTimeFormat(pattern="yyyy-MM-dd HH:mm") 注解來轉換格式,並編寫了一個工具類data2String,將時間類轉換成字符串用於頁面展示。
springmvc參數類型轉換三種方式
-
實體類中加日期格式化注解
@DateTimeFormat(pattern="yyyy-MM-dd hh:MM") private Date creationTime; -
屬性編輯器
spring3.1之前 在Controller類中通過@InitBinder完成
/** * 在controller層中加入一段數據綁定代碼 * @param webDataBinder */ @InitBinder public void initBinder(WebDataBinder webDataBinder) throws Exception{ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm"); simpleDateFormat.setLenient(false); webDataBinder.registerCustomEditor(Date.class , new CustomDateEditor(simpleDateFormat , true)); }**備注:自定義類型轉換器必須實現PropertyEditor接口或者繼承PropertyEditorSupport類 **
寫一個類 extends propertyEditorSupport(implements PropertyEditor){ public void setAsText(String text){ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy -MM-dd hh:mm"); Date date = simpleDateFormat.parse(text); this.setValue(date); } public String getAsTest(){ Date date = (Date)this.getValue(); return this.dateFormat.format(date); } } -
類型轉換器Converter
(spring 3.0以前使用正常,以后的版本需要使用< mvc:annotation-driven/>注冊使用)使用xml配置實現類型轉換(系統全局轉換器)
(1)注冊conversionservice
<!-- 注冊ConversionService-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.ezubo.global.portal.util.StringToDateConverter">
<constructor-arg index="0" value="yyyy-MM-dd hh:mm"/>
</bean>
</set>
</property>
</bean>
StringToDateConverter.java的實現
public class StringToDateConverter implements Converter<String,Date> {
private static final Logger logger = LoggerFactory.getLogger(StringToDateConverter.class);
private String pattern;
public StringToDateConverter(String pattern){
this.pattern = pattern;
}
public Date convert(String s) {
if(StringUtils.isBlank(s)){
return null;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
simpleDateFormat.setLenient(false);
try{
return simpleDateFormat.parse(s);
}catch(ParseException e){
logger.error("轉換日期異常:"+e.getMessage() , e);
throw new IllegalArgumentException("轉換日期異常:"+e.getMessage() , e);
}
}
}
(2)使用 ConfigurableWebBindingInitializer 注冊conversionService
<!--使用 ConfigurableWebBindingInitializer 注冊conversionService-->
<bean id="webBindingInitializer" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<property name="conversionService" ref="conversionService"/>
</bean>
(3)注冊ConfigurableWebBindingInitializer到RequestMappingHandlerAdapter
<!-- 注冊ConfigurableWebBindingInitializer 到RequestMappingHandlerAdapter-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="webBindingInitializer" ref="webBindingInitializer"/>
<!-- 線程安全的訪問session-->
<property name="synchronizeOnSession" value="true"/>
</bean>
(spring 3.2以后使用正常)使用<mvc:annotation-driven/>注冊conversionService
(1)注冊ConversionService
<!-- 注冊ConversionService-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.ezubo.global.portal.util.StringToDateConverter">
<constructor-arg index="0" value="yyyy-MM-dd hh:mm"/>
</bean>
</set>
</property>
</bean>
(2)需要修改springmvc.xml配置文件中的annotation-driven,增加屬性conversion-service指向新增的 conversionService。
<mvc:annotation-driven conversion-service="conversionService">
<mvc:message-converters register-defaults="true">
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"/>
<!--轉換時設置特性-->
<property name="features">
<array>
<!--避免默認的循環引用替換-->
<ref bean="DisableCircularReferenceDetect"/>
<ref bean="WriteMapNullValue"/>
<ref bean="WriteNullStringAsEmpty"/>
<ref bean="WriteNullNumberAsZero"/>
</array>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
在此項目中使用的是第一種,比較簡便。
SSM訂單操作
訂單操作的相關功能介紹:


訂單的查詢操作,它主要完成簡單的多表查詢操作,查詢訂單時,需要查詢出與訂單關聯的其它表中信息。下圖為訂單表及其關聯表關系。

下圖為查詢所有訂單流程:

下圖為查詢訂單詳情流程:

PageHelper
使用PageHelper進行分頁查詢,PageHelper是國內非常優秀的一款開源的mybatis分頁插件,它支持基本主流與常用的數據庫,例如mysql、oracle、mariaDB、DB2、SQLite、Hsqldb等。
PageHelper使用起來非常簡單,只需要導入依賴然后在spring配置文件中配置后即可使用。
分頁插件參數介紹:
helperDialect:分頁插件會自動檢測當前的數據庫鏈接,自動選擇合適的分頁方式。 你可以配置
helperDialect 屬性來指定分頁插件使用哪種方言。配置時,可以使用下面的縮寫值:
oracle , mysql , mariadb , sqlite , hsqldb , postgresql , db2 , sqlserver , informix , h2 , sqlserver201
2 , derby
特別注意 :使用 SqlServer2012 數據庫時,需要手動指定為 sqlserver2012 ,否則會使用 SqlServer2005 的
方式進行分頁。
你也可以實現 AbstractHelperDialect ,然后配置該屬性為實現類的全限定名稱即可使用自定義的實現方
法。offsetAsPageNum:默認值為 false ,該參數對使用 RowBounds 作為分頁參數時有效。 當該參數設置為
true 時,會將 RowBounds 中的 offset 參數當成 pageNum 使用,可以用頁碼和頁面大小兩個參數進行分
頁。rowBoundsWithCount:默認值為 false ,該參數對使用 RowBounds 作為分頁參數時有效。 當該參數設置
為 true 時,使用 RowBounds 分頁會進行 count 查詢。pageSizeZero:默認值為 false ,當該參數設置為 true 時,如果 pageSize=0 或者 RowBounds.limit =
0 就會查詢出全部的結果(相當於沒有執行分頁查詢,但是返回結果仍然是 Page 類型)。reasonable:分頁合理化參數,默認值為 false 。當該參數設置為 true 時, pageNum<=0 時會查詢第一
頁, pageNum>pages (超過總數時),會查詢最后一頁。默認 false 時,直接根據參數進行查詢。params:為了支持 startPage(Object params) 方法,增加了該參數來配置參數映射,用於從對象中根據屬
性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable ,不配置映射的用默認值, 默認
值為pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
。supportMethodsArguments:支持通過 Mapper 接口參數來傳遞分頁參數,默認值 false ,分頁插件會從查
詢方法的參數值中,自動根據上面 params 配置的字段中取值,查找到合適的值時就會自動分頁。 使用方法
可以參考測試代碼中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTest 和
ArgumentsObjTest 。autoRuntimeDialect:默認值為 false 。設置為 true 時,允許在運行時根據多數據源自動識別對應方言
的分頁 (不支持自動選擇 sqlserver2012 ,只能使用 sqlserver ),用法和注意事項參考下面的場景五。closeConn:默認值為 true 。當使用運行時動態數據源或沒有設置 helperDialect 屬性自動獲取數據庫類
型時,會自動獲取一個數據庫連接, 通過該屬性來設置是否關閉獲取的這個連接,默認 true 關閉,設置為
false 后,不會關閉獲取的連接,這個參數的設置要根據自己選擇的數據源來決定。
基本使用有6種方式,最常用的有兩種:
- RowBounds方式的調用
List<Country> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(1, 10));
使用這種調用方式時,可以使用RowBounds參數進行分頁,這種方式侵入性最小,通過RowBounds方式調用只是使用這個參數並沒有增加其他任何內容。分頁插件檢測到使用了RowBounds參數時,就會對該查詢進行物理分頁。
關於這種方式的調用,有兩個特殊的參數是針對 RowBounds 的,具體參考上面的分頁插件參數介紹。
注:不只有命名空間方式可以用RowBounds,使用接口的時候也可以增加RowBounds參數,例如:
//這種情況下也會進行物理分頁查詢
List<Country> selectAll(RowBounds rowBounds);
注意: 由於默認情況下的
RowBounds無法獲取查詢總數,分頁插件提供了一個繼承自RowBounds的
PageRowBounds,這個對象中增加了total屬性,執行分頁查詢后,可以從該屬性得到查詢總數。
- PageHelper.startPage靜態方法調用
這種方式在你需要進行分頁的 MyBatis 查詢方法前調用 PageHelper.startPage 靜態方法即可,緊
跟在這個方法后的第一個MyBatis 查詢方法會被進行分頁。
例如:
//獲取第1頁,10條內容,默認查詢總數count
PageHelper.startPage(1, 10);
//緊跟着的第一個select方法會被分頁
List<Country> list = countryMapper.selectIf(1);
使用步驟總結如下:

SSM權限操作
主要涉及用戶、角色、資源權限三個模塊的功能,下圖為三表的關系。

Spring Security
Spring Security 的前身是 Acegi Security ,是 Spring 項目組中用來提供安全認證服務的框架。
Spring Security 為基於J2EE企業應用軟件提供了全面安全服務。包括兩個主要操作:
- “認證”,是為用戶建立一個他所聲明的主體。主體一般式指用戶,設備或可以在你系統中執行動作的其他系
統。 - “授權”指的是一個用戶能否在你的應用中執行某個操作,在到達授權判斷之前,身份的主題已經由身份驗證
過程建立了。
快速入門步驟如下:

用戶管理
用戶登錄
使用數據庫完成springSecurity用戶登錄流程:

spring security的配置
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
<!-- 配置加密的方式
<security:password-encoder ref="passwordEncoder"/>
-->
</security:authentication-provider>
</security:authentication-manager>
Service
@Service("userService")
@Transactional
public class UserServiceImpl implements IUserService {
@Autowired
private IUserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = userDao.findByUsername(username);
List<Role> roles = userInfo.getRoles();
List<SimpleGrantedAuthority> authoritys = getAuthority(roles);
User user = new User(userInfo.getUsername(), "{noop}" + userInfo.getPassword(),
userInfo.getStatus() == 0 ? false : true, true, true, true, authoritys);
return user;
}
private List<SimpleGrantedAuthority> getAuthority(List<Role> roles) {
List<SimpleGrantedAuthority> authoritys = new ArrayList();
for (Role role : roles) {
authoritys.add(new SimpleGrantedAuthority(role.getRoleName()));
}
return authoritys;
}
}
這里從userInfo中 getPassword 前面需要加上"{noop}"是因為數據庫中的密碼還未進行加密,后續在添加用戶中進行加密處理后即可刪除。
Dao
public interface IUserDao {
@Select("select * from user where id=#{id}")
public UserInfo findById(Long id) throws Exception;
@Select("select * from user where username=#{username}")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(column = "username", property = "username"),
@Result(column = "email", property = "email"),
@Result(column = "password", property = "password"),
@Result(column = "phoneNum", property = "phoneNum"),
@Result(column = "status", property = "status"),
@Result(column = "id", property = "roles", javaType = List.class, many =
@Many(select = "com.itheima.ssm.dao.IRoleDao.findRoleByUserId")) })
public UserInfo findByUsername(String username);
}
用戶退出
使用spring security完成用戶退出,非常簡單
- 配置
<security:logout invalidate-session="true" logout-url="/logout.do" logout-success-
url="/login.jsp" />
- 頁面中
<a href="${pageContext.request.contextPath}/logout.do"
class="btn btn-default btn-flat">注銷</a>
用戶查詢

用戶添加

- 添加完成后通過redirect 重定向跳轉到查詢所有用戶。
- 前期數據庫存的用戶密碼沒有加密,現在添加用戶時,我們需要對用戶密碼進行加密。
<!-- 配置加密類 -->
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
用戶詳情

Dao
@Select("select * from user where id=#{id}")
@Results({ @Result(id = true, property = "id", column = "id"), @Result(column = "username",
property = "username"),
@Result(column = "email", property = "email"), @Result(column ="password", property = "password"),
@Result(column = "phoneNum", property = "phoneNum"), @Result(column ="status", property = "status"),
@Result(column = "id", property = "roles", javaType = List.class, many =
@Many(select = "com.itheima.ssm.dao.IRoleDao.findRoleByUserId")) })
public UserInfo findById(Long id) throws Exception;
@Select("select * from role where id in( select roleId from user_role where userId=#{userId})")
@Results(
{
@Result(id=true,column="id",property="id"),
@Result(column="roleName",property="roleName"),
@Result(column="roleDesc",property="roleDesc"), @Result(column="id",property="permissions",javaType=List.class,many=@Many(select="com.itheima.ssm
.dao.IPermissionDao.findByRoleId"))
})
public List<Role> findRoleByUserId(Long userId);
我們需要將用戶的所有角色及權限查詢出來所以需要調用IRoleDao中的findRoleByUserId,而在IRoleDao中需要調用IPermissionDao的findByRoleId
@Select("select * from permission where id in (select permissionId from role_permission where
roleId=#{roleId})")
public List<Permission> findByRoleId(Long roleId);
角色管理
角色查詢

角色添加

資源權限管理
資源權限查詢以及添加的流程和角色管理模塊的一樣(參考上圖),只是針對的表不同。
權限的關聯與控制
用戶角色關聯
用戶與角色之間是多對多關系,我們要建立它們之間的關系,只需要在中間表user_role插入數據即可。
流程如下:

角色權限關聯
角色與權限之間是多對多關系,我們要建立它們之間的關系,只需要在中間表role_permission插入數據即可。
流程和用戶角色關聯相同,參考上圖。
服務器端方法級權限控制
在服務器端我們可以通過Spring security提供的注解對方法來進行權限控制。Spring Security在方法的權限控制上支持三種類型的注解,JSR-250注解、@Secured注解和支持表達式的注解,這三種注解默認都是沒有啟用的,需要單獨通過global-method-security元素的對應屬性進行啟用。
開啟注解使用
- 配置文件
<security:global-method-security jsr250-annotations="enabled"/>
<security:global-method-security secured-annotations="enabled"/>
<security:global-method-security pre-post-annotations="disabled"/> - 注解開啟
@EnableGlobalMethodSecurity :Spring Security默認是禁用注解的,要想開啟注解,需要在繼承WebSecurityConfigurerAdapter的類上加@EnableGlobalMethodSecurity注解,並在該類中將AuthenticationManager定義為Bean。
JSR-250注解
- @RolesAllowed表示訪問對應方法時所應該具有的角色
示例:
@RolesAllowed({"USER", "ADMIN"}) 該方法只要具有"USER", "ADMIN"任意一種權限就可以訪問。這里可以省略前綴ROLE_,實際的權限可能是ROLE_ADMIN
- @PermitAll表示允許所有的角色進行訪問,也就是說不進行權限控制
- @DenyAll是和PermitAll相反的,表示無論什么角色都不能訪問

支持表達式的注解
- @PreAuthorize 在方法調用之前,基於表達式的計算結果來限制對方法的訪問
示例:
@PreAuthorize("#userId == authentication.principal.userId or hasAuthority(‘ADMIN’)")
void changePassword(@P("userId") long userId ){ }
這里表示在changePassword方法執行之前,判斷方法參數userId的值是否等於principal中保存的當前用戶的userId,或者當前用戶是否具有ROLE_ADMIN權限,兩種符合其一,就可以訪問該方法。
- @PostAuthorize 允許方法調用,但是如果表達式計算結果為false,將拋出一個安全性異常
示例:
@PostAuthorize
User getUser("returnObject.userId == authentication.principal.userId or
hasPermission(returnObject, 'ADMIN')");
- @PostFilter 允許方法調用,但必須按照表達式來過濾方法的結果
- @PreFilter 允許方法調用,但必須在進入方法之前過濾輸入值

@Secured注解
- @Secured注解標注的方法進行權限控制的支持,其值默認為disabled。
示例:
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);
@Secured("ROLE_TELLER")

頁面端標簽控制權限
在jsp頁面中我們可以使用spring security提供的權限標簽來進行權限控制

導入:
- maven導入
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>version</version>
</dependency>
- 頁面導入
<%@taglib uri="http://www.springframework.org/security/tags" prefix="security"%>
常用標簽
在jsp中我們可以使用以下三種標簽,其中authentication代表的是當前認證對象,可以獲取當前認證對象信息,例如用戶名。其它兩個標簽我們可以用於權限控制
authentication
<security:authentication property="" htmlEscape="" scope="" var=""/>
- property: 只允許指定Authentication所擁有的屬性,可以進行屬性的級聯獲取,如“principle.username”,不允許直接通過方法進行調用
- htmlEscape:表示是否需要將html進行轉義。默認為true
- scope:與var屬性一起使用,用於指定存放獲取的結果的屬性名的作用范圍,默認我pageContext。Jsp中擁有的作用范圍都進行進行指定
- var: 用於指定一個屬性名,這樣當獲取到了authentication的相關信息后會將其以var指定的屬性名進行存放,默認是存放在pageConext中
authorize
authorize是用來判斷普通權限的,通過判斷用戶是否具有對應的權限而控制其所包含內容的顯示
<security:authorize access="" method="" url="" var=""></security:authorize>
- access: 需要使用表達式來判斷權限,當表達式的返回結果為true時表示擁有對應的權限
- method:method屬性是配合url屬性一起使用的,表示用戶應當具有指定url指定method訪問的權限,method的默認值為GET,可選值為http請求的7種方法
- url:url表示如果用戶擁有訪問指定url的權限即表示可以顯示authorize標簽包含的內容
- var:用於指定將權限鑒定的結果存放在pageContext的哪個屬性中
accesscontrollist
accesscontrollist標簽是用於鑒定ACL權限的。其一共定義了三個屬性:hasPermission、domainObject和var,
其中前兩個是必須指定的
<security:accesscontrollist hasPermission="" domainObject="" var=""></security:accesscontrollist>
- hasPermission:hasPermission屬性用於指定以逗號分隔的權限列表
- domainObject:domainObject用於指定對應的域對象
- var:var則是用以將鑒定的結果以指定的屬性名存入pageContext中,以供同一頁面的其它地方使用
SSMAOP日志
基於AOP來獲取每一次操作的訪問時間、操作者用戶名、訪問ip、訪問資源url、執行市場以及訪問方法存入到數據庫日志表sysLog中,並展示到頁面中。
流程如下:

創建切面類處理日志
@Component
@Aspect
public class LogAop {
@Autowired
private HttpServletRequest request;
@Autowired
private ISysLogService sysLogService;
private Date startTime; // 訪問時間
private Class executionClass;// 訪問的類
private Method executionMethod; // 訪問的方法
// 主要獲取訪問時間、訪問的類、訪問的方法
@Before("execution(* com.itheima.ssm.controller.*.*(..))")
public void doBefore(JoinPoint jp) throws NoSuchMethodException, SecurityException {
startTime = new Date(); // 訪問時間
// 獲取訪問的類
executionClass = jp.getTarget().getClass();
// 獲取訪問的方法
String methodName = jp.getSignature().getName();// 獲取訪問的方法的名稱
Object[] args = jp.getArgs();// 獲取訪問的方法的參數
if (args == null || args.length == 0) {// 無參數
executionMethod = executionClass.getMethod(methodName); // 只能獲取無參數方法
} else {
// 有參數,就將args中所有元素遍歷,獲取對應的Class,裝入到一個Class[]
Class[] classArgs = new Class[args.length];
for (int i = 0; i < args.length; i++) {
classArgs[i] = args[i].getClass();
}
executionMethod = executionClass.getMethod(methodName, classArgs);// 獲取有參數方法
}
}
// 主要獲取日志中其它信息,時長、ip、url...
@After("execution(* com.itheima.ssm.controller.*.*(..))")
public void doAfter(JoinPoint jp) throws Exception {
// 獲取類上的@RequestMapping對象
if (executionClass != SysLogController.class) {
RequestMapping classAnnotation = (RequestMapping)executionClass.getAnnotation(RequestMapping.class);
if (classAnnotation != null) {
// 獲取方法上的@RequestMapping對象
RequestMapping methodAnnotation = executionMethod.getAnnotation(RequestMapping.class);
if (methodAnnotation != null) {
String url = ""; // 它的值應該是類上的@RequestMapping的value+方法上的@RequestMapping的value
url = classAnnotation.value()[0] + methodAnnotation.value()[0];
SysLog sysLog = new SysLog();
// 獲取訪問時長
Long executionTime = new Date().getTime() - startTime.getTime();
// 將sysLog對象屬性封裝
sysLog.setExecutionTime(executionTime);
sysLog.setUrl(url);
// 獲取ip
String ip = request.getRemoteAddr();
sysLog.setIp(ip);
// 可以通過securityContext獲取,也可以從request.getSession中獲取
SecurityContext context = SecurityContextHolder.getContext(); //request.getSession().getAttribute("SPRING_SECURITY_CONTEXT")
String username = ((User)
(context.getAuthentication().getPrincipal())).getUsername();
sysLog.setUsername(username);
sysLog.setMethod("[類名]" + executionClass.getName() + "[方法名]" +
executionMethod.getName());
sysLog.setVisitTime(startTime);
// 調用Service,調用dao將sysLog insert數據庫
sysLogService.save(sysLog);
}
}
}
}
}
在切面類中我們需要獲取登錄用戶的username,還需要獲取ip地址,我們怎么處理?
-
username獲取
SecurityContextHolder獲取
-
ip地址獲取
ip地址的獲取我們可以通過request.getRemoteAddr()方法獲取到。
在Spring中可以通過RequestContextListener來獲取request或session對象。

SysLogController
@RequestMapping("/sysLog")
@Controller
public class SysLogController {
@Autowired
private ISysLogService sysLogService;
@RequestMapping("/findAll.do")
public ModelAndView findAll() throws Exception {
ModelAndView mv = new ModelAndView();
List<SysLog> sysLogs = sysLogService.findAll();
mv.addObject("sysLogs", sysLogs);
mv.setViewName("syslog-list");
return mv;
}
}
Service
@Service
@Transactional
public class SysLogServiceImpl implements ISysLogService {
@Autowired
private ISysLogDao sysLogDao;
@Override
public void save(SysLog log) throws Exception {
sysLogDao.save(log);
}
@Override
public List<SysLog> findAll() throws Exception {
return sysLogDao.findAll();
}
}
Dao
public interface ISysLogDao {
@Select("select * from syslog")
@Results({
@Result(id=true,column="id",property="id"),
@Result(column="visitTime",property="visitTime"),
@Result(column="ip",property="ip"),
@Result(column="url",property="url"),
@Result(column="executionTime",property="executionTime"),
@Result(column="method",property="method"),
@Result(column="username",property="username")
})
public List<SysLog> findAll() throws Exception;
@Insert("insert into syslog(visitTime,username,ip,url,executionTime,method) values(#{visitTime},#{username},#{ip},#{url},#{executionTime},#{method})")
public void save(SysLog log) throws Exception;
}
