• [Spring学习笔记 4 ] AOP 概念原理以及java动态代理


    一、Spring IoC容器补充(1)
    Spring IoC容器,DI(依赖注入):
    注入的方式:设值方法注入setter(属性注入)/构造子注入(构造函数传入依赖的对象)/字段注入Field(注解)/接口注入 out
    装配的方式:手动装配(<property>、<constructor-arg>、@Autowired、@Resource)/自动装配(autowire="byName")
    
    1、装配各种类型的属性
    A、简单属性-value属性或者value元素
    <bean id="p1" class="cn.itcasg.gz.springioc.Person">
        <property name="name" value="张无忌"></property>
        <!-- property元素中的内容叫属性值,会自动把value描述的值转换成对应属性的类型 -->
        <property name="age"><value>68</value></property>
        <!-- 也可以使用value来装配一些Spring支持的类型URL,Class -->
        <property name="homePage" value="http://www.itcast.cn"></property>
    </beans>
    
    B、引用其它bean-使用ref属性或者标签。
    <property name="parent">
                <!-- ref引用其它的 bean,local表示引用本容器中的bean,parent表示引用父容器中某个bean,bean表示引入某个bean,先在当容器中找,然后再到父容器中找 -->
                <ref bean="p0" />
    </property>
    
    C、内部bean
    <property name="parent">
                <!-- 值是内部Bean ,一般不需要指定bean的名称 -->
                <bean class="cn.itcasg.gz.springioc.Person">
                    <constructor-arg value="张三丰"></constructor-arg>
                </bean>
            </property>
    D、装配集合
    (1)、数组
    代码
    public void setFavs(String[] favs) {
            this.favs = favs;
        }
    
    配置
        <!-- 装配数组 -->
            <property name="favs">
                <array>
                    <value>足球</value>
                    <value>蓝球</value>
                    <value>音乐</value>
                </array>
            </property>
    
    (2)、list
    代码
    public void setSchools(List<String> schools) {
            this.schools = schools;
        }
    
    配置
    <property name="schools">
                <list>
                    <value>北大</value>
                    <value>清华</value>
                </list>
            </property>
    
    (3)、set
    public void setCitys(Set<String> citys) {
            this.citys = citys;
        }
    
    <property name="citys">
                <set>
                    <value>广州</value>
                    <value>北京</value>
                    <value>西安</value>
                    <value>广州</value>
                    
                </set>
            </property>
    
    D、装配Map
    
        public void setScores(Map<String, Double> scores) {
            this.scores = scores;
        }
    <property name="scores">
            <map>
            <entry key="语文" value="50"></entry>
            <entry key="数学" value="30"></entry>
            <entry key="历史" value="20"></entry>
            <!-- key-ref,value-ref属性用来引用其它bean -->
            </map>
            </property>
    
    E、装配Properties
    public void setIms(Properties ims) {
            this.ims = ims;
        }
    
    
    <!-- 装配属性类型 -->
            <!-- 
            <property name="ims">
            <props>
            <prop key="qq">4858458</prop>
            <prop key="msn">caishiyou@sina.com</prop>
            </props>
            </property> -->
            <!-- 在value中直接使用键值对来作为属性内容 -->
            <property name="ims">
            <value>
            qq=58568565868
            msn=kdkdkf@ddd.com
            </value>
            </property>
    
    F、装配空值null
    
        <property name="age">
        <!-- null标签用来指定空值 -->
        <null/>
        </property>
    
    
    2、注解装配解决方案1(XML+注解配合)
    xml用来定义Bean的信息;注解用来配置依赖信息。
    
    (1)、在配置文件中配置bean
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
        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-3.0.xsd
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-3.0.xsd
               ">
    
        <!-- 开启注解配置 -->
        <context:annotation-config/>
        <bean id="personDao" class="cn.itcasg.gz.springioc.PersonDaoImpl">
        </bean>
        <bean id="personDao2" class="cn.itcasg.gz.springioc.PersonDaoImpl">
        </bean>
        
        <bean id="dao" class="cn.itcasg.gz.springioc.Person">
        </bean>
    
        <bean id="personService" class="cn.itcasg.gz.springioc.PersonServiceImpl">
        </bean>
    
    </beans>     
    (2)、在JavaBean中用注解来指定依赖注入
    @Resource(Java标签的资源注解),@Autowired(Spring自定的注解),注解可以加在setter方法上(setter注入)/可以加在Field上(Field注入)。
    @Autowired--按类型注入。required=false用来指定该依赖的对象是否是必须的。
    @Resource--按名字注入,找到名字相同,则直接注入。找不到名字相同,则找类型。可能在@Resource使用name属性来指定要注入的Bean的名称。
    @Qualifier("personDao2")--用来指定要注入的Bean叫什么名字,与@Autowired配合使用。
    
    代码:
    public class PersonServiceImpl implements IPersonService {
        
        private IPersonDao dao;
        //@Resource(name="personDao2")
        @Autowired
        @Qualifier("personDao2")
        public void setDao(IPersonDao dao) {
            System.out.println("setDao()...");
            this.dao = dao;
        }
    
    推荐使用@Resource
    
    (3)、在配置文件中开启注解扫描
    <!-- 开启注解配置 -->
        <context:annotation-config/>
    
    
    
    3、使用注解方案2,完全使用注解---(掌握)
    (1)、如何指定哪些业务组件是需要放到Spring容器中管理的--
    A、在实现类上添加注解:@Repository(持久层组件)、@Service(业务层的组件)、@Controller(控件层组件)、@Component(不知道哪一层就用他) 。
    
    @Repository("personDao")
    public class PersonDaoImpl2 implements IPersonDao {
    }
    
    如何指定Bean的名称:默认的Bean名称会是类名首字母变小写,使用value属性就可以指定具体的Bean名称。
    
    Bean的作用域:默认是单例singleton。其它作用域,则使用@Scope标签来指定。比如:
    @Controller("personAction")
    @Scope("prototype")
    public class PersonAction{
    }
    
    B、在配置文件中使用<context:component-scan>指定让Spring去扫描哪些包
    <context:component-scan base-package="cn.itcasg.gz,cn.itcasg.bj"></context:component-scan>
    
    (2)、给组件指定依赖--使用@Autowire或@Resource标签
    
    @Resource(name="personDao")
    public void setDao(IPersonDao dao) {
    }
    
    二、Spring IOC补充(2)
    1、自定义属性--如果使用value来注入非简单类型的对象
    只要依赖的对象他有一个只带一个参数(并且类型为String)的构造函数,他就可以转换。
    A、自定义类型编辑器--PropertyEditorSupport
    
    public class MyDateEditor extends PropertyEditorSupport {
        private SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        @Override
        public void setAsText(String text) throws IllegalArgumentException {
            try {
                System.out.println("调用自定义的类型编辑器setAsText()..."+text);
                Date date=sdf.parse(text);
                setValue(date);
            } catch (ParseException e) {
                e.printStackTrace();
                super.setAsText(text);
            }
        }
    
    }
    
    B、在容器中注册类型编辑器
    
        <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
            <property name="customEditors">
                <map>
                    <entry key="java.util.Date" value="cn.itcast.gz.springioc.MyDateEditor"></entry>
                </map>
            </property>
        </bean>
    
    
    2、容器扩展点及应用--Spring提供了一些接口,让我们通过这些接口可以扩充容器的功能。
    (1)、BeanPostProcessor--每一个Bean创建完以后均会执行Processor的相关方法
    
    (2)、 BeanFactoryPostProcessor--当容器把配置信息加载完以后,对象没有初始之前,可以加入自己的功能。
    
    (3)、属性配置,利用容器扩展点来让配置中支持${db.url}类似的表达式,db.url中的值是存在属性文件。
    属性文件:db.properties
    db.url=jdbc:mysql:///itcast
    db.driverClass=my.dd.d.JdbcDiver
    db.username=root
    db.password=abc
    
    Spring配置文件:
     <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
     <property name="location" value="db.properties"></property>
     </bean>
    
    简化方案:<context:property-placeholder location="db.properties"/>      
    
    <bean id="dataSource" class="cn.itcast.gz.springioc.MyDataSource">
    <property name="url" value="${db.url}"></property>
    <property name="driverClass" value="${db.driverClass}"></property>
    <property name="username" value="${db.username}"></property>
    <property name="password" value="${db.password}"></property>
    </bean>
    
    
    3、工厂bean-FactoryBea--有一种比较特殊的Bean,这种Bean是用来生产各种各样产品的Bean。
    A、定义一个生产Person对象的FactoryBean,实现Spring提供的FactoryBean<Person> 接口即可。
    public class MyFactoryBean implements FactoryBean<Person> {
    
        //要产品的时候,会调用getObject()方法
        @Override
        public Person getObject() throws Exception {
            System.out.println("getObject()...");
            return new Person();
        }
    
        @Override
        public Class<Person> getObjectType() {
            System.out.println("getObjectType()...");
            return null;
        }
    
        @Override
        public boolean isSingleton() {
            System.out.println("isSingleton()...");
            return true;
        }
    
    }
    
    B、直接在配置文件中配置
    <bean id="bean1" class="cn.itcast.gz.springioc.MyFactoryBean"></bean>
    bean1的值为由MyFactoryBean的getObject()方法所返回的类型。
    
    
    、spring3.0中使用p及c命名空间简化配置
    
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:c="http://www.springframework.org/schema/c"
        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-3.0.xsd
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-3.0.xsd
               ">
    <bean id="p1" class="cn.itcast.gz.springioc.Person">
    <property name="name" value="张无忌"></property>
    <property name="age" value="18"></property>
    </bean>
    <!-- 使用p命名空间以后的方法 -->
    <bean id="p2" class="cn.itcast.gz.springioc.Person"
    p:name="张无忌" p:age="19"/>
    <!-- 使用构造函数 -->
    <bean id="p3" class="cn.itcast.gz.springioc.Person">
    <constructor-arg value="张三丰"></constructor-arg>
    <constructor-arg value="108"></constructor-arg>
     </bean>
    
     <!-- 使用c命名空间简化构造参数后的方法 -->
    <bean id="p4" class="cn.itcast.gz.springioc.Person"
    c:name="张三丰" c:age="108" p:parent-ref="p5"
    />
     <bean id="p5" class="cn.itcast.gz.springioc.Person"
    c:name="张大三" 
    />
    </beans>   
    
    
    三、代理模式
    1、代理模式概述--当客户端不能直接与真实对象打交道或者是需要扩充真实对象的功能又不想修改真实对象的代码时候,需要应用到代理。
    抽象主题
    真实主题
    代理主题
    
    2、JDK动态代理-对接口代理--(掌握)
    //真实对象
            final IHello realObject=new HelloImpl();
            //代理对象
            IHello hello=(IHello)Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(), new Class[]{IHello.class}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    System.out.println("准备执行"+method.getName()+"--"+new Date());
                    //代理对象去调用真实对象的相应方法去响应
                    Object ret=method.invoke(realObject, args);
                    System.out.println("执行完成!");
                    return ret;
                }
            });
            hello.sayHello();
          System.out.println(    hello.toString());
    
    3、使用第三方框架来实现对类的代理CGLIB,对类代理-(了解)
    public void proxyClass(){
            final HelloImpl2 realObject=new HelloImpl2();
            //使用CGLIB的Enhancer来创建类的代理
            Enhancer en=new Enhancer();
            en.setCallback(new InvocationHandler() {
                @Override
                public Object invoke(Object obj, Method method, Object[] args)
                        throws Throwable {
                    System.out.println("准备执行"+method.getName()+"--"+new Date());
                    Object ret=method.invoke(realObject, args);
                    System.out.println("执行完成!");
                    return ret;
                }
            });
            //设置父类
            en.setSuperclass(HelloImpl2.class);
            //创建出类的代理对象
            HelloImpl2 hello=(HelloImpl2) en.create();
            //调用代理对象上的方法,均分去执行Callback里面指定的invoke方法
            hello.sayHello();
            System.out.println(hello);
            System.out.println(hello.getClass());
        }
    
    四、AOP概念及框架原理--(掌握)
    
    1、AOP的应用场景-->OOP
    Aspect-切面(方面)
    当解决某一方面的问题的相同的代码零零散散的分布核心业务方法中。造成代码很难维护。
    
    2、传统解决横切性质问题的代码
    public class PersonDaoImpl implements IPersonDao {
        @Override
        public void save(Person p) {
            //切面2:记录日志
            System.out.println(UserContext.getUser()+"准备调用save方法,时间:"+new Date());
            //切面1:安全性问题的代码
            if(!"admin".equals(UserContext.getUser())){
                new RuntimeException("没有权限!");
            }
            // 核心业务逻辑
            System.out.println("执行添加:insert into person....");
            
            //切面3:审核功能
            System.out.println(UserContext.getUser()+"成功调用save方法,时间:"+new Date());
            
        }
    
        @Override
        public void update(Long id, Person p) {
            //切面2:记录日志
            System.out.println(UserContext.getUser()+"准备调用save方法,时间:"+new Date());
    
            //切面1:安全性问题的代码
            if(!"admin".equals(UserContext.getUser())){
                new RuntimeException("没有权限!");
            }
            System.out.println("执行修改:update into person....");
            
            //切面3:审核功能
            System.out.println(UserContext.getUser()+"成功调用save方法,时间:"+new Date());
    
        }
    
        @Override
        public void delete(Long id) {
            //切面2:记录日志
            System.out.println(UserContext.getUser()+"准备调用save方法,时间:"+new Date());
    
            //切面1:安全性问题的代码
            if(!"admin".equals(UserContext.getUser())){
                new RuntimeException("没有权限!");
            }
            System.out.println("执行删除:delete into person....");
            
            //切面3:审核功能
            System.out.println(UserContext.getUser()+"成功调用save方法,时间:"+new Date());
    
        }
    
    }
    3、使用AOP的解决方案--织入
    A、切面1
    public class SercurityAscpect {
    
        public void checkPermission(Method method){
            //切面1:安全性问题的代码
            System.out.println("执行权限检查....");
            if(!"admin".equals(UserContext.getUser())){
                throw new RuntimeException("没有权限!");
            }
        }
    }
    
    B、切面2
    public class WirteLogAspect {
        public void writeBeforeLog(Method method){
            //切面2:记录日志
            System.out.println(UserContext.getUser()+"准备调用"+method.getName()+"方法,时间:"+new Date());
        }
        public void writAfterLog(Method method){
            //切面3:审计功能
            System.out.println(UserContext.getUser()+"成功调用"+method.getName()+"方法,时间:"+new Date());
        }
    }
    
    C、核心业务逻辑(切面3)
    
    public class PersonDaoImpl implements IPersonDao {
        @Override
        public void save(Person p) {
            // 核心业务逻辑
            System.out.println("执行添加:insert into person....");
            
        }
    
        @Override
        public void update(Long id, Person p) {
            System.out.println("执行修改:update into person....");
    
        }
    
        @Override
        public void delete(Long id) {
            System.out.println("执行删除:delete into person....");
            
    
        }
    
    }
    、AOP框架实现-借助IoC容器,使用代理技术,解析配置文件生成不同的代理对象。
    public void aop() throws Exception{
            //读到配置信息
            //personDao.after=writeLogAscpect.writfterLog
            //得到容器中personDao的Bean.
            //创建一个personDao这个Bean一个代理对象
            //InvocationHalder对象的invoke方法中
            //1先调用真实对象的方法。
            //2调用writeLogAscpect这个切面Bean的writAfterLog方法
            //3返回
            //把代理对象存到容器        
            Properties properties = new Properties();
            properties.load(ContainerImpl.class.getResourceAsStream("/aop.properties"));
            //循环获得所有key
            for(Object item:properties.keySet()){
                //personDao.before=sercurityAspect.checkPermission,writeLogAscpect.writeBeforeLog
                final String key=item.toString();
                String value=properties.getProperty(key);
                
                //获得Bean的名称
                String beanName=key.substring(0,key.indexOf('.'));
                
                String[] vs=value.split(",");
                for(final String v:vs){
                    //得到要创建代理的对象
                    final Object bean=getBean(beanName);
                    //sercurityAspect.checkPermission
                    //给bean创建一个代理
                    //检测Bean有没有实现接口
                    if(bean.getClass().getInterfaces().length>0){
                        //使用JDK动态代理来创建代理
                        Object proxyBean=Proxy.newProxyInstance(this.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args)
                                    throws Throwable {
                                //v=sercurityAspect.checkPermission
                                //获得切面Bean,及切面方法
                                String aspectBeanName=v.substring(0,v.indexOf('.'));
                                String aspectMethodName=v.substring(v.indexOf('.')+1);
                                Object aspectBean=getBean(aspectBeanName);
                                //找到一个public而且只带一个(Method)类型参数,并且名称是aspectMethodName的方法
                                Method aspectMethod=aspectBean.getClass().getMethod(aspectMethodName, Method.class);
                                if(key.indexOf(".before")>0){
                                    //执行切面Bean上的aspectMethod方法
                                    aspectMethod.invoke(aspectBean,method);
                                }                            
                                //调用真实对象上的method方法
                                Object ret=method.invoke(bean, args);
                                if(key.indexOf(".after")>0){
                                    //执行切面Bean上的aspectMethod方法
                                    aspectMethod.invoke(aspectBean,method);
                                }    
                                return ret;
                            }
                        });
                        //把代理Bean给保存到缓存中
                        beans.put(beanName, proxyBean);
                    }
                    else {
                        //使用其它技术来创建代理
                    }
                }
            }
        }
  • 相关阅读:
    各种各样的Hashes
    爬取时出现报错 encoding error : input conversion failed due to input error, bytes 0xAC 0x4E 0x00 0x00
    Youtube.com中Uncaught TypeError: copy is not a function,如何解决
    生成“2021-Aug-27th”这样的1st,2nd,3rd, 4th 日期格式
    How to clear the content of the recycle bin in PowerShell?
    Delphi开发的服务在Windows2003 64位注册方式
    VS2015打开时,一直处理等待状态,并显示“Microsoft Visual Studio正忙”的提示窗,如下图
    Oracle操作类
    ie浏览器与其他浏览器兼容性问题解决
    echarts实时曲线【转】
  • 原文地址:https://www.cnblogs.com/amosli/p/3442449.html
Copyright © 2020-2023  润新知